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 9a9bf8bfd2 issue #1946 (Unique Rows by Hash set XML cleanup, MDI) 
(#6794)
9a9bf8bfd2 is described below

commit 9a9bf8bfd2921e4639956da81ba82a0d64008f40
Author: Matt Casters <[email protected]>
AuthorDate: Tue Mar 17 09:27:21 2026 +0100

    issue #1946 (Unique Rows by Hash set XML cleanup, MDI) (#6794)
---
 .../mdi/0040-unique-hashset-template.hpl           |  66 +++++
 .../mdi/0040-unique-hashset-validate.hpl           | 255 +++++++++++++++++
 integration-tests/mdi/0040-unique-hashset.hpl      | 278 ++++++++++++++++++
 .../datasets/golden-unique-rows-hashset-fields.csv |   4 +
 .../mdi/datasets/golden-unique-rows-hashset.csv    |   2 +
 .../mdi/files/unique-rows-hashset.xml              |  34 +++
 .../main-0040-unique-hashset.hwf}                  |  63 ++++-
 .../dataset/golden-unique-rows-hashset-fields.json |  16 ++
 .../dataset/golden-unique-rows-hashset.json        |  32 +++
 .../0040-unique-hashset-validate UNIT.json         |  74 +++++
 .../transforms/0034-unique-rows-hashset.hpl        | 309 +++++++++++++++++++++
 .../datasets/golden-unique-rows-hashset.csv        |   6 +
 .../transforms/main-0034-unique-rows.hwf           |  15 +-
 .../dataset/golden-unique-rows-hashset.json        |  32 +++
 .../unit-test/0034-unique-rows-hashset UNIT.json   |  34 +++
 .../uniquerowsbyhashset/UniqueRowsByHashSet.java   |  15 +-
 .../UniqueRowsByHashSetDialog.java                 |  19 +-
 .../UniqueRowsByHashSetMeta.java                   | 203 ++++----------
 .../messages/messages_en_US.properties             |   6 +
 .../UniqueRowsByHashSetMetaTest.java               |  89 +++---
 .../src/test/resources/transform.xml               |  33 +++
 21 files changed, 1356 insertions(+), 229 deletions(-)

