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 0f1184527f Issue #5660 (#5665)
0f1184527f is described below
commit 0f1184527f620758127368ae130ad62a457e1e90
Author: Matt Casters <[email protected]>
AuthorDate: Mon Sep 8 16:05:54 2025 +0200
Issue #5660 (#5665)
* Issue #5660 (plus code cleanup and integration tests, MDI fixes, doc
update)
* Issue #5660 (dialog code hardening)
* rebase code
---------
Co-authored-by: Matt Casters <[email protected]>
Co-authored-by: Hans Van Akelyen <[email protected]>
---
.../ROOT/pages/pipeline/transforms/mergerows.adoc | 17 ++
integration-tests/mdi/0030-merge-rows-template.hpl | 289 ++++++++++++++++++++
integration-tests/mdi/0030-merge-rows.hpl | 282 ++++++++++++++++++++
.../mdi/datasets/golden-merge-rows.csv | 13 +
integration-tests/mdi/main-0030-merge-rows.hwf | 80 ++++++
.../mdi/metadata/dataset/golden-merge-rows.json | 40 +++
.../metadata/unit-test/0030-merge-rows UNIT.json | 43 +++
.../transforms/0077-merge-rows-diff.hpl | 291 ++++++++++++++++++++
integration-tests/transforms/0077-merge-rows.hpl | 292 +++++++++++++++++++++
.../transforms/datasets/golden-merge-rows-diff.csv | 13 +
.../transforms/datasets/golden-merge-rows.csv | 13 +
.../transforms/main-0077-merge-rows.hwf | 83 ++++++
.../metadata/dataset/golden-merge-rows-diff.json | 48 ++++
.../metadata/dataset/golden-merge-rows.json | 40 +++
.../metadata/unit-test/0077-merge-rows UNIT.json | 40 +++
.../unit-test/0077-merge-rows-diff UNIT.json | 48 ++++
.../pipeline/transforms/mergerows/MergeRows.java | 65 +++--
.../transforms/mergerows/MergeRowsDialog.java | 65 +++--
.../transforms/mergerows/MergeRowsMeta.java | 221 +++++-----------
.../mergerows/messages/messages_en_US.properties | 6 +-
.../mergerows/MergeRowsMetaInjectionTest.java | 41 ---
.../transforms/mergerows/MergeRowsMetaTest.java | 67 ++---
.../mergerows/src/test/resources/merge-rows.xml | 30 +++
.../org/apache/hop/ui/hopgui/HopCommandGui.java | 2 +
24 files changed, 1844 insertions(+), 285 deletions(-)
diff --git
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/mergerows.adoc
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/mergerows.adoc
index faa49a235d..f8d8b317e6 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/mergerows.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/mergerows.adoc
@@ -76,7 +76,24 @@ In the subsequent transform, you can use the flag field
generated by **Merge row
|Reference rows origin|Specify the transform that produces the reference rows.
It's a Stream with original rows (rows that you want to compare the new rows
to).
|Compare rows origin|Specify the transform that produces the compare rows.
It's a Stream with new rows
|Flag fieldname|Specify the name of the flag field on the output stream.
+|Difference fieldname|Specify the name of the field to contain the difference
in case the status is **changed**.
+The difference will be in the form of a "changes" array in JSON format.
|Keys to match|Specify fields containing the keys on which to match. Click
"Get key fields" to insert all of the fields from the reference rows
|Values to compare|Specify fields contaning the values to compare. Click "Get
value fields" to insert all of the fields from the compare rows.
Key fields do not need to be repeated here.
|===
+
+== Differences JSON example
+
+If you specify a field name for the differences in JSON, you will changes
appear when a change was detected:
+
+[source,json]
+----
+{
+ "changes" : [
+ { "field1" : { "from" : "aaa", "to" : "bbb" }},
+ { "field2" : { "from" : 111, "to" : 222 }}
+ ...
+ ]
+}
+----
diff --git a/integration-tests/mdi/0030-merge-rows-template.hpl
b/integration-tests/mdi/0030-merge-rows-template.hpl
new file mode 100644
index 0000000000..be58d08e7e
--- /dev/null
+++ b/integration-tests/mdi/0030-merge-rows-template.hpl
@@ -0,0 +1,289 @@
+<?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>
+ <name>0030-merge-rows-template</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description/>
+ <extended_description/>
+ <pipeline_version/>
+ <pipeline_type>Normal</pipeline_type>
+ <parameters>
+ </parameters>
+ <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>
+ <created_user>-</created_user>
+ <created_date>2025/09/02 11:26:20.192</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2025/09/02 11:26:20.192</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>source1</from>
+ <to>Merge rows</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>source2</from>
+ <to>Merge rows</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Merge rows</from>
+ <to>OUTPUT</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <transform>
+ <name>Merge rows</name>
+ <type>MergeRows</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <keys>
+ </keys>
+ <values>
+ </values>
+ <flag_field/>
+ <reference>source1</reference>
+ <compare>source2</compare>
+ <compare>
+ </compare>
+ <attributes/>
+ <GUI>
+ <xloc>288</xloc>
+ <yloc>112</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>source1</name>
+ <type>DataGrid</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <data>
+ <line>
+ <item>1</item>
+ <item>James</item>
+ <item>3.6</item>
+ </line>
+ <line>
+ <item>2</item>
+ <item>Marie</item>
+ <item>8.3</item>
+ </line>
+ <line>
+ <item>3</item>
+ <item>Michael</item>
+ <item>9.1</item>
+ </line>
+ <line>
+ <item>4</item>
+ <item>Patrcia</item>
+ <item>4.2</item>
+ </line>
+ <line>
+ <item>5</item>
+ <item>Robert</item>
+ <item>5.0</item>
+ </line>
+ <line>
+ <item>6</item>
+ <item>Linda</item>
+ <item>1.6</item>
+ </line>
+ <line>
+ <item>7</item>
+ <item>David</item>
+ <item>7.2</item>
+ </line>
+ <line>
+ <item>8</item>
+ <item>Elizabeth</item>
+ <item>6.9</item>
+ </line>
+ <line>
+ <item>9</item>
+ <item>Willian</item>
+ <item>4.2</item>
+ </line>
+ <line>
+ <item>10</item>
+ <item>Barbara</item>
+ <item>0.5</item>
+ </line>
+ </data>
+ <fields>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>id</name>
+ <type>Integer</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>name</name>
+ <type>String</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>score</name>
+ <format>#.#</format>
+ <type>Number</type>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>144</xloc>
+ <yloc>64</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>source2</name>
+ <type>DataGrid</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <data>
+ <line>
+ <item>1</item>
+ <item>James</item>
+ <item>3.6</item>
+ </line>
+ <line>
+ <item>3</item>
+ <item>Michael</item>
+ <item>9.1</item>
+ </line>
+ <line>
+ <item>4</item>
+ <item>Patricia</item>
+ <item>4.2</item>
+ </line>
+ <line>
+ <item>5</item>
+ <item>Robert</item>
+ <item>5.0</item>
+ </line>
+ <line>
+ <item>7</item>
+ <item>David</item>
+ <item>7.2</item>
+ </line>
+ <line>
+ <item>8</item>
+ <item>Elizabeth</item>
+ <item>6.9</item>
+ </line>
+ <line>
+ <item>9</item>
+ <item>William</item>
+ <item>4.2</item>
+ </line>
+ <line>
+ <item>10</item>
+ <item>Barbara</item>
+ <item>0.5</item>
+ </line>
+ <line>
+ <item>11</item>
+ <item>Richard</item>
+ <item>9.6</item>
+ </line>
+ <line>
+ <item>12</item>
+ <item>Susan</item>
+ <item>5.4</item>
+ </line>
+ </data>
+ <fields>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>id</name>
+ <type>Integer</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>name</name>
+ <type>String</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>score</name>
+ <format>#.#</format>
+ <type>Number</type>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>144</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>OUTPUT</name>
+ <type>Dummy</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ <GUI>
+ <xloc>432</xloc>
+ <yloc>112</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/mdi/0030-merge-rows.hpl
b/integration-tests/mdi/0030-merge-rows.hpl
new file mode 100644
index 0000000000..b14f42124c
--- /dev/null
+++ b/integration-tests/mdi/0030-merge-rows.hpl
@@ -0,0 +1,282 @@
+<?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>
+ <name>0030-merge-rows</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description/>
+ <extended_description/>
+ <pipeline_version/>
+ <pipeline_type>Normal</pipeline_type>
+ <parameters>
+ </parameters>
+ <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>
+ <created_user>-</created_user>
+ <created_date>2025/09/02 11:28:05.214</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2025/09/02 11:28:05.214</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>metadata</from>
+ <to>ETL metadata injection</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>keys</from>
+ <to>ETL metadata injection</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>values</from>
+ <to>ETL metadata injection</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>ETL metadata injection</from>
+ <to>OUTPUT</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <transform>
+ <name>ETL metadata injection</name>
+ <type>MetaInject</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <filename>${PROJECT_HOME}/0030-merge-rows-template.hpl</filename>
+ <run_configuration>local</run_configuration>
+ <source_transform>OUTPUT</source_transform>
+ <source_output_fields>
+ <source_output_field>
+ <source_output_field_name>id</source_output_field_name>
+ <source_output_field_type>Integer</source_output_field_type>
+ <source_output_field_length>-1</source_output_field_length>
+ <source_output_field_precision>-1</source_output_field_precision>
+ </source_output_field>
+ <source_output_field>
+ <source_output_field_name>name</source_output_field_name>
+ <source_output_field_type>String</source_output_field_type>
+ <source_output_field_length>-1</source_output_field_length>
+ <source_output_field_precision>-1</source_output_field_precision>
+ </source_output_field>
+ <source_output_field>
+ <source_output_field_name>score</source_output_field_name>
+ <source_output_field_type>Number</source_output_field_type>
+ <source_output_field_length>-1</source_output_field_length>
+ <source_output_field_precision>-1</source_output_field_precision>
+ </source_output_field>
+ <source_output_field>
+ <source_output_field_name>flagfield</source_output_field_name>
+ <source_output_field_type>String</source_output_field_type>
+ <source_output_field_length>-1</source_output_field_length>
+ <source_output_field_precision>-1</source_output_field_precision>
+ </source_output_field>
+ </source_output_fields>
+ <target_file/>
+ <create_parent_folder>Y</create_parent_folder>
+ <no_execution>N</no_execution>
+ <stream_source_transform/>
+ <stream_target_transform/>
+ <mappings>
+ <mapping>
+ <target_transform_name>Merge rows</target_transform_name>
+ <target_attribute_key>VALUE_FIELD</target_attribute_key>
+ <target_detail>Y</target_detail>
+ <source_transform>values</source_transform>
+ <source_field>value</source_field>
+ </mapping>
+ <mapping>
+ <target_transform_name>Merge rows</target_transform_name>
+ <target_attribute_key>KEY_FIELD</target_attribute_key>
+ <target_detail>Y</target_detail>
+ <source_transform>keys</source_transform>
+ <source_field>key</source_field>
+ </mapping>
+ <mapping>
+ <target_transform_name>Merge rows</target_transform_name>
+ <target_attribute_key>FLAG_FIELD</target_attribute_key>
+ <target_detail>N</target_detail>
+ <source_transform>metadata</source_transform>
+ <source_field>flagfield</source_field>
+ </mapping>
+ <mapping>
+ <target_transform_name>Merge rows</target_transform_name>
+ <target_attribute_key>COMPARE_TRANSFORM</target_attribute_key>
+ <target_detail>N</target_detail>
+ <source_transform>metadata</source_transform>
+ <source_field>compare</source_field>
+ </mapping>
+ <mapping>
+ <target_transform_name>Merge rows</target_transform_name>
+ <target_attribute_key>REFERENCE_TRANSFORM</target_attribute_key>
+ <target_detail>N</target_detail>
+ <source_transform>metadata</source_transform>
+ <source_field>reference</source_field>
+ </mapping>
+ </mappings>
+ <attributes/>
+ <GUI>
+ <xloc>304</xloc>
+ <yloc>64</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>OUTPUT</name>
+ <type>Dummy</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ <GUI>
+ <xloc>512</xloc>
+ <yloc>64</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>keys</name>
+ <type>DataGrid</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <data>
+ <line>
+ <item>id</item>
+ </line>
+ </data>
+ <fields>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>key</name>
+ <type>String</type>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>128</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>metadata</name>
+ <type>DataGrid</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <data>
+ <line>
+ <item>source1</item>
+ <item>source2</item>
+ <item>flagfield</item>
+ </line>
+ </data>
+ <fields>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>reference</name>
+ <type>String</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>compare</name>
+ <type>String</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>flagfield</name>
+ <type>String</type>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>128</xloc>
+ <yloc>64</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>values</name>
+ <type>DataGrid</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <data>
+ <line>
+ <item>name</item>
+ </line>
+ <line>
+ <item>score</item>
+ </line>
+ </data>
+ <fields>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>value</name>
+ <type>String</type>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>128</xloc>
+ <yloc>240</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/mdi/datasets/golden-merge-rows.csv
b/integration-tests/mdi/datasets/golden-merge-rows.csv
new file mode 100644
index 0000000000..e6b5b37c5e
--- /dev/null
+++ b/integration-tests/mdi/datasets/golden-merge-rows.csv
@@ -0,0 +1,13 @@
+id,name,score,flagfield
+1,James,3.6,identical
+2,Marie,8.3,deleted
+3,Michael,9.1,identical
+4,Patricia,4.2,changed
+5,Robert,5.0,identical
+6,Linda,1.6,deleted
+7,David,7.2,identical
+8,Elizabeth,6.9,identical
+9,William,4.2,changed
+10,Barbara,0.5,identical
+11,Richard,9.6,new
+12,Susan,5.4,new
diff --git a/integration-tests/mdi/main-0030-merge-rows.hwf
b/integration-tests/mdi/main-0030-merge-rows.hwf
new file mode 100644
index 0000000000..e02a2ae162
--- /dev/null
+++ b/integration-tests/mdi/main-0030-merge-rows.hwf
@@ -0,0 +1,80 @@
+<?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.
+
+-->
+<workflow>
+ <name>main-0030-merge-rows</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description/>
+ <extended_description/>
+ <workflow_version/>
+ <created_user>-</created_user>
+ <created_date>2025/09/02 11:24:45.914</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2025/09/02 11:24:45.914</modified_date>
+ <parameters>
+ </parameters>
+ <actions>
+ <action>
+ <name>Start</name>
+ <description/>
+ <type>SPECIAL</type>
+ <attributes/>
+ <DayOfMonth>1</DayOfMonth>
+ <doNotWaitOnFirstExecution>N</doNotWaitOnFirstExecution>
+ <hour>12</hour>
+ <intervalMinutes>60</intervalMinutes>
+ <intervalSeconds>0</intervalSeconds>
+ <minutes>0</minutes>
+ <repeat>N</repeat>
+ <schedulerType>0</schedulerType>
+ <weekDay>1</weekDay>
+ <parallel>N</parallel>
+ <xloc>80</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>Run Pipeline Unit Tests</name>
+ <description/>
+ <type>RunPipelineTests</type>
+ <attributes/>
+ <test_names>
+ <test_name>
+ <name>0030-merge-rows UNIT</name>
+ </test_name>
+ </test_names>
+ <parallel>N</parallel>
+ <xloc>256</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ </actions>
+ <hops>
+ <hop>
+ <from>Start</from>
+ <to>Run Pipeline Unit Tests</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>Y</unconditional>
+ </hop>
+ </hops>
+ <notepads>
+ </notepads>
+ <attributes/>
+</workflow>
diff --git a/integration-tests/mdi/metadata/dataset/golden-merge-rows.json
b/integration-tests/mdi/metadata/dataset/golden-merge-rows.json
new file mode 100644
index 0000000000..a0c11e533b
--- /dev/null
+++ b/integration-tests/mdi/metadata/dataset/golden-merge-rows.json
@@ -0,0 +1,40 @@
+{
+ "base_filename": "golden-merge-rows.csv",
+ "name": "golden-merge-rows",
+ "description": "",
+ "dataset_fields": [
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 5,
+ "field_precision": 0,
+ "field_name": "id",
+ "field_format": "####0;-####0"
+ },
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 2,
+ "field_precision": -1,
+ "field_name": "name",
+ "field_format": ""
+ },
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 1,
+ "field_precision": -1,
+ "field_name": "score",
+ "field_format": "####0.0#########;-####0.0#########"
+ },
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 2,
+ "field_precision": -1,
+ "field_name": "flagfield",
+ "field_format": ""
+ }
+ ],
+ "folder_name": ""
+}
\ No newline at end of file
diff --git a/integration-tests/mdi/metadata/unit-test/0030-merge-rows UNIT.json
b/integration-tests/mdi/metadata/unit-test/0030-merge-rows UNIT.json
new file mode 100644
index 0000000000..144146a4af
--- /dev/null
+++ b/integration-tests/mdi/metadata/unit-test/0030-merge-rows UNIT.json
@@ -0,0 +1,43 @@
+{
+ "database_replacements": [],
+ "autoOpening": true,
+ "description": "",
+ "persist_filename": "",
+ "test_type": "UNIT_TEST",
+ "variableValues": [],
+ "basePath": "",
+ "golden_data_sets": [
+ {
+ "field_mappings": [
+ {
+ "transform_field": "id",
+ "data_set_field": "id"
+ },
+ {
+ "transform_field": "name",
+ "data_set_field": "name"
+ },
+ {
+ "transform_field": "score",
+ "data_set_field": "score"
+ },
+ {
+ "transform_field": "flagfield",
+ "data_set_field": "flagfield"
+ }
+ ],
+ "field_order": [
+ "id",
+ "name",
+ "score",
+ "flagfield"
+ ],
+ "data_set_name": "golden-merge-rows",
+ "transform_name": "OUTPUT"
+ }
+ ],
+ "input_data_sets": [],
+ "name": "0030-merge-rows UNIT",
+ "trans_test_tweaks": [],
+ "pipeline_filename": "./0030-merge-rows.hpl"
+}
\ No newline at end of file
diff --git a/integration-tests/transforms/0077-merge-rows-diff.hpl
b/integration-tests/transforms/0077-merge-rows-diff.hpl
new file mode 100644
index 0000000000..834df0f156
--- /dev/null
+++ b/integration-tests/transforms/0077-merge-rows-diff.hpl
@@ -0,0 +1,291 @@
+<?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>
+ <name>0077-merge-rows-diff</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description/>
+ <extended_description/>
+ <pipeline_version/>
+ <pipeline_type>Normal</pipeline_type>
+ <parameters>
+ </parameters>
+ <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>
+ <created_user>-</created_user>
+ <created_date>2025/09/02 10:00:26.808</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2025/09/02 10:00:26.808</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>source1</from>
+ <to>Merge rows (diff)</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>source2</from>
+ <to>Merge rows (diff)</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Merge rows (diff)</from>
+ <to>validate</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <transform>
+ <name>Merge rows (diff)</name>
+ <type>MergeRows</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <compare>source2</compare>
+ <diff-field>difference</diff-field>
+ <flag_field>flagfield</flag_field>
+ <keys>
+ <key>id</key>
+ </keys>
+ <reference>source1</reference>
+ <values>
+ <value>name</value>
+ <value>score</value>
+ </values>
+ <attributes/>
+ <GUI>
+ <xloc>224</xloc>
+ <yloc>80</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>source1</name>
+ <type>DataGrid</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <data>
+ <line>
+ <item>1</item>
+ <item>James</item>
+ <item>3.6</item>
+ </line>
+ <line>
+ <item>2</item>
+ <item>Marie</item>
+ <item>8.3</item>
+ </line>
+ <line>
+ <item>3</item>
+ <item>Michael</item>
+ <item>9.1</item>
+ </line>
+ <line>
+ <item>4</item>
+ <item>Patrcia</item>
+ <item>4.2</item>
+ </line>
+ <line>
+ <item>5</item>
+ <item>Robert</item>
+ <item>5.0</item>
+ </line>
+ <line>
+ <item>6</item>
+ <item>Linda</item>
+ <item>1.6</item>
+ </line>
+ <line>
+ <item>7</item>
+ <item>David</item>
+ <item>7.2</item>
+ </line>
+ <line>
+ <item>8</item>
+ <item>Elizabeth</item>
+ <item>6.9</item>
+ </line>
+ <line>
+ <item>9</item>
+ <item>Willian</item>
+ <item>4.2</item>
+ </line>
+ <line>
+ <item>10</item>
+ <item>Barbara</item>
+ <item>0.5</item>
+ </line>
+ </data>
+ <fields>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>id</name>
+ <type>Integer</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>name</name>
+ <type>String</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>score</name>
+ <format>#.#</format>
+ <type>Number</type>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>80</xloc>
+ <yloc>32</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>source2</name>
+ <type>DataGrid</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <data>
+ <line>
+ <item>1</item>
+ <item>James</item>
+ <item>3.6</item>
+ </line>
+ <line>
+ <item>3</item>
+ <item>Michael</item>
+ <item>9.1</item>
+ </line>
+ <line>
+ <item>4</item>
+ <item>Patricia</item>
+ <item>4.2</item>
+ </line>
+ <line>
+ <item>5</item>
+ <item>Robert</item>
+ <item>5.0</item>
+ </line>
+ <line>
+ <item>7</item>
+ <item>David</item>
+ <item>7.2</item>
+ </line>
+ <line>
+ <item>8</item>
+ <item>Elizabeth</item>
+ <item>6.9</item>
+ </line>
+ <line>
+ <item>9</item>
+ <item>William</item>
+ <item>4.3</item>
+ </line>
+ <line>
+ <item>10</item>
+ <item>Barbara</item>
+ <item>0.7</item>
+ </line>
+ <line>
+ <item>11</item>
+ <item>Richard</item>
+ <item>9.6</item>
+ </line>
+ <line>
+ <item>12</item>
+ <item>Susan</item>
+ <item>5.4</item>
+ </line>
+ </data>
+ <fields>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>id</name>
+ <type>Integer</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>name</name>
+ <type>String</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>score</name>
+ <format>#.#</format>
+ <type>Number</type>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>80</xloc>
+ <yloc>128</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>validate</name>
+ <type>Dummy</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ <GUI>
+ <xloc>400</xloc>
+ <yloc>80</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/transforms/0077-merge-rows.hpl
b/integration-tests/transforms/0077-merge-rows.hpl
new file mode 100644
index 0000000000..9c62bd7c66
--- /dev/null
+++ b/integration-tests/transforms/0077-merge-rows.hpl
@@ -0,0 +1,292 @@
+<?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>
+ <name>0077-merge-rows</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description/>
+ <extended_description/>
+ <pipeline_version/>
+ <pipeline_type>Normal</pipeline_type>
+ <parameters>
+ </parameters>
+ <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>
+ <created_user>-</created_user>
+ <created_date>2025/09/02 10:00:26.808</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2025/09/02 10:00:26.808</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>source1</from>
+ <to>Merge rows (diff)</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>source2</from>
+ <to>Merge rows (diff)</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Merge rows (diff)</from>
+ <to>validate</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <transform>
+ <name>Merge rows (diff)</name>
+ <type>MergeRows</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <keys>
+ <key>id</key>
+ </keys>
+ <values>
+ <value>name</value>
+ <value>score</value>
+ </values>
+ <flag_field>flagfield</flag_field>
+ <reference>source1</reference>
+ <compare>source2</compare>
+ <compare>
+ </compare>
+ <attributes/>
+ <GUI>
+ <xloc>240</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>source1</name>
+ <type>DataGrid</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <data>
+ <line>
+ <item>1</item>
+ <item>James</item>
+ <item>3.6</item>
+ </line>
+ <line>
+ <item>2</item>
+ <item>Marie</item>
+ <item>8.3</item>
+ </line>
+ <line>
+ <item>3</item>
+ <item>Michael</item>
+ <item>9.1</item>
+ </line>
+ <line>
+ <item>4</item>
+ <item>Patrcia</item>
+ <item>4.2</item>
+ </line>
+ <line>
+ <item>5</item>
+ <item>Robert</item>
+ <item>5.0</item>
+ </line>
+ <line>
+ <item>6</item>
+ <item>Linda</item>
+ <item>1.6</item>
+ </line>
+ <line>
+ <item>7</item>
+ <item>David</item>
+ <item>7.2</item>
+ </line>
+ <line>
+ <item>8</item>
+ <item>Elizabeth</item>
+ <item>6.9</item>
+ </line>
+ <line>
+ <item>9</item>
+ <item>Willian</item>
+ <item>4.2</item>
+ </line>
+ <line>
+ <item>10</item>
+ <item>Barbara</item>
+ <item>0.5</item>
+ </line>
+ </data>
+ <fields>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>id</name>
+ <type>Integer</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>name</name>
+ <type>String</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>score</name>
+ <format>#.#</format>
+ <type>Number</type>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>96</xloc>
+ <yloc>48</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>source2</name>
+ <type>DataGrid</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <data>
+ <line>
+ <item>1</item>
+ <item>James</item>
+ <item>3.6</item>
+ </line>
+ <line>
+ <item>3</item>
+ <item>Michael</item>
+ <item>9.1</item>
+ </line>
+ <line>
+ <item>4</item>
+ <item>Patricia</item>
+ <item>4.2</item>
+ </line>
+ <line>
+ <item>5</item>
+ <item>Robert</item>
+ <item>5.0</item>
+ </line>
+ <line>
+ <item>7</item>
+ <item>David</item>
+ <item>7.2</item>
+ </line>
+ <line>
+ <item>8</item>
+ <item>Elizabeth</item>
+ <item>6.9</item>
+ </line>
+ <line>
+ <item>9</item>
+ <item>William</item>
+ <item>4.2</item>
+ </line>
+ <line>
+ <item>10</item>
+ <item>Barbara</item>
+ <item>0.5</item>
+ </line>
+ <line>
+ <item>11</item>
+ <item>Richard</item>
+ <item>9.6</item>
+ </line>
+ <line>
+ <item>12</item>
+ <item>Susan</item>
+ <item>5.4</item>
+ </line>
+ </data>
+ <fields>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>id</name>
+ <type>Integer</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>name</name>
+ <type>String</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>score</name>
+ <format>#.#</format>
+ <type>Number</type>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>96</xloc>
+ <yloc>144</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>validate</name>
+ <type>Dummy</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ <GUI>
+ <xloc>416</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/transforms/datasets/golden-merge-rows-diff.csv
b/integration-tests/transforms/datasets/golden-merge-rows-diff.csv
new file mode 100644
index 0000000000..b0f1611774
--- /dev/null
+++ b/integration-tests/transforms/datasets/golden-merge-rows-diff.csv
@@ -0,0 +1,13 @@
+id,name,score,flagfield,difference
+1,James,3.6,"{""changes"":[]}",identical
+2,Marie,8.3,"{""changes"":[]}",deleted
+3,Michael,9.1,"{""changes"":[]}",identical
+4,Patricia,4.2,"{""changes"":[{""name"":{""from"":""Patrcia"",""to"":""Patricia""}}]}",changed
+5,Robert,5,"{""changes"":[]}",identical
+6,Linda,1.6,"{""changes"":[]}",deleted
+7,David,7.2,"{""changes"":[]}",identical
+8,Elizabeth,6.9,"{""changes"":[]}",identical
+9,William,4.3,"{""changes"":[{""name"":{""from"":""Willian"",""to"":""William""}},{""score"":{""from"":4.2,""to"":4.3}}]}",changed
+10,Barbara,0.7,"{""changes"":[{""score"":{""from"":0.5,""to"":0.7}}]}",changed
+11,Richard,9.6,"{""changes"":[]}",new
+12,Susan,5.4,"{""changes"":[]}",new
diff --git a/integration-tests/transforms/datasets/golden-merge-rows.csv
b/integration-tests/transforms/datasets/golden-merge-rows.csv
new file mode 100644
index 0000000000..b386349627
--- /dev/null
+++ b/integration-tests/transforms/datasets/golden-merge-rows.csv
@@ -0,0 +1,13 @@
+id,name,score,flagfield
+1,James,3.6,identical
+2,Marie,8.3,deleted
+3,Michael,9.1,identical
+4,Patricia,4.2,changed
+5,Robert,5,identical
+6,Linda,1.6,deleted
+7,David,7.2,identical
+8,Elizabeth,6.9,identical
+9,William,4.2,changed
+10,Barbara,0.5,identical
+11,Richard,9.6,new
+12,Susan,5.4,new
diff --git a/integration-tests/transforms/main-0077-merge-rows.hwf
b/integration-tests/transforms/main-0077-merge-rows.hwf
new file mode 100644
index 0000000000..3a2878fbad
--- /dev/null
+++ b/integration-tests/transforms/main-0077-merge-rows.hwf
@@ -0,0 +1,83 @@
+<?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.
+
+-->
+<workflow>
+ <name>main-0077-merge-rows</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description/>
+ <extended_description/>
+ <workflow_version/>
+ <created_user>-</created_user>
+ <created_date>2025/09/02 10:00:02.532</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2025/09/02 10:00:02.532</modified_date>
+ <parameters>
+ </parameters>
+ <actions>
+ <action>
+ <name>Start</name>
+ <description/>
+ <type>SPECIAL</type>
+ <attributes/>
+ <DayOfMonth>1</DayOfMonth>
+ <doNotWaitOnFirstExecution>N</doNotWaitOnFirstExecution>
+ <hour>12</hour>
+ <intervalMinutes>60</intervalMinutes>
+ <intervalSeconds>0</intervalSeconds>
+ <minutes>0</minutes>
+ <repeat>N</repeat>
+ <schedulerType>0</schedulerType>
+ <weekDay>1</weekDay>
+ <parallel>N</parallel>
+ <xloc>80</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>Run Pipeline Unit Tests</name>
+ <description/>
+ <type>RunPipelineTests</type>
+ <attributes/>
+ <test_names>
+ <test_name>
+ <name>0077-merge-rows UNIT</name>
+ </test_name>
+ <test_name>
+ <name>0077-merge-rows-diff UNIT</name>
+ </test_name>
+ </test_names>
+ <parallel>N</parallel>
+ <xloc>272</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ </actions>
+ <hops>
+ <hop>
+ <from>Start</from>
+ <to>Run Pipeline Unit Tests</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>Y</unconditional>
+ </hop>
+ </hops>
+ <notepads>
+ </notepads>
+ <attributes/>
+</workflow>
diff --git
a/integration-tests/transforms/metadata/dataset/golden-merge-rows-diff.json
b/integration-tests/transforms/metadata/dataset/golden-merge-rows-diff.json
new file mode 100644
index 0000000000..ca0feaf854
--- /dev/null
+++ b/integration-tests/transforms/metadata/dataset/golden-merge-rows-diff.json
@@ -0,0 +1,48 @@
+{
+ "base_filename": "golden-merge-rows-diff.csv",
+ "name": "golden-merge-rows-diff",
+ "description": "",
+ "dataset_fields": [
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 5,
+ "field_precision": 0,
+ "field_name": "id",
+ "field_format": "####0;-####0"
+ },
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 2,
+ "field_precision": -1,
+ "field_name": "name",
+ "field_format": ""
+ },
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 1,
+ "field_precision": -1,
+ "field_name": "score",
+ "field_format": "#.#"
+ },
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 2,
+ "field_precision": -1,
+ "field_name": "flagfield",
+ "field_format": ""
+ },
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 2,
+ "field_precision": -1,
+ "field_name": "difference",
+ "field_format": ""
+ }
+ ],
+ "folder_name": ""
+}
\ No newline at end of file
diff --git
a/integration-tests/transforms/metadata/dataset/golden-merge-rows.json
b/integration-tests/transforms/metadata/dataset/golden-merge-rows.json
new file mode 100644
index 0000000000..9a01d44f42
--- /dev/null
+++ b/integration-tests/transforms/metadata/dataset/golden-merge-rows.json
@@ -0,0 +1,40 @@
+{
+ "base_filename": "golden-merge-rows.csv",
+ "name": "golden-merge-rows",
+ "description": "",
+ "dataset_fields": [
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 5,
+ "field_precision": 0,
+ "field_name": "id",
+ "field_format": "####0;-####0"
+ },
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 2,
+ "field_precision": -1,
+ "field_name": "name",
+ "field_format": ""
+ },
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 1,
+ "field_precision": -1,
+ "field_name": "score",
+ "field_format": "#.#"
+ },
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 2,
+ "field_precision": -1,
+ "field_name": "flagfield",
+ "field_format": ""
+ }
+ ],
+ "folder_name": ""
+}
\ No newline at end of file
diff --git a/integration-tests/transforms/metadata/unit-test/0077-merge-rows
UNIT.json b/integration-tests/transforms/metadata/unit-test/0077-merge-rows
UNIT.json
new file mode 100644
index 0000000000..59f66f05b4
--- /dev/null
+++ b/integration-tests/transforms/metadata/unit-test/0077-merge-rows UNIT.json
@@ -0,0 +1,40 @@
+{
+ "database_replacements": [],
+ "autoOpening": true,
+ "description": "",
+ "persist_filename": "",
+ "test_type": "UNIT_TEST",
+ "variableValues": [],
+ "basePath": "",
+ "golden_data_sets": [
+ {
+ "field_mappings": [
+ {
+ "transform_field": "id",
+ "data_set_field": "id"
+ },
+ {
+ "transform_field": "name",
+ "data_set_field": "name"
+ },
+ {
+ "transform_field": "score",
+ "data_set_field": "score"
+ },
+ {
+ "transform_field": "flagfield",
+ "data_set_field": "flagfield"
+ }
+ ],
+ "field_order": [
+ "id"
+ ],
+ "data_set_name": "golden-merge-rows",
+ "transform_name": "validate"
+ }
+ ],
+ "input_data_sets": [],
+ "name": "0077-merge-rows UNIT",
+ "trans_test_tweaks": [],
+ "pipeline_filename": "./0077-merge-rows.hpl"
+}
\ No newline at end of file
diff --git
a/integration-tests/transforms/metadata/unit-test/0077-merge-rows-diff
UNIT.json
b/integration-tests/transforms/metadata/unit-test/0077-merge-rows-diff UNIT.json
new file mode 100644
index 0000000000..d818cf7416
--- /dev/null
+++ b/integration-tests/transforms/metadata/unit-test/0077-merge-rows-diff
UNIT.json
@@ -0,0 +1,48 @@
+{
+ "database_replacements": [],
+ "autoOpening": true,
+ "description": "",
+ "persist_filename": "",
+ "test_type": "UNIT_TEST",
+ "variableValues": [],
+ "basePath": "",
+ "golden_data_sets": [
+ {
+ "field_mappings": [
+ {
+ "transform_field": "id",
+ "data_set_field": "id"
+ },
+ {
+ "transform_field": "name",
+ "data_set_field": "name"
+ },
+ {
+ "transform_field": "score",
+ "data_set_field": "score"
+ },
+ {
+ "transform_field": "flagfield",
+ "data_set_field": "flagfield"
+ },
+ {
+ "transform_field": "difference",
+ "data_set_field": "difference"
+ }
+ ],
+ "field_order": [
+ "id",
+ "name",
+ "score",
+ "flagfield",
+ "difference"
+ ],
+ "data_set_name": "golden-merge-rows-diff",
+ "transform_name": "validate"
+ }
+ ],
+ "input_data_sets": [],
+ "name": "0077-merge-rows-diff UNIT",
+ "trans_test_tweaks": [],
+ "pipeline_filename": "./0077-merge-rows-diff.hpl"
+}
\ No newline at end of file
diff --git
a/plugins/transforms/mergerows/src/main/java/org/apache/hop/pipeline/transforms/mergerows/MergeRows.java
b/plugins/transforms/mergerows/src/main/java/org/apache/hop/pipeline/transforms/mergerows/MergeRows.java
index 2982a98e12..0d2a10aa3b 100644
---
a/plugins/transforms/mergerows/src/main/java/org/apache/hop/pipeline/transforms/mergerows/MergeRows.java
+++
b/plugins/transforms/mergerows/src/main/java/org/apache/hop/pipeline/transforms/mergerows/MergeRows.java
@@ -19,10 +19,12 @@ package org.apache.hop.pipeline.transforms.mergerows;
import java.util.Arrays;
import java.util.List;
+import org.apache.commons.lang.StringUtils;
import org.apache.hop.core.exception.HopException;
import org.apache.hop.core.exception.HopRowException;
import org.apache.hop.core.exception.HopTransformException;
import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.IValueMeta;
import org.apache.hop.core.row.RowDataUtil;
import org.apache.hop.core.row.RowMeta;
import org.apache.hop.i18n.BaseMessages;
@@ -32,6 +34,8 @@ import org.apache.hop.pipeline.transform.BaseTransform;
import org.apache.hop.pipeline.transform.ITransform;
import org.apache.hop.pipeline.transform.TransformMeta;
import org.apache.hop.pipeline.transform.stream.IStream;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
/**
* Merge rows from 2 sorted streams to detect changes. Use this as feed for a
dimension in case you
@@ -81,15 +85,15 @@ public class MergeRows extends BaseTransform<MergeRowsMeta,
MergeRowsData> {
if (data.one != null) {
// Find the key indexes:
- data.keyNrs = new int[meta.getKeyFields().length];
+ data.keyNrs = new int[meta.getKeyFields().size()];
for (int i = 0; i < data.keyNrs.length; i++) {
- data.keyNrs[i] =
data.oneRowSet.getRowMeta().indexOfValue(meta.getKeyFields()[i]);
+ data.keyNrs[i] =
data.oneRowSet.getRowMeta().indexOfValue(meta.getKeyFields().get(i));
if (data.keyNrs[i] < 0) {
String message =
BaseMessages.getString(
PKG,
"MergeRows.Exception.UnableToFindFieldInReferenceStream",
- meta.getKeyFields()[i]);
+ meta.getKeyFields().get(i));
logError(message);
throw new HopTransformException(message);
}
@@ -97,15 +101,15 @@ public class MergeRows extends
BaseTransform<MergeRowsMeta, MergeRowsData> {
}
if (data.two != null) {
- data.valueNrs = new int[meta.getValueFields().length];
+ data.valueNrs = new int[meta.getValueFields().size()];
for (int i = 0; i < data.valueNrs.length; i++) {
- data.valueNrs[i] =
data.twoRowSet.getRowMeta().indexOfValue(meta.getValueFields()[i]);
+ data.valueNrs[i] =
data.twoRowSet.getRowMeta().indexOfValue(meta.getValueFields().get(i));
if (data.valueNrs[i] < 0) {
String message =
BaseMessages.getString(
PKG,
"MergeRows.Exception.UnableToFindFieldInReferenceStream",
- meta.getValueFields()[i]);
+ meta.getValueFields().get(i));
logError(message);
throw new HopTransformException(message);
}
@@ -146,20 +150,19 @@ public class MergeRows extends
BaseTransform<MergeRowsMeta, MergeRowsData> {
}
Object[] outputRow;
- int outputIndex;
- String flagField = null;
+ String flagField;
+ String differenceJson = "{\"changes\":[]}";
+ boolean getDifference = StringUtils.isNotEmpty(meta.getDiffJsonField());
if (data.one == null && data.two != null) { // Record 2 is flagged as new!
outputRow = data.two;
- outputIndex = data.twoRowSet.getRowMeta().size();
flagField = VALUE_NEW;
// Also get a next row from compare rowset...
data.two = getRowFrom(data.twoRowSet);
} else if (data.one != null && data.two == null) { // Record 1 is flagged
as deleted!
outputRow = data.one;
- outputIndex = data.oneRowSet.getRowMeta().size();
flagField = VALUE_DELETED;
// Also get a next row from reference rowset...
@@ -169,16 +172,40 @@ public class MergeRows extends
BaseTransform<MergeRowsMeta, MergeRowsData> {
int compare = data.oneRowSet.getRowMeta().compare(data.one, data.two,
data.keyNrs);
if (compare == 0) { // The Key matches, we CAN compare the two rows...
- int compareValues = data.oneRowSet.getRowMeta().compare(data.one,
data.two, data.valueNrs);
+ int compareValues = 0;
+ if (getDifference) {
+ JSONObject j = new JSONObject();
+ JSONArray jChanges = new JSONArray();
+ j.put("changes", jChanges);
+ for (int valueNr : data.valueNrs) {
+ IValueMeta valueMeta =
data.oneRowSet.getRowMeta().getValueMeta(valueNr);
+ Object refData = data.one[valueNr];
+ Object cmpData = data.two[valueNr];
+ int compareValue = valueMeta.compare(refData, cmpData);
+ if (compareValue != 0) {
+ JSONObject jChange = new JSONObject();
+ jChanges.add(jChange);
+ JSONObject jField = new JSONObject();
+ jChange.put(valueMeta.getName(), jField);
+ jField.put("from", refData);
+ jField.put("to", cmpData);
+ compareValues = compareValue;
+ }
+ if (compareValues != 0) {
+ differenceJson = j.toJSONString();
+ }
+ }
+ } else {
+ compareValues = data.oneRowSet.getRowMeta().compare(data.one,
data.two, data.valueNrs);
+ }
+
if (compareValues == 0) {
outputRow = data.two; // documented behavior: use the comparison
stream
- outputIndex = data.twoRowSet.getRowMeta().size();
flagField = VALUE_IDENTICAL;
} else {
// Return the compare (most recent) row
//
outputRow = data.two;
- outputIndex = data.twoRowSet.getRowMeta().size();
flagField = VALUE_CHANGED;
}
@@ -189,13 +216,11 @@ public class MergeRows extends
BaseTransform<MergeRowsMeta, MergeRowsData> {
if (compare < 0) { // one < two
outputRow = data.one;
- outputIndex = data.oneRowSet.getRowMeta().size();
flagField = VALUE_DELETED;
data.one = getRowFrom(data.oneRowSet);
} else {
outputRow = data.two;
- outputIndex = data.twoRowSet.getRowMeta().size();
flagField = VALUE_NEW;
data.two = getRowFrom(data.twoRowSet);
@@ -203,8 +228,16 @@ public class MergeRows extends
BaseTransform<MergeRowsMeta, MergeRowsData> {
}
}
+ // Optionally add the difference JSON field
+ //
+ outputRow = RowDataUtil.resizeArray(outputRow, data.outputRowMeta.size());
+ if (getDifference && differenceJson != null) {
+ outputRow[data.outputRowMeta.size() - 2] = differenceJson;
+ }
+ outputRow[data.outputRowMeta.size() - 1] = flagField;
+
// send the row to the next transforms...
- putRow(data.outputRowMeta, RowDataUtil.addValueData(outputRow,
outputIndex, flagField));
+ putRow(data.outputRowMeta, outputRow);
if (checkFeedback(getLinesRead()) && isBasic()) {
logBasic(BaseMessages.getString(PKG, "MergeRows.LineNumber") +
getLinesRead());
diff --git
a/plugins/transforms/mergerows/src/main/java/org/apache/hop/pipeline/transforms/mergerows/MergeRowsDialog.java
b/plugins/transforms/mergerows/src/main/java/org/apache/hop/pipeline/transforms/mergerows/MergeRowsDialog.java
index d9134b9904..bcd41348ec 100644
---
a/plugins/transforms/mergerows/src/main/java/org/apache/hop/pipeline/transforms/mergerows/MergeRowsDialog.java
+++
b/plugins/transforms/mergerows/src/main/java/org/apache/hop/pipeline/transforms/mergerows/MergeRowsDialog.java
@@ -57,6 +57,7 @@ public class MergeRowsDialog extends BaseTransformDialog {
private CCombo wCompare;
private Text wFlagField;
+ private Text wDiffField;
private TableView wKeys;
@@ -200,15 +201,15 @@ public class MergeRowsDialog extends BaseTransformDialog {
fdCompare.right = new FormAttachment(100, 0);
wCompare.setLayoutData(fdCompare);
- // TransformName line
- Label wlFlagfield = new Label(shell, SWT.RIGHT);
- wlFlagfield.setText(BaseMessages.getString(PKG,
"MergeRowsDialog.FlagField.Label"));
- PropsUi.setLook(wlFlagfield);
+ // The flag field line
+ Label wlFlagField = new Label(shell, SWT.RIGHT);
+ wlFlagField.setText(BaseMessages.getString(PKG,
"MergeRowsDialog.FlagField.Label"));
+ PropsUi.setLook(wlFlagField);
FormData fdlFlagfield = new FormData();
fdlFlagfield.left = new FormAttachment(0, 0);
fdlFlagfield.right = new FormAttachment(middle, -margin);
fdlFlagfield.top = new FormAttachment(wCompare, margin);
- wlFlagfield.setLayoutData(fdlFlagfield);
+ wlFlagField.setLayoutData(fdlFlagfield);
wFlagField = new Text(shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
PropsUi.setLook(wFlagField);
wFlagField.addModifyListener(lsMod);
@@ -218,16 +219,34 @@ public class MergeRowsDialog extends BaseTransformDialog {
fdFlagfield.right = new FormAttachment(100, 0);
wFlagField.setLayoutData(fdFlagfield);
+ // The flag field line
+ Label wlDiffField = new Label(shell, SWT.RIGHT);
+ wlDiffField.setText(BaseMessages.getString(PKG,
"MergeRowsDialog.DiffField.Label"));
+ PropsUi.setLook(wlDiffField);
+ FormData fdlDifffield = new FormData();
+ fdlDifffield.left = new FormAttachment(0, 0);
+ fdlDifffield.right = new FormAttachment(middle, -margin);
+ fdlDifffield.top = new FormAttachment(wFlagField, margin);
+ wlDiffField.setLayoutData(fdlDifffield);
+ wDiffField = new Text(shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
+ PropsUi.setLook(wDiffField);
+ wDiffField.addModifyListener(lsMod);
+ FormData fdDifffield = new FormData();
+ fdDifffield.top = new FormAttachment(wFlagField, margin);
+ fdDifffield.left = new FormAttachment(middle, 0);
+ fdDifffield.right = new FormAttachment(100, 0);
+ wDiffField.setLayoutData(fdDifffield);
+
// THE KEYS TO MATCH...
Label wlKeys = new Label(shell, SWT.NONE);
wlKeys.setText(BaseMessages.getString(PKG, "MergeRowsDialog.Keys.Label"));
PropsUi.setLook(wlKeys);
FormData fdlKeys = new FormData();
fdlKeys.left = new FormAttachment(0, 0);
- fdlKeys.top = new FormAttachment(wFlagField, margin);
+ fdlKeys.top = new FormAttachment(wDiffField, margin);
wlKeys.setLayoutData(fdlKeys);
- int nrKeyRows = (input.getKeyFields() != null ?
input.getKeyFields().length : 1);
+ int nrKeyRows = (input.getKeyFields() != null ?
input.getKeyFields().size() : 1);
ColumnInfo[] ciKeys =
new ColumnInfo[] {
@@ -260,10 +279,10 @@ public class MergeRowsDialog extends BaseTransformDialog {
PropsUi.setLook(wlValues);
FormData fdlValues = new FormData();
fdlValues.left = new FormAttachment(50, 0);
- fdlValues.top = new FormAttachment(wFlagField, margin);
+ fdlValues.top = new FormAttachment(wDiffField, margin);
wlValues.setLayoutData(fdlValues);
- int nrValueRows = (input.getValueFields() != null ?
input.getValueFields().length : 1);
+ int nrValueRows = (input.getValueFields() != null ?
input.getValueFields().size() : 1);
ColumnInfo[] ciValues =
new ColumnInfo[] {
@@ -304,21 +323,16 @@ public class MergeRowsDialog extends BaseTransformDialog {
wReference.setText(Const.NVL(infoStreams.get(0).getTransformName(), ""));
wCompare.setText(Const.NVL(infoStreams.get(1).getTransformName(), ""));
- if (input.getFlagField() != null) {
- wFlagField.setText(input.getFlagField());
- }
+ wFlagField.setText(Const.NVL(input.getFlagField(), ""));
+ wDiffField.setText(Const.NVL(input.getDiffJsonField(), ""));
- for (int i = 0; i < input.getKeyFields().length; i++) {
+ for (int i = 0; i < input.getKeyFields().size(); i++) {
TableItem item = wKeys.table.getItem(i);
- if (input.getKeyFields()[i] != null) {
- item.setText(1, input.getKeyFields()[i]);
- }
+ item.setText(1, Const.NVL(input.getKeyFields().get(i), ""));
}
- for (int i = 0; i < input.getValueFields().length; i++) {
+ for (int i = 0; i < input.getValueFields().size(); i++) {
TableItem item = wValues.table.getItem(i);
- if (input.getValueFields()[i] != null) {
- item.setText(1, input.getValueFields()[i]);
- }
+ item.setText(1, Const.NVL(input.getValueFields().get(i), ""));
}
wTransformName.selectAll();
@@ -336,24 +350,27 @@ public class MergeRowsDialog extends BaseTransformDialog {
return;
}
+ input.setReferenceTransform(wReference.getText());
+ input.setCompareTransform(wCompare.getText());
List<IStream> infoStreams = input.getTransformIOMeta().getInfoStreams();
infoStreams.get(0).setTransformMeta(pipelineMeta.findTransform(wReference.getText()));
infoStreams.get(1).setTransformMeta(pipelineMeta.findTransform(wCompare.getText()));
input.setFlagField(wFlagField.getText());
+ input.setDiffJsonField(wDiffField.getText());
int nrKeys = wKeys.nrNonEmpty();
int nrValues = wValues.nrNonEmpty();
- input.allocate(nrKeys, nrValues);
-
+ input.getKeyFields().clear();
for (int i = 0; i < nrKeys; i++) {
TableItem item = wKeys.getNonEmpty(i);
- input.getKeyFields()[i] = item.getText(1);
+ input.getKeyFields().add(item.getText(1));
}
+ input.getValueFields().clear();
for (int i = 0; i < nrValues; i++) {
TableItem item = wValues.getNonEmpty(i);
- input.getValueFields()[i] = item.getText(1);
+ input.getValueFields().add(item.getText(1));
}
transformName = wTransformName.getText(); // return value
diff --git
a/plugins/transforms/mergerows/src/main/java/org/apache/hop/pipeline/transforms/mergerows/MergeRowsMeta.java
b/plugins/transforms/mergerows/src/main/java/org/apache/hop/pipeline/transforms/mergerows/MergeRowsMeta.java
index d59bbb7962..c784972431 100644
---
a/plugins/transforms/mergerows/src/main/java/org/apache/hop/pipeline/transforms/mergerows/MergeRowsMeta.java
+++
b/plugins/transforms/mergerows/src/main/java/org/apache/hop/pipeline/transforms/mergerows/MergeRowsMeta.java
@@ -17,23 +17,23 @@
package org.apache.hop.pipeline.transforms.mergerows;
+import java.util.ArrayList;
import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.commons.lang.StringUtils;
import org.apache.hop.core.CheckResult;
-import org.apache.hop.core.Const;
import org.apache.hop.core.ICheckResult;
import org.apache.hop.core.annotations.Transform;
import org.apache.hop.core.exception.HopRowException;
import org.apache.hop.core.exception.HopTransformException;
-import org.apache.hop.core.exception.HopXmlException;
-import org.apache.hop.core.injection.Injection;
-import org.apache.hop.core.injection.InjectionSupported;
import org.apache.hop.core.row.IRowMeta;
import org.apache.hop.core.row.IValueMeta;
import org.apache.hop.core.row.value.ValueMetaString;
import org.apache.hop.core.util.Utils;
import org.apache.hop.core.variables.IVariables;
-import org.apache.hop.core.xml.XmlHandler;
import org.apache.hop.i18n.BaseMessages;
+import org.apache.hop.metadata.api.HopMetadataProperty;
import org.apache.hop.metadata.api.IHopMetadataProvider;
import org.apache.hop.pipeline.PipelineMeta;
import org.apache.hop.pipeline.PipelineMeta.PipelineType;
@@ -45,9 +45,9 @@ import org.apache.hop.pipeline.transform.stream.IStream;
import org.apache.hop.pipeline.transform.stream.IStream.StreamType;
import org.apache.hop.pipeline.transform.stream.Stream;
import org.apache.hop.pipeline.transform.stream.StreamIcon;
-import org.w3c.dom.Node;
-@InjectionSupported(localizationPrefix = "MergeRows.Injection.")
+@Getter
+@Setter
@Transform(
id = "MergeRows",
image = "mergerows.svg",
@@ -58,152 +58,71 @@ import org.w3c.dom.Node;
documentationUrl = "/pipeline/transforms/mergerows.html")
public class MergeRowsMeta extends BaseTransformMeta<MergeRows, MergeRowsData>
{
private static final Class<?> PKG = MergeRowsMeta.class;
- public static final String CONST_VALUE = "value";
- @Injection(name = "FLAG_FIELD")
+ @HopMetadataProperty(
+ key = "flag_field",
+ injectionKey = "FLAG_FIELD",
+ injectionKeyDescription = "MergeRows.Injection.FLAG_FIELD")
private String flagField;
- @Injection(name = "KEY_FIELDS")
- private String[] keyFields;
-
- @Injection(name = "VALUE_FIELDS")
- private String[] valueFields;
-
- /**
- * @return Returns the keyFields.
- */
- public String[] getKeyFields() {
- return keyFields;
- }
-
- /**
- * @param keyFields The keyFields to set.
- */
- public void setKeyFields(String[] keyFields) {
- this.keyFields = keyFields;
- }
-
- /**
- * @return Returns the valueFields.
- */
- public String[] getValueFields() {
- return valueFields;
- }
-
- /**
- * @param valueFields The valueFields to set.
- */
- public void setValueFields(String[] valueFields) {
- this.valueFields = valueFields;
- }
+ @HopMetadataProperty(
+ key = "key",
+ groupKey = "keys",
+ injectionKey = "KEY_FIELD",
+ injectionKeyDescription = "MergeRows.Injection.KEY_FIELD",
+ injectionGroupKey = "KEY_FIELDS",
+ injectionGroupDescription = "MergeRows.Injection.KEY_FIELDS")
+ private List<String> keyFields;
+
+ @HopMetadataProperty(
+ key = "value",
+ groupKey = "values",
+ injectionKey = "VALUE_FIELD",
+ injectionKeyDescription = "MergeRows.Injection.VALUE_FIELD",
+ injectionGroupKey = "VALUE_FIELDS",
+ injectionGroupDescription = "MergeRows.Injection.VALUE_FIELDS")
+ private List<String> valueFields;
+
+ @HopMetadataProperty(
+ key = "reference",
+ injectionKey = "REFERENCE_TRANSFORM",
+ injectionKeyDescription =
"MergeRowsMeta.InfoStream.FirstStream.Description")
+ private String referenceTransform;
+
+ @HopMetadataProperty(
+ key = "compare",
+ injectionKey = "COMPARE_TRANSFORM",
+ injectionKeyDescription =
"MergeRowsMeta.InfoStream.SecondStream.Description")
+ private String compareTransform;
+
+ @HopMetadataProperty(
+ key = "diff-field",
+ injectionKey = "DIFF_FIELD",
+ injectionKeyDescription = "MergeRows.Injection.DIFF_FIELD")
+ private String diffJsonField;
public MergeRowsMeta() {
- super(); // allocate BaseTransformMeta
- }
-
- @Override
- public void loadXml(Node transformNode, IHopMetadataProvider
metadataProvider)
- throws HopXmlException {
- readData(transformNode);
- }
-
- /**
- * @return Returns the flagField.
- */
- public String getFlagField() {
- return flagField;
- }
-
- /**
- * @param flagField The flagField to set.
- */
- public void setFlagField(String flagField) {
- this.flagField = flagField;
+ super();
+ keyFields = new ArrayList<>();
+ valueFields = new ArrayList<>();
}
- public void allocate(int nrKeys, int nrValues) {
- keyFields = new String[nrKeys];
- valueFields = new String[nrValues];
+ public MergeRowsMeta(MergeRowsMeta m) {
+ this.flagField = m.flagField;
+ this.keyFields = new ArrayList<>(m.keyFields);
+ this.valueFields = new ArrayList<>(m.valueFields);
+ this.referenceTransform = m.referenceTransform;
+ this.compareTransform = m.compareTransform;
}
@Override
- public Object clone() {
- MergeRowsMeta retval = (MergeRowsMeta) super.clone();
- int nrKeys = keyFields.length;
- int nrValues = valueFields.length;
- retval.allocate(nrKeys, nrValues);
- System.arraycopy(keyFields, 0, retval.keyFields, 0, nrKeys);
- System.arraycopy(valueFields, 0, retval.valueFields, 0, nrValues);
- return retval;
- }
-
- @Override
- public String getXml() {
- StringBuilder retval = new StringBuilder();
-
- retval.append(" <keys>" + Const.CR);
- for (int i = 0; i < keyFields.length; i++) {
- retval.append(" " + XmlHandler.addTagValue("key", keyFields[i]));
- }
- retval.append(" </keys>" + Const.CR);
-
- retval.append(" <values>" + Const.CR);
- for (int i = 0; i < valueFields.length; i++) {
- retval.append(" " + XmlHandler.addTagValue(CONST_VALUE,
valueFields[i]));
- }
- retval.append(" </values>" + Const.CR);
-
- retval.append(XmlHandler.addTagValue("flag_field", flagField));
-
- List<IStream> infoStreams = getTransformIOMeta().getInfoStreams();
- retval.append(XmlHandler.addTagValue("reference",
infoStreams.get(0).getTransformName()));
- retval.append(XmlHandler.addTagValue("compare",
infoStreams.get(1).getTransformName()));
- retval.append(" <compare>" + Const.CR);
-
- retval.append(" </compare>" + Const.CR);
-
- return retval.toString();
- }
-
- private void readData(Node transformNode) throws HopXmlException {
- try {
-
- Node keysnode = XmlHandler.getSubNode(transformNode, "keys");
- Node valuesnode = XmlHandler.getSubNode(transformNode, "values");
-
- int nrKeys = XmlHandler.countNodes(keysnode, "key");
- int nrValues = XmlHandler.countNodes(valuesnode, CONST_VALUE);
-
- allocate(nrKeys, nrValues);
-
- for (int i = 0; i < nrKeys; i++) {
- Node keynode = XmlHandler.getSubNodeByNr(keysnode, "key", i);
- keyFields[i] = XmlHandler.getNodeValue(keynode);
- }
-
- for (int i = 0; i < nrValues; i++) {
- Node valuenode = XmlHandler.getSubNodeByNr(valuesnode, CONST_VALUE, i);
- valueFields[i] = XmlHandler.getNodeValue(valuenode);
- }
-
- flagField = XmlHandler.getTagValue(transformNode, "flag_field");
-
- List<IStream> infoStreams = getTransformIOMeta().getInfoStreams();
- IStream referenceStream = infoStreams.get(0);
- IStream compareStream = infoStreams.get(1);
-
- compareStream.setSubject(XmlHandler.getTagValue(transformNode,
"compare"));
- referenceStream.setSubject(XmlHandler.getTagValue(transformNode,
"reference"));
- } catch (Exception e) {
- throw new HopXmlException(
- BaseMessages.getString(PKG,
"MergeRowsMeta.Exception.UnableToLoadTransformMeta"), e);
- }
+ public MergeRowsMeta clone() {
+ return new MergeRowsMeta(this);
}
@Override
public void setDefault() {
flagField = "flagfield";
- allocate(0, 0);
}
@Override
@@ -214,14 +133,6 @@ public class MergeRowsMeta extends
BaseTransformMeta<MergeRows, MergeRowsData> {
}
}
- public boolean chosesTargetTransforms() {
- return false;
- }
-
- public String[] getTargetTransforms() {
- return null;
- }
-
@Override
public void getFields(
IRowMeta r,
@@ -251,6 +162,12 @@ public class MergeRowsMeta extends
BaseTransformMeta<MergeRows, MergeRowsData> {
IValueMeta flagFieldValue = new ValueMetaString(flagField);
flagFieldValue.setOrigin(name);
r.addValueMeta(flagFieldValue);
+
+ if (StringUtils.isNotEmpty(variables.resolve(diffJsonField))) {
+ IValueMeta diffField = new
ValueMetaString(variables.resolve(diffJsonField));
+ diffField.setOrigin(name);
+ r.addValueMeta(diffField);
+ }
}
@Override
@@ -313,7 +230,7 @@ public class MergeRowsMeta extends
BaseTransformMeta<MergeRows, MergeRowsData> {
MergeRows.checkInputLayoutValid(referenceRowMeta, compareRowMeta);
rowsMatch = true;
} catch (HopRowException kre) {
- rowsMatch = false;
+ // Ignore
}
if (rowsMatch) {
cr =
@@ -338,7 +255,6 @@ public class MergeRowsMeta extends
BaseTransformMeta<MergeRows, MergeRowsData> {
public ITransformIOMeta getTransformIOMeta() {
ITransformIOMeta ioMeta = super.getTransformIOMeta(false);
if (ioMeta == null) {
-
ioMeta = new TransformIOMeta(true, true, false, false, false, false);
ioMeta.addStream(
@@ -347,25 +263,20 @@ public class MergeRowsMeta extends
BaseTransformMeta<MergeRows, MergeRowsData> {
null,
BaseMessages.getString(PKG,
"MergeRowsMeta.InfoStream.FirstStream.Description"),
StreamIcon.INFO,
- null));
+ referenceTransform));
ioMeta.addStream(
new Stream(
StreamType.INFO,
null,
BaseMessages.getString(PKG,
"MergeRowsMeta.InfoStream.SecondStream.Description"),
StreamIcon.INFO,
- null));
+ compareTransform));
setTransformIOMeta(ioMeta);
}
return ioMeta;
}
- @Override
- public void resetTransformIoMeta() {
- // Do Nothing
- }
-
@Override
public PipelineType[] getSupportedPipelineTypes() {
return new PipelineType[] {
diff --git
a/plugins/transforms/mergerows/src/main/resources/org/apache/hop/pipeline/transforms/mergerows/messages/messages_en_US.properties
b/plugins/transforms/mergerows/src/main/resources/org/apache/hop/pipeline/transforms/mergerows/messages/messages_en_US.properties
index ee4b0ac191..566d8c9e3f 100644
---
a/plugins/transforms/mergerows/src/main/resources/org/apache/hop/pipeline/transforms/mergerows/messages/messages_en_US.properties
+++
b/plugins/transforms/mergerows/src/main/resources/org/apache/hop/pipeline/transforms/mergerows/messages/messages_en_US.properties
@@ -19,8 +19,11 @@ MergeRows.Description=Merge two streams of rows, sorted on a
certain key. The tw
MergeRows.Exception.InvalidLayoutDetected=Invalid layout detected in input
streams, keys and values to merge have to be of identical structure and be in
the same place in the rows
MergeRows.Exception.UnableToFindFieldInReferenceStream=Unable to find field
[{0}] in reference stream.
MergeRows.Injection.FLAG_FIELD=The name of the flag field.
+MergeRows.Injection.DIFF_FIELD=The name of the field for the difference JSON.
MergeRows.Injection.KEY_FIELDS=Specify key fields to compare.
+MergeRows.Injection.KEY_FIELD=The key field to match with
MergeRows.Injection.VALUE_FIELDS=Specify value fields to compare.
+MergeRows.Injection.VALUE_FIELD=The field to compare
MergeRows.LineNumber=linenr
MergeRows.Log.BothTrueAndFalseNeeded=Both the ''true'' and the ''false''
transforms need to be supplied, or neither
MergeRows.Log.DataInfo=ONE\: {0} / TWO\:
@@ -30,7 +33,8 @@ MergeRowsDialog.ColumnInfo.ValueField=Value field
MergeRowsDialog.Compare.Label=Compare rows origin\:
MergeRowsDialog.ErrorGettingFields.DialogMessage=Unable to get the fields
because of an error\:
MergeRowsDialog.ErrorGettingFields.DialogTitle=Error getting fields
-MergeRowsDialog.FlagField.Label=Flag fieldname
+MergeRowsDialog.FlagField.Label=Flag field name
+MergeRowsDialog.DiffField.Label=Difference JSON field name
MergeRowsDialog.KeyFields.Button=Get &key fields
MergeRowsDialog.Keys.Label=Keys to match \:
MergeRowsDialog.MergeRowsWarningDialog.DialogMessage=If the incoming data is
not sorted on the specified keys, the output results may not be correct. We
recommend sorting the incoming data within the pipeline.
diff --git
a/plugins/transforms/mergerows/src/test/java/org/apache/hop/pipeline/transforms/mergerows/MergeRowsMetaInjectionTest.java
b/plugins/transforms/mergerows/src/test/java/org/apache/hop/pipeline/transforms/mergerows/MergeRowsMetaInjectionTest.java
deleted file mode 100644
index 0727e9f0aa..0000000000
---
a/plugins/transforms/mergerows/src/test/java/org/apache/hop/pipeline/transforms/mergerows/MergeRowsMetaInjectionTest.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.mergerows;
-
-import org.apache.hop.core.injection.BaseMetadataInjectionTestJunit5;
-import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.RegisterExtension;
-
-public class MergeRowsMetaInjectionTest extends
BaseMetadataInjectionTestJunit5<MergeRowsMeta> {
- @RegisterExtension
- static RestoreHopEngineEnvironmentExtension env = new
RestoreHopEngineEnvironmentExtension();
-
- @BeforeEach
- public void setup() throws Exception {
- setup(new MergeRowsMeta());
- }
-
- @Test
- public void test() throws Exception {
- check("FLAG_FIELD", () -> meta.getFlagField());
- check("KEY_FIELDS", () -> meta.getKeyFields()[0]);
- check("VALUE_FIELDS", () -> meta.getValueFields()[0]);
- }
-}
diff --git
a/plugins/transforms/mergerows/src/test/java/org/apache/hop/pipeline/transforms/mergerows/MergeRowsMetaTest.java
b/plugins/transforms/mergerows/src/test/java/org/apache/hop/pipeline/transforms/mergerows/MergeRowsMetaTest.java
index b957584263..3083170635 100644
---
a/plugins/transforms/mergerows/src/test/java/org/apache/hop/pipeline/transforms/mergerows/MergeRowsMetaTest.java
+++
b/plugins/transforms/mergerows/src/test/java/org/apache/hop/pipeline/transforms/mergerows/MergeRowsMetaTest.java
@@ -6,80 +6,51 @@
* (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
+ * 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.mergerows;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
import org.apache.hop.core.HopEnvironment;
-import org.apache.hop.core.exception.HopException;
import org.apache.hop.core.plugins.PluginRegistry;
import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
-import org.apache.hop.pipeline.transform.ITransformMeta;
-import org.apache.hop.pipeline.transforms.loadsave.LoadSaveTester;
-import org.apache.hop.pipeline.transforms.loadsave.initializer.IInitializer;
-import
org.apache.hop.pipeline.transforms.loadsave.validator.ArrayLoadSaveValidator;
-import
org.apache.hop.pipeline.transforms.loadsave.validator.IFieldLoadSaveValidator;
-import
org.apache.hop.pipeline.transforms.loadsave.validator.StringLoadSaveValidator;
+import org.apache.hop.pipeline.transform.TransformSerializationTestUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
-public class MergeRowsMetaTest implements IInitializer<ITransformMeta> {
- LoadSaveTester loadSaveTester;
- Class<MergeRowsMeta> testMetaClass = MergeRowsMeta.class;
+class MergeRowsMetaTest {
@RegisterExtension
static RestoreHopEngineEnvironmentExtension env = new
RestoreHopEngineEnvironmentExtension();
@BeforeEach
- public void setUpLoadSave() throws Exception {
+ void setUpLoadSave() throws Exception {
HopEnvironment.init();
PluginRegistry.init();
- List<String> attributes = Arrays.asList("flagField", "keyFields",
"valueFields");
-
- IFieldLoadSaveValidator<String[]> stringArrayLoadSaveValidator =
- new ArrayLoadSaveValidator<>(new StringLoadSaveValidator(), 5);
-
- Map<String, IFieldLoadSaveValidator<?>> attrValidatorMap = new HashMap<>();
- attrValidatorMap.put("keyFields", stringArrayLoadSaveValidator);
- attrValidatorMap.put("valueFields", stringArrayLoadSaveValidator);
-
- Map<String, IFieldLoadSaveValidator<?>> typeValidatorMap = new HashMap<>();
-
- loadSaveTester =
- new LoadSaveTester(
- testMetaClass,
- attributes,
- new HashMap<>(),
- new HashMap<>(),
- attrValidatorMap,
- typeValidatorMap,
- this);
- }
-
- // Call the allocate method on the LoadSaveTester meta class
- @Override
- public void modify(ITransformMeta someMeta) {
- if (someMeta instanceof MergeRowsMeta) {
- ((MergeRowsMeta) someMeta).allocate(5, 5);
- }
}
@Test
- public void testSerialization() throws HopException {
- loadSaveTester.testSerialization();
+ void testSerialization() throws Exception {
+ MergeRowsMeta meta =
+ TransformSerializationTestUtil.testSerialization("/merge-rows.xml",
MergeRowsMeta.class);
+
+ assertEquals(1, meta.getKeyFields().size());
+ assertEquals("id", meta.getKeyFields().get(0));
+ assertEquals("flagfield", meta.getFlagField());
+ assertEquals("source1", meta.getReferenceTransform());
+ assertEquals("source2", meta.getCompareTransform());
+ assertEquals(2, meta.getValueFields().size());
+ assertEquals("name", meta.getValueFields().get(0));
+ assertEquals("score", meta.getValueFields().get(1));
}
-
- // Note - cloneTest removed as load/save validator covers clone testing as
well now.
}
diff --git a/plugins/transforms/mergerows/src/test/resources/merge-rows.xml
b/plugins/transforms/mergerows/src/test/resources/merge-rows.xml
new file mode 100644
index 0000000000..7984c65343
--- /dev/null
+++ b/plugins/transforms/mergerows/src/test/resources/merge-rows.xml
@@ -0,0 +1,30 @@
+<?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.
+ ~
+ -->
+<transform>
+ <keys>
+ <key>id</key>
+ </keys>
+ <values>
+ <value>name</value>
+ <value>score</value>
+ </values>
+ <flag_field>flagfield</flag_field>
+ <reference>source1</reference>
+ <compare>source2</compare>
+</transform>
\ No newline at end of file
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/HopCommandGui.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/HopCommandGui.java
index efac41f7cc..40a11e7b9b 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/HopCommandGui.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/HopCommandGui.java
@@ -20,6 +20,7 @@ package org.apache.hop.ui.hopgui;
import lombok.Getter;
import lombok.Setter;
+import org.apache.hop.core.Const;
import org.apache.hop.core.HopVersionProvider;
import org.apache.hop.core.exception.HopException;
import org.apache.hop.core.variables.IVariables;
@@ -45,6 +46,7 @@ public class HopCommandGui implements Runnable, IHopCommand {
CommandLine cmd, IVariables variables, MultiMetadataProvider
metadataProvider)
throws HopException {
// Nothing specific
+ System.setProperty(Const.HOP_PLATFORM_RUNTIME, "GUI");
}
@Override