diff --git a/integration-tests/mdi/0040-unique-hashset-template.hpl 
b/integration-tests/mdi/0040-unique-hashset-template.hpl
new file mode 100644
index 0000000000..d7db03cdd9
--- /dev/null
+++ b/integration-tests/mdi/0040-unique-hashset-template.hpl
@@ -0,0 +1,66 @@
+<?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>0040-unique-hashset-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>2026/03/16 15:28:54.628</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/03/16 15:28:54.628</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+  </order>
+  <transform>
+    <name>Unique rows (HashSet)</name>
+    <type>UniqueRowsByHashSet</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <fields>
+</fields>
+    <reject_duplicate_row>N</reject_duplicate_row>
+    <store_values>Y</store_values>
+    <attributes/>
+    <GUI>
+      <xloc>176</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git a/integration-tests/mdi/0040-unique-hashset-validate.hpl 
b/integration-tests/mdi/0040-unique-hashset-validate.hpl
new file mode 100644
index 0000000000..89cdc07028
--- /dev/null
+++ b/integration-tests/mdi/0040-unique-hashset-validate.hpl
@@ -0,0 +1,255 @@
+<?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>0040-unique-hashset-validate</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>2026/03/16 15:40:51.338</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/03/16 15:40:51.338</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+    <hop>
+      <from>${java.io.tmpdir}/unique-rows-hashset.hpl</from>
+      <to>validate</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>${java.io.tmpdir}/unique-rows-hashset.hpl fields</from>
+      <to>validate fields</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>${java.io.tmpdir}/unique-rows-hashset.hpl</name>
+    <type>getXMLData</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <IsAFile>N</IsAFile>
+    <IsIgnoreEmptyFile>N</IsIgnoreEmptyFile>
+    <IsInFields>N</IsInFields>
+    <XmlField/>
+    <addresultfile>N</addresultfile>
+    <doNotFailIfNoFile>Y</doNotFailIfNoFile>
+    <encoding>UTF-8</encoding>
+    <extensionFieldName/>
+    <fields>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>error_description</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>error_description</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>reject_duplicate_row</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>reject_duplicate_row</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>store_values</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>store_values</xPath>
+      </field>
+    </fields>
+    <file>
+      <exclude_filemask/>
+      <file_required>N</file_required>
+      <filemask/>
+      <include_subfolders>N</include_subfolders>
+      <name>${java.io.tmpdir}/unique-rows-hashset.hpl</name>
+    </file>
+    <hiddenFieldName/>
+    <ignorecomments>N</ignorecomments>
+    <include>N</include>
+    <include_field/>
+    <lastModificationTimeFieldName/>
+    <limit>0</limit>
+    <loopxpath>/pipeline/transform</loopxpath>
+    <namespaceaware>N</namespaceaware>
+    <pathFieldName/>
+    <prunePath/>
+    <readurl>N</readurl>
+    <rootUriNameFieldName/>
+    <rownum>N</rownum>
+    <rownum_field/>
+    <shortFileFieldName/>
+    <sizeFieldName/>
+    <uriNameFieldName/>
+    <usetoken>N</usetoken>
+    <validating>N</validating>
+    <attributes/>
+    <GUI>
+      <xloc>112</xloc>
+      <yloc>48</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>352</xloc>
+      <yloc>48</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>${java.io.tmpdir}/unique-rows-hashset.hpl fields</name>
+    <type>getXMLData</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <IsAFile>N</IsAFile>
+    <IsIgnoreEmptyFile>N</IsIgnoreEmptyFile>
+    <IsInFields>N</IsInFields>
+    <XmlField/>
+    <addresultfile>N</addresultfile>
+    <doNotFailIfNoFile>Y</doNotFailIfNoFile>
+    <encoding>UTF-8</encoding>
+    <extensionFieldName/>
+    <fields>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>name</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>name</xPath>
+      </field>
+    </fields>
+    <file>
+      <exclude_filemask/>
+      <file_required>N</file_required>
+      <filemask/>
+      <include_subfolders>N</include_subfolders>
+      <name>${java.io.tmpdir}/unique-rows-hashset.hpl</name>
+    </file>
+    <hiddenFieldName/>
+    <ignorecomments>N</ignorecomments>
+    <include>N</include>
+    <include_field/>
+    <lastModificationTimeFieldName/>
+    <limit>0</limit>
+    <loopxpath>/pipeline/transform/fields/field</loopxpath>
+    <namespaceaware>N</namespaceaware>
+    <pathFieldName/>
+    <prunePath/>
+    <readurl>N</readurl>
+    <rootUriNameFieldName/>
+    <rownum>N</rownum>
+    <rownum_field/>
+    <shortFileFieldName/>
+    <sizeFieldName/>
+    <uriNameFieldName/>
+    <usetoken>N</usetoken>
+    <validating>N</validating>
+    <attributes/>
+    <GUI>
+      <xloc>112</xloc>
+      <yloc>144</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>validate fields</name>
+    <type>Dummy</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+    <GUI>
+      <xloc>352</xloc>
+      <yloc>144</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git a/integration-tests/mdi/0040-unique-hashset.hpl 
b/integration-tests/mdi/0040-unique-hashset.hpl
new file mode 100644
index 0000000000..f30e99909d
--- /dev/null
+++ b/integration-tests/mdi/0040-unique-hashset.hpl
@@ -0,0 +1,278 @@
+<?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>0040-unique-hashset</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>2026/03/16 15:29:25.284</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/03/16 15:29:25.284</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+    <hop>
+      <from>unique-rows-hashset.xml</from>
+      <to>ETL metadata injection</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>unique-rows-hashset.xml fields</from>
+      <to>ETL metadata injection</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>unique-rows-hashset.xml</name>
+    <type>getXMLData</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <IsAFile>N</IsAFile>
+    <IsIgnoreEmptyFile>N</IsIgnoreEmptyFile>
+    <IsInFields>N</IsInFields>
+    <XmlField/>
+    <addresultfile>N</addresultfile>
+    <doNotFailIfNoFile>Y</doNotFailIfNoFile>
+    <encoding>UTF-8</encoding>
+    <extensionFieldName/>
+    <fields>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>store_values</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>store_values</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>reject_duplicate_row</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>reject_duplicate_row</xPath>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>error_description</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>error_description</xPath>
+      </field>
+    </fields>
+    <file>
+      <exclude_filemask/>
+      <file_required>N</file_required>
+      <filemask/>
+      <include_subfolders>N</include_subfolders>
+      <name>${PROJECT_HOME}/files/unique-rows-hashset.xml</name>
+    </file>
+    <hiddenFieldName/>
+    <ignorecomments>N</ignorecomments>
+    <include>N</include>
+    <include_field/>
+    <lastModificationTimeFieldName/>
+    <limit>0</limit>
+    <loopxpath>/transform</loopxpath>
+    <namespaceaware>N</namespaceaware>
+    <pathFieldName/>
+    <prunePath/>
+    <readurl>N</readurl>
+    <rootUriNameFieldName/>
+    <rownum>N</rownum>
+    <rownum_field/>
+    <shortFileFieldName/>
+    <sizeFieldName/>
+    <uriNameFieldName/>
+    <usetoken>N</usetoken>
+    <validating>N</validating>
+    <attributes/>
+    <GUI>
+      <xloc>96</xloc>
+      <yloc>80</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>unique-rows-hashset.xml fields</name>
+    <type>getXMLData</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <IsAFile>N</IsAFile>
+    <IsIgnoreEmptyFile>N</IsIgnoreEmptyFile>
+    <IsInFields>N</IsInFields>
+    <XmlField/>
+    <addresultfile>N</addresultfile>
+    <doNotFailIfNoFile>Y</doNotFailIfNoFile>
+    <encoding>UTF-8</encoding>
+    <extensionFieldName/>
+    <fields>
+      <field>
+        <currency/>
+        <decimal/>
+        <element_type>Node</element_type>
+        <format/>
+        <group/>
+        <length>-1</length>
+        <name>name</name>
+        <precision>-1</precision>
+        <repeat>N</repeat>
+        <result_type>valueof</result_type>
+        <trim_type>none</trim_type>
+        <type>String</type>
+        <xPath>name</xPath>
+      </field>
+    </fields>
+    <file>
+      <exclude_filemask/>
+      <file_required>N</file_required>
+      <filemask/>
+      <include_subfolders>N</include_subfolders>
+      <name>${PROJECT_HOME}/files/unique-rows-hashset.xml</name>
+    </file>
+    <hiddenFieldName/>
+    <ignorecomments>N</ignorecomments>
+    <include>N</include>
+    <include_field/>
+    <lastModificationTimeFieldName/>
+    <limit>0</limit>
+    <loopxpath>/transform/fields/field</loopxpath>
+    <namespaceaware>N</namespaceaware>
+    <pathFieldName/>
+    <prunePath/>
+    <readurl>N</readurl>
+    <rootUriNameFieldName/>
+    <rownum>N</rownum>
+    <rownum_field/>
+    <shortFileFieldName/>
+    <sizeFieldName/>
+    <uriNameFieldName/>
+    <usetoken>N</usetoken>
+    <validating>N</validating>
+    <attributes/>
+    <GUI>
+      <xloc>96</xloc>
+      <yloc>176</yloc>
+    </GUI>
+  </transform>
+  <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}/0040-unique-hashset-template.hpl</filename>
+    <run_configuration>local</run_configuration>
+    <source_transform/>
+    <source_output_fields>    </source_output_fields>
+    <target_file>${java.io.tmpdir}/unique-rows-hashset.hpl</target_file>
+    <create_parent_folder>Y</create_parent_folder>
+    <no_execution>Y</no_execution>
+    <allow_empty_stream_on_execution>N</allow_empty_stream_on_execution>
+    <stream_source_transform/>
+    <stream_target_transform/>
+    <mappings>
+      <mapping>
+        <target_transform_name>Unique rows (HashSet)</target_transform_name>
+        <target_attribute_key>COMPARE_FIELD_NAME</target_attribute_key>
+        <target_detail>Y</target_detail>
+        <source_transform>unique-rows-hashset.xml fields</source_transform>
+        <source_field>name</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>Unique rows (HashSet)</target_transform_name>
+        <target_attribute_key>ERROR_DESCRIPTION</target_attribute_key>
+        <target_detail>N</target_detail>
+        <source_transform>unique-rows-hashset.xml</source_transform>
+        <source_field>error_description</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>Unique rows (HashSet)</target_transform_name>
+        <target_attribute_key>REJECT_DUPLICATE_ROW</target_attribute_key>
+        <target_detail>N</target_detail>
+        <source_transform>unique-rows-hashset.xml</source_transform>
+        <source_field>reject_duplicate_row</source_field>
+      </mapping>
+      <mapping>
+        <target_transform_name>Unique rows (HashSet)</target_transform_name>
+        <target_attribute_key>STORE_VALUES</target_attribute_key>
+        <target_detail>N</target_detail>
+        <source_transform>unique-rows-hashset.xml</source_transform>
+        <source_field>store_values</source_field>
+      </mapping>
+    </mappings>
+    <attributes/>
+    <GUI>
+      <xloc>256</xloc>
+      <yloc>80</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git 
a/integration-tests/mdi/datasets/golden-unique-rows-hashset-fields.csv 
b/integration-tests/mdi/datasets/golden-unique-rows-hashset-fields.csv
new file mode 100644
index 0000000000..68cc89633c
--- /dev/null
+++ b/integration-tests/mdi/datasets/golden-unique-rows-hashset-fields.csv
@@ -0,0 +1,4 @@
+name
+compareField1
+compareField2
+compareField3
diff --git a/integration-tests/mdi/datasets/golden-unique-rows-hashset.csv 
b/integration-tests/mdi/datasets/golden-unique-rows-hashset.csv
new file mode 100644
index 0000000000..e57944bc4d
--- /dev/null
+++ b/integration-tests/mdi/datasets/golden-unique-rows-hashset.csv
@@ -0,0 +1,2 @@
+store_values,reject_duplicate_row,error_description
+Y,Y,Duplicate row detected
diff --git a/integration-tests/mdi/files/unique-rows-hashset.xml 
b/integration-tests/mdi/files/unique-rows-hashset.xml
new file mode 100644
index 0000000000..048b921f11
--- /dev/null
+++ b/integration-tests/mdi/files/unique-rows-hashset.xml
@@ -0,0 +1,34 @@
+<?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>
+    <store_values>Y</store_values>
+    <reject_duplicate_row>Y</reject_duplicate_row>
+    <error_description>Duplicate row detected</error_description>
+    <fields>
+        <field>
+            <name>compareField1</name>
+        </field>
+        <field>
+            <name>compareField2</name>
+        </field>
+        <field>
+            <name>compareField3</name>
+        </field>
+    </fields>
+</transform>
+
diff --git a/integration-tests/transforms/main-0034-unique-rows.hwf 
b/integration-tests/mdi/main-0040-unique-hashset.hwf
similarity index 56%
copy from integration-tests/transforms/main-0034-unique-rows.hwf
copy to integration-tests/mdi/main-0040-unique-hashset.hwf
index 083945f40a..7b7648162f 100644
--- a/integration-tests/transforms/main-0034-unique-rows.hwf
+++ b/integration-tests/mdi/main-0040-unique-hashset.hwf
@@ -18,16 +18,15 @@ limitations under the License.
 
 -->
 <workflow>
-  <name>main-0034-unique-rows</name>
+  <name>main-0040-unique-hashset</name>
   <name_sync_with_filename>Y</name_sync_with_filename>
   <description/>
   <extended_description/>
   <workflow_version/>
-  <workflow_status>0</workflow_status>
   <created_user>-</created_user>
-  <created_date>2021/07/21 20:36:24.295</created_date>
+  <created_date>2026/03/16 15:28:51.665</created_date>
   <modified_user>-</modified_user>
-  <modified_date>2021/07/21 20:36:24.295</modified_date>
+  <modified_date>2026/03/16 15:28:51.665</modified_date>
   <parameters>
     </parameters>
   <actions>
@@ -36,31 +35,58 @@ limitations under the License.
       <description/>
       <type>SPECIAL</type>
       <attributes/>
-      <repeat>N</repeat>
-      <schedulerType>0</schedulerType>
-      <intervalSeconds>0</intervalSeconds>
-      <intervalMinutes>60</intervalMinutes>
+      <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>
-      <DayOfMonth>1</DayOfMonth>
       <parallel>N</parallel>
-      <xloc>128</xloc>
+      <xloc>96</xloc>
       <yloc>80</yloc>
       <attributes_hac/>
     </action>
     <action>
-      <name>Run Clone Row Tests</name>
+      <name>Run Pipeline Unit Tests</name>
       <description/>
       <type>RunPipelineTests</type>
       <attributes/>
       <test_names>
         <test_name>
-          <name>0034-unique-rows UNIT</name>
+          <name>0040-unique-hashset-validate UNIT</name>
         </test_name>
       </test_names>
       <parallel>N</parallel>
-      <xloc>272</xloc>
+      <xloc>416</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>0040-unique-hashset</name>
+      <description/>
+      <type>PIPELINE</type>
+      <attributes/>
+      <add_date>N</add_date>
+      <add_time>N</add_time>
+      <clear_files>N</clear_files>
+      <clear_rows>N</clear_rows>
+      <create_parent_folder>N</create_parent_folder>
+      <exec_per_row>N</exec_per_row>
+      <filename>${PROJECT_HOME}/0040-unique-hashset.hpl</filename>
+      <loglevel>Basic</loglevel>
+      <parameters>
+        <pass_all_parameters>Y</pass_all_parameters>
+      </parameters>
+      <params_from_previous>N</params_from_previous>
+      <run_configuration>local</run_configuration>
+      <set_append_logfile>N</set_append_logfile>
+      <set_logfile>N</set_logfile>
+      <wait_until_finished>Y</wait_until_finished>
+      <parallel>N</parallel>
+      <xloc>256</xloc>
       <yloc>80</yloc>
       <attributes_hac/>
     </action>
@@ -68,11 +94,18 @@ limitations under the License.
   <hops>
     <hop>
       <from>Start</from>
-      <to>Run Clone Row Tests</to>
+      <to>0040-unique-hashset</to>
       <enabled>Y</enabled>
-      <evaluation>N</evaluation>
+      <evaluation>Y</evaluation>
       <unconditional>Y</unconditional>
     </hop>
+    <hop>
+      <from>0040-unique-hashset</from>
+      <to>Run Pipeline Unit Tests</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
   </hops>
   <notepads>
   </notepads>
diff --git 
a/integration-tests/mdi/metadata/dataset/golden-unique-rows-hashset-fields.json 
b/integration-tests/mdi/metadata/dataset/golden-unique-rows-hashset-fields.json
new file mode 100644
index 0000000000..ab29b163a2
--- /dev/null
+++ 
b/integration-tests/mdi/metadata/dataset/golden-unique-rows-hashset-fields.json
@@ -0,0 +1,16 @@
+{
+  "base_filename": "golden-unique-rows-hashset-fields.csv",
+  "name": "golden-unique-rows-hashset-fields",
+  "description": "",
+  "dataset_fields": [
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "name",
+      "field_format": ""
+    }
+  ],
+  "folder_name": ""
+}
\ No newline at end of file
diff --git 
a/integration-tests/mdi/metadata/dataset/golden-unique-rows-hashset.json 
b/integration-tests/mdi/metadata/dataset/golden-unique-rows-hashset.json
new file mode 100644
index 0000000000..a8a650d326
--- /dev/null
+++ b/integration-tests/mdi/metadata/dataset/golden-unique-rows-hashset.json
@@ -0,0 +1,32 @@
+{
+  "base_filename": "golden-unique-rows-hashset.csv",
+  "name": "golden-unique-rows-hashset",
+  "description": "",
+  "dataset_fields": [
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "store_values",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "reject_duplicate_row",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "error_description",
+      "field_format": ""
+    }
+  ],
+  "folder_name": ""
+}
\ No newline at end of file
diff --git 
a/integration-tests/mdi/metadata/unit-test/0040-unique-hashset-validate 
UNIT.json 
b/integration-tests/mdi/metadata/unit-test/0040-unique-hashset-validate 
UNIT.json
new file mode 100644
index 0000000000..af8ecd66a4
--- /dev/null
+++ b/integration-tests/mdi/metadata/unit-test/0040-unique-hashset-validate 
UNIT.json   
@@ -0,0 +1,74 @@
+{
+  "database_replacements": [],
+  "autoOpening": true,
+  "description": "",
+  "persist_filename": "",
+  "test_type": "UNIT_TEST",
+  "variableValues": [],
+  "basePath": "${HOP_UNIT_TESTS_FOLDER}",
+  "golden_data_sets": [
+    {
+      "field_mappings": [
+        {
+          "transform_field": "error_description",
+          "data_set_field": "error_description"
+        },
+        {
+          "transform_field": "reject_duplicate_row",
+          "data_set_field": "reject_duplicate_row"
+        },
+        {
+          "transform_field": "store_values",
+          "data_set_field": "store_values"
+        }
+      ],
+      "field_order": [
+        "store_values",
+        "reject_duplicate_row",
+        "error_description"
+      ],
+      "data_set_name": "golden-unique-rows-hashset",
+      "transform_name": "Dummy (do nothing)"
+    },
+    {
+      "field_mappings": [
+        {
+          "transform_field": "error_description",
+          "data_set_field": "error_description"
+        },
+        {
+          "transform_field": "reject_duplicate_row",
+          "data_set_field": "reject_duplicate_row"
+        },
+        {
+          "transform_field": "store_values",
+          "data_set_field": "store_values"
+        }
+      ],
+      "field_order": [
+        "store_values",
+        "reject_duplicate_row",
+        "error_description"
+      ],
+      "data_set_name": "golden-unique-rows-hashset",
+      "transform_name": "validate"
+    },
+    {
+      "field_mappings": [
+        {
+          "transform_field": "name",
+          "data_set_field": "name"
+        }
+      ],
+      "field_order": [
+        "name"
+      ],
+      "data_set_name": "golden-unique-rows-hashset-fields",
+      "transform_name": "validate fields"
+    }
+  ],
+  "input_data_sets": [],
+  "name": "0040-unique-hashset-validate UNIT",
+  "trans_test_tweaks": [],
+  "pipeline_filename": "./0040-unique-hashset-validate.hpl"
+}
\ No newline at end of file
diff --git a/integration-tests/transforms/0034-unique-rows-hashset.hpl 
b/integration-tests/transforms/0034-unique-rows-hashset.hpl
new file mode 100644
index 0000000000..0386abc432
--- /dev/null
+++ b/integration-tests/transforms/0034-unique-rows-hashset.hpl
@@ -0,0 +1,309 @@
+<?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>0034-unique-rows-hashset</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description/>
+    <extended_description/>
+    <pipeline_version/>
+    <pipeline_type>Normal</pipeline_type>
+    <pipeline_status>0</pipeline_status>
+    <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>2021/07/13 21:09:00.016</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2021/07/13 21:09:00.016</modified_date>
+  </info>
+  <notepads>
+    <notepad>
+      <backgroundcolorblue>251</backgroundcolorblue>
+      <backgroundcolorgreen>232</backgroundcolorgreen>
+      <backgroundcolorred>201</backgroundcolorred>
+      <bordercolorblue>90</bordercolorblue>
+      <bordercolorgreen>58</bordercolorgreen>
+      <bordercolorred>14</bordercolorred>
+      <fontbold>N</fontbold>
+      <fontcolorblue>90</fontcolorblue>
+      <fontcolorgreen>58</fontcolorgreen>
+      <fontcolorred>14</fontcolorred>
+      <fontitalic>N</fontitalic>
+      <fontname>Segoe UI</fontname>
+      <fontsize>9</fontsize>
+      <height>-1</height>
+      <xloc>112</xloc>
+      <yloc>32</yloc>
+      <note>Warning, row are sorted in case insentive order and dataset is 
case sensitive order</note>
+      <width>437</width>
+    </notepad>
+  </notepads>
+  <order>
+    <hop>
+      <from>Data grid</from>
+      <to>sort</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>sort</from>
+      <to>Unique rows (HashSet)</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Unique rows (HashSet)</from>
+      <to>Result</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Unique rows (HashSet)</from>
+      <to>Duplicate row</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>Data grid</name>
+    <type>DataGrid</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <data>
+      <line>
+        <item>ABCD</item>
+        <item>Z</item>
+        <item>1</item>
+      </line>
+      <line>
+        <item>ABCD</item>
+        <item>Z</item>
+        <item>2</item>
+      </line>
+      <line>
+        <item>ABCD</item>
+        <item>z</item>
+        <item>2</item>
+      </line>
+      <line>
+        <item>ABCD</item>
+        <item>z</item>
+        <item>6</item>
+      </line>
+      <line>
+        <item>abcd</item>
+        <item>X</item>
+        <item>9</item>
+      </line>
+      <line>
+        <item>abcd</item>
+        <item>X</item>
+        <item>9</item>
+      </line>
+      <line>
+        <item>EFG</item>
+        <item>X</item>
+        <item>8</item>
+      </line>
+      <line>
+        <item>EFG</item>
+        <item>Z</item>
+        <item>1</item>
+      </line>
+      <line>
+        <item>ABCD</item>
+        <item>Z</item>
+        <item>2</item>
+      </line>
+      <line>
+        <item>ABCD</item>
+        <item>z</item>
+        <item>2</item>
+      </line>
+      <line>
+        <item>EFG</item>
+        <item>X</item>
+        <item>6</item>
+      </line>
+      <line>
+        <item>EFG</item>
+        <item>Z</item>
+        <item>8</item>
+      </line>
+    </data>
+    <fields>
+      <field>
+        <length>-1</length>
+        <precision>-1</precision>
+        <set_empty_string>N</set_empty_string>
+        <name>Field_A</name>
+        <type>String</type>
+      </field>
+      <field>
+        <length>-1</length>
+        <precision>-1</precision>
+        <set_empty_string>N</set_empty_string>
+        <name>Field_B</name>
+        <type>String</type>
+      </field>
+      <field>
+        <length>-1</length>
+        <precision>-1</precision>
+        <set_empty_string>N</set_empty_string>
+        <name>Field_C</name>
+        <type>Integer</type>
+      </field>
+    </fields>
+    <attributes/>
+    <GUI>
+      <xloc>112</xloc>
+      <yloc>112</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Duplicate row</name>
+    <type>Dummy</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+    <GUI>
+      <xloc>384</xloc>
+      <yloc>240</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Result</name>
+    <type>Dummy</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+    <GUI>
+      <xloc>496</xloc>
+      <yloc>112</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Unique rows (HashSet)</name>
+    <type>UniqueRowsByHashSet</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <error_description>duplicate</error_description>
+    <fields>
+      <field>
+        <name>Field_A</name>
+      </field>
+      <field>
+        <name>Field_B</name>
+      </field>
+    </fields>
+    <reject_duplicate_row>Y</reject_duplicate_row>
+    <store_values>Y</store_values>
+    <attributes/>
+    <GUI>
+      <xloc>384</xloc>
+      <yloc>112</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>sort</name>
+    <type>SortRows</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <compress>N</compress>
+    <directory>${java.io.tmpdir}</directory>
+    <fields>
+      <field>
+        <ascending>Y</ascending>
+        <case_sensitive>N</case_sensitive>
+        <collator_enabled>N</collator_enabled>
+        <collator_strength>0</collator_strength>
+        <name>Field_A</name>
+        <presorted>N</presorted>
+      </field>
+      <field>
+        <ascending>Y</ascending>
+        <case_sensitive>N</case_sensitive>
+        <collator_enabled>N</collator_enabled>
+        <collator_strength>0</collator_strength>
+        <name>Field_B</name>
+        <presorted>N</presorted>
+      </field>
+      <field>
+        <ascending>Y</ascending>
+        <case_sensitive>N</case_sensitive>
+        <collator_enabled>N</collator_enabled>
+        <collator_strength>0</collator_strength>
+        <name>Field_C</name>
+        <presorted>N</presorted>
+      </field>
+    </fields>
+    <sort_size>1000000</sort_size>
+    <unique_rows>N</unique_rows>
+    <attributes/>
+    <GUI>
+      <xloc>240</xloc>
+      <yloc>112</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+    <error>
+      <source_transform>Unique rows (HashSet)</source_transform>
+      <target_transform>Duplicate row</target_transform>
+      <is_enabled>Y</is_enabled>
+      <nr_valuename/>
+      <descriptions_valuename/>
+      <fields_valuename/>
+      <codes_valuename/>
+      <max_errors/>
+      <max_pct_errors/>
+      <min_pct_rows/>
+    </error>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git 
a/integration-tests/transforms/datasets/golden-unique-rows-hashset.csv 
b/integration-tests/transforms/datasets/golden-unique-rows-hashset.csv
new file mode 100644
index 0000000000..2b53c4edd5
--- /dev/null
+++ b/integration-tests/transforms/datasets/golden-unique-rows-hashset.csv
@@ -0,0 +1,6 @@
+Field_A,Field_B,Field_C
+abcd,X,9
+ABCD,Z,1
+ABCD,z,2
+EFG,X,6
+EFG,Z,1
diff --git a/integration-tests/transforms/main-0034-unique-rows.hwf 
b/integration-tests/transforms/main-0034-unique-rows.hwf
index 083945f40a..77ec103def 100644
--- a/integration-tests/transforms/main-0034-unique-rows.hwf
+++ b/integration-tests/transforms/main-0034-unique-rows.hwf
@@ -36,21 +36,22 @@ limitations under the License.
       <description/>
       <type>SPECIAL</type>
       <attributes/>
-      <repeat>N</repeat>
-      <schedulerType>0</schedulerType>
-      <intervalSeconds>0</intervalSeconds>
-      <intervalMinutes>60</intervalMinutes>
+      <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>
-      <DayOfMonth>1</DayOfMonth>
       <parallel>N</parallel>
       <xloc>128</xloc>
       <yloc>80</yloc>
       <attributes_hac/>
     </action>
     <action>
-      <name>Run Clone Row Tests</name>
+      <name>Unique Rows Tests</name>
       <description/>
       <type>RunPipelineTests</type>
       <attributes/>
@@ -68,7 +69,7 @@ limitations under the License.
   <hops>
     <hop>
       <from>Start</from>
-      <to>Run Clone Row Tests</to>
+      <to>Unique Rows Tests</to>
       <enabled>Y</enabled>
       <evaluation>N</evaluation>
       <unconditional>Y</unconditional>
diff --git 
a/integration-tests/transforms/metadata/dataset/golden-unique-rows-hashset.json 
b/integration-tests/transforms/metadata/dataset/golden-unique-rows-hashset.json
new file mode 100644
index 0000000000..7b3447880f
--- /dev/null
+++ 
b/integration-tests/transforms/metadata/dataset/golden-unique-rows-hashset.json
@@ -0,0 +1,32 @@
+{
+  "base_filename": "golden-unique-rows-hashset.csv",
+  "name": "golden-unique-rows-hashset",
+  "description": "",
+  "dataset_fields": [
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "Field_A",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "Field_B",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 5,
+      "field_precision": 0,
+      "field_name": "Field_C",
+      "field_format": "####0;-####0"
+    }
+  ],
+  "folder_name": ""
+}
\ No newline at end of file
diff --git 
a/integration-tests/transforms/metadata/unit-test/0034-unique-rows-hashset 
UNIT.json 
b/integration-tests/transforms/metadata/unit-test/0034-unique-rows-hashset 
UNIT.json
new file mode 100644
index 0000000000..87e01b9de6
--- /dev/null
+++ b/integration-tests/transforms/metadata/unit-test/0034-unique-rows-hashset 
UNIT.json        
@@ -0,0 +1,34 @@
+{
+  "database_replacements": [],
+  "autoOpening": true,
+  "description": "",
+  "persist_filename": "",
+  "test_type": "UNIT_TEST",
+  "variableValues": [],
+  "basePath": "${HOP_UNIT_TESTS_FOLDER}",
+  "golden_data_sets": [
+    {
+      "field_mappings": [
+        {
+          "transform_field": "Field_A",
+          "data_set_field": "Field_A"
+        },
+        {
+          "transform_field": "Field_B",
+          "data_set_field": "Field_B"
+        },
+        {
+          "transform_field": "Field_C",
+          "data_set_field": "Field_C"
+        }
+      ],
+      "field_order": [],
+      "data_set_name": "golden-unique-rows-hashset",
+      "transform_name": "Result"
+    }
+  ],
+  "input_data_sets": [],
+  "name": "0034-unique-rows-hashset UNIT",
+  "trans_test_tweaks": [],
+  "pipeline_filename": "./0034-unique-rows-hashset.hpl"
+}
\ No newline at end of file
diff --git 
a/plugins/transforms/uniquerowsbyhashset/src/main/java/org/apache/hop/pipeline/transforms/uniquerowsbyhashset/UniqueRowsByHashSet.java
 
b/plugins/transforms/uniquerowsbyhashset/src/main/java/org/apache/hop/pipeline/transforms/uniquerowsbyhashset/UniqueRowsByHashSet.java
index 6b6492784b..cd7424dceb 100644
--- 
a/plugins/transforms/uniquerowsbyhashset/src/main/java/org/apache/hop/pipeline/transforms/uniquerowsbyhashset/UniqueRowsByHashSet.java
+++ 
b/plugins/transforms/uniquerowsbyhashset/src/main/java/org/apache/hop/pipeline/transforms/uniquerowsbyhashset/UniqueRowsByHashSet.java
@@ -61,19 +61,20 @@ public class UniqueRowsByHashSet
       data.outputRowMeta = getInputRowMeta().clone();
       meta.getFields(data.outputRowMeta, getTransformName(), null, null, this, 
metadataProvider);
 
-      data.storeValues = meta.getStoreValues();
+      data.storeValues = meta.isStoreValues();
 
       // ICache lookup of fields
-      data.fieldnrs = new int[meta.getCompareFields().length];
+      data.fieldnrs = new int[meta.getCompareFields().size()];
 
-      for (int i = 0; i < meta.getCompareFields().length; i++) {
-        data.fieldnrs[i] = 
getInputRowMeta().indexOfValue(meta.getCompareFields()[i]);
+      for (int i = 0; i < meta.getCompareFields().size(); i++) {
+        UniqueRowsByHashSetMeta.CompareField compareField = 
meta.getCompareFields().get(i);
+        data.fieldnrs[i] = 
getInputRowMeta().indexOfValue(compareField.getName());
         if (data.fieldnrs[i] < 0) {
           logError(
               BaseMessages.getString(
                   PKG,
                   "UniqueRowsByHashSet.Log.CouldNotFindFieldInRow",
-                  meta.getCompareFields()[i]));
+                  meta.getCompareFields().get(i)));
           setErrors(1);
           stopAll();
           return false;
@@ -81,8 +82,8 @@ public class UniqueRowsByHashSet
         if (data.sendDuplicateRows) {
           data.compareFields =
               data.compareFields == null
-                  ? meta.getCompareFields()[i]
-                  : data.compareFields + "," + meta.getCompareFields()[i];
+                  ? meta.getCompareFields().get(i).getName()
+                  : data.compareFields + "," + meta.getCompareFields().get(i);
         }
       }
       if (data.sendDuplicateRows && 
!Utils.isEmpty(meta.getErrorDescription())) {
diff --git 
a/plugins/transforms/uniquerowsbyhashset/src/main/java/org/apache/hop/pipeline/transforms/uniquerowsbyhashset/UniqueRowsByHashSetDialog.java
 
b/plugins/transforms/uniquerowsbyhashset/src/main/java/org/apache/hop/pipeline/transforms/uniquerowsbyhashset/UniqueRowsByHashSetDialog.java
index c060130c15..c9c7b8e1e7 100644
--- 
a/plugins/transforms/uniquerowsbyhashset/src/main/java/org/apache/hop/pipeline/transforms/uniquerowsbyhashset/UniqueRowsByHashSetDialog.java
+++ 
b/plugins/transforms/uniquerowsbyhashset/src/main/java/org/apache/hop/pipeline/transforms/uniquerowsbyhashset/UniqueRowsByHashSetDialog.java
@@ -27,6 +27,7 @@ import org.apache.hop.core.variables.IVariables;
 import org.apache.hop.i18n.BaseMessages;
 import org.apache.hop.pipeline.PipelineMeta;
 import org.apache.hop.pipeline.transform.TransformMeta;
+import 
org.apache.hop.pipeline.transforms.uniquerowsbyhashset.UniqueRowsByHashSetMeta.CompareField;
 import org.apache.hop.ui.core.ConstUi;
 import org.apache.hop.ui.core.PropsUi;
 import org.apache.hop.ui.core.dialog.BaseDialog;
@@ -184,7 +185,7 @@ public class UniqueRowsByHashSetDialog extends 
BaseTransformDialog {
     fdlFields.top = new FormAttachment(wSettings, margin);
     wlFields.setLayoutData(fdlFields);
 
-    final int FieldsRows = input.getCompareFields() == null ? 0 : 
input.getCompareFields().length;
+    final int FieldsRows = input.getCompareFields().size();
 
     colinf =
         new ColumnInfo[] {
@@ -257,16 +258,14 @@ public class UniqueRowsByHashSetDialog extends 
BaseTransformDialog {
 
   /** Copy information from the meta-data input to the dialog fields. */
   public void getData() {
-    wStoreValues.setSelection(input.getStoreValues());
+    wStoreValues.setSelection(input.isStoreValues());
     wRejectDuplicateRow.setSelection(input.isRejectDuplicateRow());
     if (input.getErrorDescription() != null) {
       wErrorDesc.setText(input.getErrorDescription());
     }
-    for (int i = 0; i < input.getCompareFields().length; i++) {
+    for (int i = 0; i < input.getCompareFields().size(); i++) {
       TableItem item = wFields.table.getItem(i);
-      if (input.getCompareFields()[i] != null) {
-        item.setText(1, input.getCompareFields()[i]);
-      }
+      item.setText(1, Const.NVL(input.getCompareFields().get(i).getName(), 
""));
     }
     wFields.setRowNums();
     wFields.optWidth(true);
@@ -284,12 +283,10 @@ public class UniqueRowsByHashSetDialog extends 
BaseTransformDialog {
     }
 
     int nrFields = wFields.nrNonEmpty();
-    input.allocate(nrFields);
-
-    for (int i = 0; i < nrFields; i++) {
-      TableItem item = wFields.getNonEmpty(i);
 
-      input.getCompareFields()[i] = item.getText(1);
+    input.getCompareFields().clear();
+    for (TableItem item : wFields.getNonEmptyItems()) {
+      input.getCompareFields().add(new CompareField(item.getText(1)));
     }
 
     transformName = wTransformName.getText(); // return value
diff --git 
a/plugins/transforms/uniquerowsbyhashset/src/main/java/org/apache/hop/pipeline/transforms/uniquerowsbyhashset/UniqueRowsByHashSetMeta.java
 
b/plugins/transforms/uniquerowsbyhashset/src/main/java/org/apache/hop/pipeline/transforms/uniquerowsbyhashset/UniqueRowsByHashSetMeta.java
index b6d56927ec..4fa440610b 100644
--- 
a/plugins/transforms/uniquerowsbyhashset/src/main/java/org/apache/hop/pipeline/transforms/uniquerowsbyhashset/UniqueRowsByHashSetMeta.java
+++ 
b/plugins/transforms/uniquerowsbyhashset/src/main/java/org/apache/hop/pipeline/transforms/uniquerowsbyhashset/UniqueRowsByHashSetMeta.java
@@ -17,21 +17,21 @@
 
 package org.apache.hop.pipeline.transforms.uniquerowsbyhashset;
 
+import java.util.ArrayList;
 import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
 import org.apache.hop.core.CheckResult;
 import org.apache.hop.core.ICheckResult;
 import org.apache.hop.core.annotations.Transform;
-import org.apache.hop.core.exception.HopTransformException;
-import org.apache.hop.core.exception.HopXmlException;
 import org.apache.hop.core.row.IRowMeta;
 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.transform.BaseTransformMeta;
 import org.apache.hop.pipeline.transform.TransformMeta;
-import org.w3c.dom.Node;
 
 @Transform(
     id = "UniqueRowsByHashSet",
@@ -41,169 +41,84 @@ import org.w3c.dom.Node;
     categoryDescription = 
"i18n:org.apache.hop.pipeline.transform:BaseTransform.Category.Transform",
     keywords = "i18n::UniqueRowsByHashSetMeta.keyword",
     documentationUrl = "/pipeline/transforms/uniquerowsbyhashset.html")
+@Getter
+@Setter
 public class UniqueRowsByHashSetMeta
     extends BaseTransformMeta<UniqueRowsByHashSet, UniqueRowsByHashSetData> {
   private static final Class<?> PKG = UniqueRowsByHashSetMeta.class;
-  public static final String CONST_FIELD = "field";
-  public static final String CONST_SPACES = "      ";
+
+  @Getter
+  @Setter
+  public static class CompareField {
+    @HopMetadataProperty(
+        key = "name",
+        injectionKey = "COMPARE_FIELD_NAME",
+        injectionKeyDescription = 
"UniqueRowsByHashSetMeta.Injection.COMPARE_FIELD_NAME")
+    private String name;
+
+    public CompareField() {}
+
+    public CompareField(CompareField f) {
+      this();
+      this.name = f.name;
+    }
+
+    public CompareField(String name) {
+      this.name = name;
+    }
+  }
 
   /**
    * Whether to compare strictly by hash value or to store the row values for 
strict equality
    * checking
    */
+  @HopMetadataProperty(
+      key = "store_values",
+      injectionKey = "STORE_VALUES",
+      injectionKeyDescription = 
"UniqueRowsByHashSetMeta.Injection.STORE_VALUES")
   private boolean storeValues;
 
   /** The fields to compare for duplicates, null means all */
-  private String[] compareFields;
-
+  @HopMetadataProperty(
+      key = "field",
+      groupKey = "fields",
+      injectionKey = "COMPARE_FIELD",
+      injectionGroupKey = "COMPARE_FIELDS",
+      injectionKeyDescription = 
"UniqueRowsByHashSetMeta.Injection.COMPARE_FIELD",
+      injectionGroupDescription = 
"UniqueRowsByHashSetMeta.Injection.COMPARE_FIELDS")
+  private List<CompareField> compareFields;
+
+  @HopMetadataProperty(
+      key = "reject_duplicate_row",
+      injectionKey = "REJECT_DUPLICATE_ROW",
+      injectionKeyDescription = 
"UniqueRowsByHashSetMeta.Injection.REJECT_DUPLICATE_ROW")
   private boolean rejectDuplicateRow;
+
+  @HopMetadataProperty(
+      key = "error_description",
+      injectionKey = "ERROR_DESCRIPTION",
+      injectionKeyDescription = 
"UniqueRowsByHashSetMeta.Injection.ERROR_DESCRIPTION")
   private String errorDescription;
 
   public UniqueRowsByHashSetMeta() {
-    super(); // allocate BaseTransformMeta
-  }
-
-  /**
-   * @param compareField The compareField to set.
-   */
-  public void setCompareFields(String[] compareField) {
-    this.compareFields = compareField;
-  }
-
-  public boolean getStoreValues() {
-    return storeValues;
-  }
-
-  public void setStoreValues(boolean storeValues) {
-    this.storeValues = storeValues;
-  }
-
-  /**
-   * @return Returns the compareField.
-   */
-  public String[] getCompareFields() {
-    return compareFields;
-  }
-
-  public void allocate(int nrFields) {
-    compareFields = new String[nrFields];
-  }
-
-  /**
-   * @param rejectDuplicateRow The rejectDuplicateRow to set.
-   */
-  public void setRejectDuplicateRow(boolean rejectDuplicateRow) {
-    this.rejectDuplicateRow = rejectDuplicateRow;
-  }
-
-  /**
-   * @return Returns the rejectDuplicateRow.
-   */
-  public boolean isRejectDuplicateRow() {
-    return rejectDuplicateRow;
-  }
-
-  /**
-   * @param errorDescription The errorDescription to set.
-   */
-  public void setErrorDescription(String errorDescription) {
-    this.errorDescription = errorDescription;
-  }
-
-  /**
-   * @return Returns the errorDescription.
-   */
-  public String getErrorDescription() {
-    return errorDescription;
-  }
-
-  @Override
-  public void loadXml(Node transformNode, IHopMetadataProvider 
metadataProvider)
-      throws HopXmlException {
-    readData(transformNode);
-  }
-
-  @Override
-  public Object clone() {
-    UniqueRowsByHashSetMeta retval = (UniqueRowsByHashSetMeta) super.clone();
-
-    int nrFields = compareFields.length;
-
-    retval.allocate(nrFields);
-
-    System.arraycopy(compareFields, 0, retval.compareFields, 0, nrFields);
-    return retval;
-  }
-
-  private void readData(Node transformNode) throws HopXmlException {
-    try {
-      storeValues = "Y".equalsIgnoreCase(XmlHandler.getTagValue(transformNode, 
"store_values"));
-      rejectDuplicateRow =
-          "Y".equalsIgnoreCase(XmlHandler.getTagValue(transformNode, 
"reject_duplicate_row"));
-      errorDescription = XmlHandler.getTagValue(transformNode, 
"error_description");
-
-      Node fields = XmlHandler.getSubNode(transformNode, "fields");
-      int nrFields = XmlHandler.countNodes(fields, CONST_FIELD);
-
-      allocate(nrFields);
-
-      for (int i = 0; i < nrFields; i++) {
-        Node fnode = XmlHandler.getSubNodeByNr(fields, CONST_FIELD, i);
-
-        compareFields[i] = XmlHandler.getTagValue(fnode, "name");
-      }
-
-    } catch (Exception e) {
-      throw new HopXmlException(
-          BaseMessages.getString(
-              PKG, 
"UniqueRowsByHashSetMeta.Exception.UnableToLoadTransformMetaFromXML"),
-          e);
-    }
-  }
-
-  @Override
-  public void setDefault() {
+    super();
+    compareFields = new ArrayList<>();
     rejectDuplicateRow = false;
     errorDescription = null;
     storeValues = true;
-    int nrFields = 0;
-
-    allocate(nrFields);
-
-    for (int i = 0; i < nrFields; i++) {
-      compareFields[i] = CONST_FIELD + i;
-    }
   }
 
-  @Override
-  public void getFields(
-      IRowMeta row,
-      String name,
-      IRowMeta[] info,
-      TransformMeta nextTransform,
-      IVariables variables,
-      IHopMetadataProvider metadataProvider)
-      throws HopTransformException {
-    // Do Nothing
+  public UniqueRowsByHashSetMeta(UniqueRowsByHashSetMeta m) {
+    this();
+    this.storeValues = m.storeValues;
+    this.rejectDuplicateRow = m.rejectDuplicateRow;
+    this.errorDescription = m.errorDescription;
+    m.compareFields.forEach(f -> this.compareFields.add(new CompareField(f)));
   }
 
   @Override
-  public String getXml() {
-    StringBuilder retval = new StringBuilder();
-
-    retval.append(CONST_SPACES + XmlHandler.addTagValue("store_values", 
storeValues));
-    retval.append(
-        CONST_SPACES + XmlHandler.addTagValue("reject_duplicate_row", 
rejectDuplicateRow));
-    retval.append(CONST_SPACES + XmlHandler.addTagValue("error_description", 
errorDescription));
-    retval.append("    <fields>");
-    for (String compareField : compareFields) {
-      retval.append("      <field>");
-      retval.append("        " + XmlHandler.addTagValue("name", compareField));
-      retval.append("        </field>");
-    }
-    retval.append("      </fields>");
-
-    return retval.toString();
+  public Object clone() {
+    return new UniqueRowsByHashSetMeta(this);
   }
 
   @Override
diff --git 
a/plugins/transforms/uniquerowsbyhashset/src/main/resources/org/apache/hop/pipeline/transforms/uniquerowsbyhashset/messages/messages_en_US.properties
 
b/plugins/transforms/uniquerowsbyhashset/src/main/resources/org/apache/hop/pipeline/transforms/uniquerowsbyhashset/messages/messages_en_US.properties
index 329a2f12bb..3d5cfcd8c0 100644
--- 
a/plugins/transforms/uniquerowsbyhashset/src/main/resources/org/apache/hop/pipeline/transforms/uniquerowsbyhashset/messages/messages_en_US.properties
+++ 
b/plugins/transforms/uniquerowsbyhashset/src/main/resources/org/apache/hop/pipeline/transforms/uniquerowsbyhashset/messages/messages_en_US.properties
@@ -36,3 +36,9 @@ 
UniqueRowsByHashSetMeta.CheckResult.NoInputReceivedFromOtherTransforms=No input
 
UniqueRowsByHashSetMeta.CheckResult.TransformReceivingInfoFromOtherTransforms=Transform
 is receiving info from other transforms.
 UniqueRowsByHashSetMeta.Exception.UnableToLoadTransformMetaFromXML=Unable to 
load transform info from XML
 UniqueRowsByHashSetMeta.keyword=unique,rows,hashset
+UniqueRowsByHashSetMeta.Injection.STORE_VALUES=Store values in memory or only 
hash?
+UniqueRowsByHashSetMeta.Injection.COMPARE_FIELDS=The fields to compare for 
uniqueness
+UniqueRowsByHashSetMeta.Injection.COMPARE_FIELD=A field to compare for 
uniqueness
+UniqueRowsByHashSetMeta.Injection.REJECT_DUPLICATE_ROW=Reject duplicate rows?
+UniqueRowsByHashSetMeta.Injection.ERROR_DESCRIPTION=Rejected error description
+UniqueRowsByHashSetMeta.Injection.COMPARE_FIELD_NAME=Field name to compare for 
uniqueness
\ No newline at end of file
diff --git 
a/plugins/transforms/uniquerowsbyhashset/src/test/java/org/apache/hop/pipeline/transforms/uniquerowsbyhashset/UniqueRowsByHashSetMetaTest.java
 
b/plugins/transforms/uniquerowsbyhashset/src/test/java/org/apache/hop/pipeline/transforms/uniquerowsbyhashset/UniqueRowsByHashSetMetaTest.java
index 3be29962f7..6b3413c870 100644
--- 
a/plugins/transforms/uniquerowsbyhashset/src/test/java/org/apache/hop/pipeline/transforms/uniquerowsbyhashset/UniqueRowsByHashSetMetaTest.java
+++ 
b/plugins/transforms/uniquerowsbyhashset/src/test/java/org/apache/hop/pipeline/transforms/uniquerowsbyhashset/UniqueRowsByHashSetMetaTest.java
@@ -17,57 +17,56 @@
 
 package org.apache.hop.pipeline.transforms.uniquerowsbyhashset;
 
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.apache.hop.core.exception.HopException;
-import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
-import org.apache.hop.pipeline.transforms.loadsave.LoadSaveTester;
-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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Objects;
+import org.apache.hop.core.xml.XmlHandler;
+import org.apache.hop.metadata.serializer.memory.MemoryMetadataProvider;
+import org.apache.hop.metadata.serializer.xml.XmlMetadataUtil;
+import org.apache.hop.pipeline.transform.TransformMeta;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.RegisterExtension;
 
 class UniqueRowsByHashSetMetaTest {
-  @RegisterExtension
-  static RestoreHopEngineEnvironmentExtension env = new 
RestoreHopEngineEnvironmentExtension();
-
   @Test
-  void testRoundTrip() throws HopException {
-    List<String> attributes =
-        Arrays.asList("store_values", "reject_duplicate_row", 
"error_description", "name");
-
-    Map<String, String> getterMap = new HashMap<>();
-    getterMap.put("store_values", "getStoreValues");
-    getterMap.put("reject_duplicate_row", "isRejectDuplicateRow");
-    getterMap.put("error_description", "getErrorDescription");
-    getterMap.put("name", "getCompareFields");
-
-    Map<String, String> setterMap = new HashMap<>();
-    setterMap.put("store_values", "setStoreValues");
-    setterMap.put("reject_duplicate_row", "setRejectDuplicateRow");
-    setterMap.put("error_description", "setErrorDescription");
-    setterMap.put("name", "setCompareFields");
+  void testLoadSave() throws Exception {
+    Path path = 
Paths.get(Objects.requireNonNull(getClass().getResource("/transform.xml")).toURI());
+    String xml = Files.readString(path);
+    UniqueRowsByHashSetMeta meta = new UniqueRowsByHashSetMeta();
+    XmlMetadataUtil.deSerializeFromXml(
+        XmlHandler.loadXmlString(xml, TransformMeta.XML_TAG),
+        UniqueRowsByHashSetMeta.class,
+        meta,
+        new MemoryMetadataProvider());
 
-    Map<String, IFieldLoadSaveValidator<?>> fieldLoadSaveValidatorAttributeMap 
= new HashMap<>();
+    validate(meta);
 
-    // Arrays need to be consistent length
-    IFieldLoadSaveValidator<String[]> stringArrayLoadSaveValidator =
-        new ArrayLoadSaveValidator<>(new StringLoadSaveValidator(), 25);
-
-    fieldLoadSaveValidatorAttributeMap.put("name", 
stringArrayLoadSaveValidator);
-
-    LoadSaveTester loadSaveTester =
-        new LoadSaveTester(
-            UniqueRowsByHashSetMeta.class,
-            attributes,
-            getterMap,
-            setterMap,
-            fieldLoadSaveValidatorAttributeMap,
-            new HashMap<>());
+    // Do a round trip:
+    //
+    String xmlCopy =
+        XmlHandler.openTag(TransformMeta.XML_TAG)
+            + XmlMetadataUtil.serializeObjectToXml(meta)
+            + XmlHandler.closeTag(TransformMeta.XML_TAG);
+    UniqueRowsByHashSetMeta metaCopy = new UniqueRowsByHashSetMeta();
+    XmlMetadataUtil.deSerializeFromXml(
+        XmlHandler.loadXmlString(xmlCopy, TransformMeta.XML_TAG),
+        UniqueRowsByHashSetMeta.class,
+        metaCopy,
+        new MemoryMetadataProvider());
+    validate(metaCopy);
+  }
 
-    loadSaveTester.testSerialization();
+  private static void validate(UniqueRowsByHashSetMeta meta) {
+    assertNotNull(meta.getCompareFields());
+    assertTrue(meta.isStoreValues());
+    assertTrue(meta.isRejectDuplicateRow());
+    assertEquals("Duplicate row detected", meta.getErrorDescription());
+    assertEquals("compareField1", meta.getCompareFields().get(0).getName());
+    assertEquals("compareField2", meta.getCompareFields().get(1).getName());
+    assertEquals("compareField3", meta.getCompareFields().get(2).getName());
   }
 }
diff --git 
a/plugins/transforms/uniquerowsbyhashset/src/test/resources/transform.xml 
b/plugins/transforms/uniquerowsbyhashset/src/test/resources/transform.xml
new file mode 100644
index 0000000000..e81c2962ea
--- /dev/null
+++ b/plugins/transforms/uniquerowsbyhashset/src/test/resources/transform.xml
@@ -0,0 +1,33 @@
+<?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>
+    <store_values>Y</store_values>
+    <reject_duplicate_row>Y</reject_duplicate_row>
+    <error_description>Duplicate row detected</error_description>
+    <fields>
+        <field>
+            <name>compareField1</name>
+        </field>
+        <field>
+            <name>compareField2</name>
+        </field>
+        <field>
+            <name>compareField3</name>
+        </field>
+    </fields>
+</transform>

Reply via email to