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 75a94ab694 Add UUID plugin value type + Relational/Mongo support 
(issue #5624) (#5702)
75a94ab694 is described below

commit 75a94ab69449b8a409ad2beb2f51308ccf298ece
Author: Matteo <[email protected]>
AuthorDate: Mon Sep 29 11:45:07 2025 +0200

    Add UUID plugin value type + Relational/Mongo support (issue #5624) (#5702)
    
    * Add UUID plugin value type + Relational/Mongo support (issue 5624)
    
    * Changed UUID valuetype icon to NULL
    
    * Fix uuid sort in table view + add uuid integration test
    
    * Add UUID integration tests for Mongo (issue #5624)
    
    * spotless + junit5
    
    ---------
    
    Co-authored-by: Matteo Ladislai <[email protected]>
    Co-authored-by: Hans Van Akelyen <[email protected]>
---
 assemblies/plugins/pom.xml                         |   6 +
 .../apache/hop/core/database/BaseDatabaseMeta.java |  24 ++
 .../java/org/apache/hop/core/row/IValueMeta.java   |   8 +-
 .../apache/hop/core/row/value/ValueMetaBase.java   |   7 +-
 .../hop/core/database/BaseDatabaseMetaTest.java    |  51 ++++
 .../integration-tests-database.yaml                |   5 +-
 docker/integration-tests/unit-test-db-startup.sql  |  20 ++
 integration-tests/database/0032-postgres-uuid.hpl  | 215 ++++++++++++++
 .../database/main-0032-postgres-uuid.hwf           | 159 ++++++++++
 .../mongo/tests/mongo-uuid/main-mongo-uuid.hwf     | 163 ++++++++++
 .../mongo/tests/mongo-uuid/mongo-delete-uuid.hpl   | 153 ++++++++++
 .../mongo/tests/mongo-uuid/mongo-insert-uuid.hpl   | 183 ++++++++++++
 .../mongo/tests/mongo-uuid/mongo-read-uuid.hpl     | 295 ++++++++++++++++++
 .../main/java/org/apache/hop/mongo/MongoProp.java  |  14 +-
 .../apache/hop/mongo/wrapper/field/MongoField.java |  13 +
 .../field/MongodbInputDiscoverFieldsImpl.java      |   4 +
 .../mongodbdelete/MongoDbDeleteData.java           |  13 +
 .../mongodbdelete/MongoDbDeleteDialog.java         |  11 +-
 .../mongodboutput/MongoDbOutputData.java           |  13 +
 .../mongodboutput/MongoDbOutputDialog.java         |  11 +-
 .../apache/hop/mongo/wrapper/MongoFieldTest.java   |   5 +
 .../transforms/pgbulkloader/PGBulkLoader.java      |  18 +-
 plugins/valuetypes/pom.xml                         |   4 +-
 plugins/valuetypes/{ => uuid}/pom.xml              |  10 +-
 plugins/valuetypes/uuid/src/assembly/assembly.xml  |  43 +++
 .../java/org/apache/hop/uuid/ValueMetaUuid.java    | 330 +++++++++++++++++++++
 .../src/main/resources/version.xml}                |  14 +-
 .../org/apache/hop/uuid/ValueMetaUuidTest.java     | 216 ++++++++++++++
 28 files changed, 1980 insertions(+), 28 deletions(-)

diff --git a/assemblies/plugins/pom.xml b/assemblies/plugins/pom.xml
index b2c5b5192e..4baeeb6bb2 100644
--- a/assemblies/plugins/pom.xml
+++ b/assemblies/plugins/pom.xml
@@ -1681,6 +1681,12 @@
             <version>${project.version}</version>
             <type>zip</type>
         </dependency>
+        <dependency>
+            <groupId>org.apache.hop</groupId>
+            <artifactId>hop-valuetypes-uuid</artifactId>
+            <version>${project.version}</version>
+            <type>zip</type>
+        </dependency>
 
     </dependencies>
 </project>
diff --git 
a/core/src/main/java/org/apache/hop/core/database/BaseDatabaseMeta.java 
b/core/src/main/java/org/apache/hop/core/database/BaseDatabaseMeta.java
index e193334000..cd9495b42b 100644
--- a/core/src/main/java/org/apache/hop/core/database/BaseDatabaseMeta.java
+++ b/core/src/main/java/org/apache/hop/core/database/BaseDatabaseMeta.java
@@ -33,10 +33,12 @@ import java.util.TreeSet;
 import java.util.stream.Stream;
 import org.apache.hop.core.Const;
 import org.apache.hop.core.exception.HopDatabaseException;
+import org.apache.hop.core.exception.HopPluginException;
 import org.apache.hop.core.exception.HopValueException;
 import org.apache.hop.core.gui.plugin.GuiElementType;
 import org.apache.hop.core.gui.plugin.GuiWidgetElement;
 import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.value.ValueMetaFactory;
 import org.apache.hop.core.util.StringUtil;
 import org.apache.hop.core.util.Utils;
 import org.apache.hop.core.variables.IVariables;
@@ -1913,6 +1915,28 @@ public abstract class BaseDatabaseMeta implements 
Cloneable, IDatabase {
   @Override
   public IValueMeta customizeValueFromSqlType(
       IValueMeta v, java.sql.ResultSetMetaData rm, int index) throws 
SQLException {
+    if (v == null || rm == null) {
+      return null;
+    }
+
+    String typeName = rm.getColumnTypeName(index);
+    // Most dbs expose uuid as "UUID", sql server (native) as 
"UNIQUEIDENTIFIER"
+    if ("uuid".equalsIgnoreCase(typeName) || 
"uniqueidentifier".equalsIgnoreCase(typeName)) {
+      try {
+
+        int uuidTypeId = ValueMetaFactory.getIdForValueMeta("UUID");
+
+        // Keep any existing metadata
+        IValueMeta u = ValueMetaFactory.cloneValueMeta(v, uuidTypeId);
+
+        u.setLength(-1);
+        u.setPrecision(-1);
+
+        return u;
+      } catch (HopPluginException ignore) {
+        // UUID plugin not present
+      }
+    }
     return null;
   }
 
diff --git a/core/src/main/java/org/apache/hop/core/row/IValueMeta.java 
b/core/src/main/java/org/apache/hop/core/row/IValueMeta.java
index 0fdb22b080..3544bc1b35 100644
--- a/core/src/main/java/org/apache/hop/core/row/IValueMeta.java
+++ b/core/src/main/java/org/apache/hop/core/row/IValueMeta.java
@@ -344,7 +344,13 @@ public interface IValueMeta extends Cloneable {
     try {
       return typeCodes[type];
     } catch (Exception e) {
-      return "unknown/illegal";
+      // consult plugin registry
+      try {
+        String pluginType = ValueMetaFactory.getValueMetaName(type);
+        return pluginType.equals("-") ? "unknown/illegal" : pluginType;
+      } catch (Exception ignore) {
+        return "unknown/illegal";
+      }
     }
   }
 
diff --git 
a/core/src/main/java/org/apache/hop/core/row/value/ValueMetaBase.java 
b/core/src/main/java/org/apache/hop/core/row/value/ValueMetaBase.java
index 111829332a..6a0f0bf594 100644
--- a/core/src/main/java/org/apache/hop/core/row/value/ValueMetaBase.java
+++ b/core/src/main/java/org/apache/hop/core/row/value/ValueMetaBase.java
@@ -4638,7 +4638,12 @@ public class ValueMetaBase implements IValueMeta {
       case TYPE_JSON:
         return getJson(data);
       default:
-        throw new HopValueException(this + CONST_CANNOT_CONVERT + 
conversionMetadata.getType());
+        // Generic plugin-aware path
+        try {
+          return conversionMetadata.convertData(this, data);
+        } catch (Exception e) {
+          throw new HopValueException(this + CONST_CANNOT_CONVERT + 
conversionMetadata.getType());
+        }
     }
   }
 
diff --git 
a/core/src/test/java/org/apache/hop/core/database/BaseDatabaseMetaTest.java 
b/core/src/test/java/org/apache/hop/core/database/BaseDatabaseMetaTest.java
index afc1c84f39..21d0ffd368 100644
--- a/core/src/test/java/org/apache/hop/core/database/BaseDatabaseMetaTest.java
+++ b/core/src/test/java/org/apache/hop/core/database/BaseDatabaseMetaTest.java
@@ -24,10 +24,14 @@ import static org.junit.Assert.assertTrue;
 
 import java.sql.DatabaseMetaData;
 import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
 import org.apache.hop.core.HopClientEnvironment;
+import org.apache.hop.core.exception.HopPluginException;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.value.ValueMetaFactory;
 import org.apache.hop.core.row.value.ValueMetaString;
 import org.apache.hop.core.variables.Variables;
 import org.apache.hop.junit.rules.RestoreHopEnvironment;
@@ -280,4 +284,51 @@ public class BaseDatabaseMetaTest {
                 });
     Mockito.when(db.getDatabaseMeta()).thenReturn(dm);
   }
+
+  /**
+   * Build a String meta that mimics what getValueFromSqlType might have 
produced before
+   * customization: name set, BINARY_STRING storage with String storage 
metadata, etc.
+   */
+  private IValueMeta buildPreMeta() throws HopPluginException {
+    ValueMetaString v = new ValueMetaString("id");
+    v.setStorageType(IValueMeta.STORAGE_TYPE_BINARY_STRING);
+    ValueMetaString storage = new ValueMetaString("id");
+    storage.setStringEncoding("UTF-8");
+    v.setStorageMetadata(storage);
+    v.setLength(36);
+    v.setPrecision(0);
+    v.setOriginalColumnType(java.sql.Types.OTHER);
+    v.setOriginalColumnTypeName("uuid");
+    return v;
+  }
+
+  @Test
+  public void testCustomizeValueFromSqlTypeUuid() throws Exception {
+    int uuidTypeId;
+    try {
+      uuidTypeId = ValueMetaFactory.getIdForValueMeta("UUID");
+    } catch (Exception ignore) {
+      // UUID plugin not present:, skip the rest
+      return;
+    }
+
+    ResultSetMetaData rm = Mockito.mock(ResultSetMetaData.class);
+    Mockito.when(rm.getColumnTypeName(1)).thenReturn("UUID");
+
+    IValueMeta pre = buildPreMeta();
+    IValueMeta out = nativeMeta.customizeValueFromSqlType(pre, rm, 1);
+
+    assertEquals(uuidTypeId, out.getType());
+    assertEquals("id", out.getName());
+    // length/precision reset
+    assertEquals(-1, out.getLength());
+    assertEquals(-1, out.getPrecision());
+    // storage type preserved
+    assertEquals(pre.getStorageType(), out.getStorageType());
+    assertTrue(out.getStorageMetadata() != null);
+    assertTrue(out.getStorageMetadata().isString());
+    // original JDBC metadata preserved by clone
+    assertEquals(pre.getOriginalColumnType(), out.getOriginalColumnType());
+    assertEquals(pre.getOriginalColumnTypeName(), 
out.getOriginalColumnTypeName());
+  }
 }
diff --git a/docker/integration-tests/integration-tests-database.yaml 
b/docker/integration-tests/integration-tests-database.yaml
index 8637649dc6..08530955ed 100644
--- a/docker/integration-tests/integration-tests-database.yaml
+++ b/docker/integration-tests/integration-tests-database.yaml
@@ -42,6 +42,9 @@ services:
       timeout: 10s
       retries: 6
       start_period: 120s
+    volumes:
+      # init scripts run automatically on first cluster init (read only)
+      - 
./unit-test-db-startup.sql:/docker-entrypoint-initdb.d/01-extensions.sql:ro
 
   mssql:
     image: mcr.microsoft.com/mssql/server:2022-latest
@@ -60,4 +63,4 @@ services:
     environment:
       - MYSQL_ROOT_PASSWORD=my-secret-pw
     volumes:
-      - ./resource/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
\ No newline at end of file
+      - ./resource/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
diff --git a/docker/integration-tests/unit-test-db-startup.sql 
b/docker/integration-tests/unit-test-db-startup.sql
new file mode 100644
index 0000000000..d31639638f
--- /dev/null
+++ b/docker/integration-tests/unit-test-db-startup.sql
@@ -0,0 +1,20 @@
+/**
+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.
+**/
+
+CREATE EXTENSION IF NOT EXISTS pgcrypto;
diff --git a/integration-tests/database/0032-postgres-uuid.hpl 
b/integration-tests/database/0032-postgres-uuid.hpl
new file mode 100644
index 0000000000..6fb1e60a91
--- /dev/null
+++ b/integration-tests/database/0032-postgres-uuid.hpl
@@ -0,0 +1,215 @@
+<?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>0032-postgres-uuid</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>2023/05/09 16:12:45.934</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2023/05/09 16:12:45.934</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+    <hop>
+      <from>Table input CHAR</from>
+      <to>String to Uuid</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Table input UUID</from>
+      <to>Join rows (cartesian product)</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>String to Uuid</from>
+      <to>Join rows (cartesian product)</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Join rows (cartesian product)</from>
+      <to>Table output</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>Join rows (cartesian product)</name>
+    <type>JoinRows</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <cache_size>500</cache_size>
+    <compare>
+      <condition>
+        <conditions>
+</conditions>
+        <function>=</function>
+        <leftvalue>id_uuid</leftvalue>
+        <negated>N</negated>
+        <operator>-</operator>
+        <rightvalue>id_char</rightvalue>
+      </condition>
+    </compare>
+    <directory>%%java.io.tmpdir%%</directory>
+    <prefix>out</prefix>
+    <attributes/>
+    <GUI>
+      <xloc>368</xloc>
+      <yloc>176</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>String to Uuid</name>
+    <type>SelectValues</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <fields>
+      <field>
+        <length>-2</length>
+        <name>id_char</name>
+        <precision>-2</precision>
+      </field>
+      <meta>
+        <date_format_lenient>N</date_format_lenient>
+        <length>-2</length>
+        <lenient_string_to_number>N</lenient_string_to_number>
+        <name>id_char</name>
+        <precision>-2</precision>
+        <rename>id_char</rename>
+        <roundingType>half_even</roundingType>
+        <storage_type/>
+        <type>UUID</type>
+      </meta>
+      <select_unspecified>N</select_unspecified>
+    </fields>
+    <attributes/>
+    <GUI>
+      <xloc>368</xloc>
+      <yloc>304</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Table input CHAR</name>
+    <type>TableInput</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <connection>unit-test-db</connection>
+    <execute_each_row>N</execute_each_row>
+    <limit>0</limit>
+    <sql>SELECT id::CHAR(36) AS id_char
+FROM uuid_random;</sql>
+    <variables_active>N</variables_active>
+    <attributes/>
+    <GUI>
+      <xloc>144</xloc>
+      <yloc>304</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Table input UUID</name>
+    <type>TableInput</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <connection>unit-test-db</connection>
+    <execute_each_row>N</execute_each_row>
+    <limit>0</limit>
+    <sql>SELECT id AS id_uuid
+FROM uuid_random;</sql>
+    <variables_active>N</variables_active>
+    <attributes/>
+    <GUI>
+      <xloc>144</xloc>
+      <yloc>176</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Table output</name>
+    <type>TableOutput</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <commit>1000</commit>
+    <connection>unit-test-db</connection>
+    <fields>
+      <field>
+        <column_name>id</column_name>
+        <stream_name>id_char</stream_name>
+      </field>
+    </fields>
+    <ignore_errors>N</ignore_errors>
+    <only_when_have_rows>N</only_when_have_rows>
+    <partitioning_daily>N</partitioning_daily>
+    <partitioning_enabled>N</partitioning_enabled>
+    <partitioning_monthly>Y</partitioning_monthly>
+    <return_keys>N</return_keys>
+    <specify_fields>Y</specify_fields>
+    <table>uuid_insert</table>
+    <tablename_in_field>N</tablename_in_field>
+    <tablename_in_table>Y</tablename_in_table>
+    <truncate>N</truncate>
+    <use_batch>Y</use_batch>
+    <attributes/>
+    <GUI>
+      <xloc>560</xloc>
+      <yloc>176</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git a/integration-tests/database/main-0032-postgres-uuid.hwf 
b/integration-tests/database/main-0032-postgres-uuid.hwf
new file mode 100644
index 0000000000..c475c0584b
--- /dev/null
+++ b/integration-tests/database/main-0032-postgres-uuid.hwf
@@ -0,0 +1,159 @@
+<?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-0032-postgres-uuid</name>
+  <name_sync_with_filename>Y</name_sync_with_filename>
+  <description/>
+  <extended_description/>
+  <workflow_version/>
+  <created_user>-</created_user>
+  <created_date>2022/12/02 13:51:33.435</created_date>
+  <modified_user>-</modified_user>
+  <modified_date>2022/12/02 13:51:33.435</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>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Init table</name>
+      <description/>
+      <type>SQL</type>
+      <attributes/>
+      <connection>unit-test-db</connection>
+      <sendOneStatement>N</sendOneStatement>
+      <sql>DROP TABLE IF EXISTS uuid_random;
+
+CREATE TABLE uuid_random
+(
+    id   uuid PRIMARY KEY DEFAULT gen_random_uuid(),
+    amount INT
+);
+
+INSERT INTO uuid_random (amount)
+VALUES (1),(1),(1),(1),(1);
+
+
+
+DROP TABLE IF EXISTS uuid_insert;
+
+CREATE TABLE uuid_insert
+(
+    id   uuid PRIMARY KEY
+);
+</sql>
+      <sqlfromfile>N</sqlfromfile>
+      <useVariableSubstitution>N</useVariableSubstitution>
+      <parallel>N</parallel>
+      <xloc>224</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>0032-postgres-uuid.hpl</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}/0032-postgres-uuid.hpl</filename>
+      <logext/>
+      <logfile/>
+      <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>384</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Evaluate rows number in a table</name>
+      <description/>
+      <type>EVAL_TABLE_CONTENT</type>
+      <attributes/>
+      <add_rows_result>N</add_rows_result>
+      <clear_result_rows>Y</clear_result_rows>
+      <connection>unit-test-db</connection>
+      <is_custom_sql>N</is_custom_sql>
+      <is_usevars>N</is_usevars>
+      <limit>5</limit>
+      <success_condition>rows_count_equal</success_condition>
+      <tablename>uuid_insert</tablename>
+      <parallel>N</parallel>
+      <xloc>592</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+  </actions>
+  <hops>
+    <hop>
+      <from>Start</from>
+      <to>Init table</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>Y</unconditional>
+    </hop>
+    <hop>
+      <from>Init table</from>
+      <to>0032-postgres-uuid.hpl</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>0032-postgres-uuid.hpl</from>
+      <to>Evaluate rows number in a table</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+  </hops>
+  <notepads>
+  </notepads>
+  <attributes/>
+</workflow>
diff --git a/integration-tests/mongo/tests/mongo-uuid/main-mongo-uuid.hwf 
b/integration-tests/mongo/tests/mongo-uuid/main-mongo-uuid.hwf
new file mode 100644
index 0000000000..e85a37836c
--- /dev/null
+++ b/integration-tests/mongo/tests/mongo-uuid/main-mongo-uuid.hwf
@@ -0,0 +1,163 @@
+<?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-mongo-uuid</name>
+  <name_sync_with_filename>Y</name_sync_with_filename>
+  <description/>
+  <extended_description/>
+  <workflow_version/>
+  <created_user>-</created_user>
+  <created_date>2025/09/27 16:04:41.874</created_date>
+  <modified_user>-</modified_user>
+  <modified_date>2025/09/27 16:04:41.874</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>50</xloc>
+      <yloc>50</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>mongo-insert-uuid.hpl</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}/tests/mongo-uuid/mongo-insert-uuid.hpl</filename>
+      <logext/>
+      <logfile/>
+      <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>240</xloc>
+      <yloc>48</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>mongo-delete-uuid.hpl</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}/tests/mongo-uuid/mongo-delete-uuid.hpl</filename>
+      <logext/>
+      <logfile/>
+      <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>416</xloc>
+      <yloc>48</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>mongo-read-uuid.hpl</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}/tests/mongo-uuid/mongo-read-uuid.hpl</filename>
+      <logext/>
+      <logfile/>
+      <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>608</xloc>
+      <yloc>48</yloc>
+      <attributes_hac/>
+    </action>
+  </actions>
+  <hops>
+    <hop>
+      <from>Start</from>
+      <to>mongo-insert-uuid.hpl</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>Y</unconditional>
+    </hop>
+    <hop>
+      <from>mongo-insert-uuid.hpl</from>
+      <to>mongo-delete-uuid.hpl</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>mongo-delete-uuid.hpl</from>
+      <to>mongo-read-uuid.hpl</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+  </hops>
+  <notepads>
+  </notepads>
+  <attributes/>
+</workflow>
diff --git a/integration-tests/mongo/tests/mongo-uuid/mongo-delete-uuid.hpl 
b/integration-tests/mongo/tests/mongo-uuid/mongo-delete-uuid.hpl
new file mode 100644
index 0000000000..cf48a6d16a
--- /dev/null
+++ b/integration-tests/mongo/tests/mongo-uuid/mongo-delete-uuid.hpl
@@ -0,0 +1,153 @@
+<?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>mongo-delete-uuid</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/27 16:48:50.463</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2025/09/27 16:48:50.463</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>42</height>
+      <xloc>224</xloc>
+      <yloc>64</yloc>
+      <note>Deletes 2 of the rows inserted by mongo-insert-uuid in uuid_insert.
+If this fails, also check the prev transform.</note>
+      <width>362</width>
+    </notepad>
+  </notepads>
+  <order>
+    <hop>
+      <from>Data grid</from>
+      <to>MongoDB Delete</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>45528287-0cf6-4c9c-9228-ca90a182c2c7</item>
+        <item>d3956c05-1251-4f90-b730-1aee603d1499</item>
+      </line>
+      <line>
+        <item>80132062-d0db-4146-a6d8-b634a7acd044</item>
+        <item>91045c8c-cf81-4a38-b429-40efdf128848</item>
+      </line>
+    </data>
+    <fields>
+      <field>
+        <length>-1</length>
+        <precision>-1</precision>
+        <set_empty_string>N</set_empty_string>
+        <name>id1</name>
+        <type>UUID</type>
+      </field>
+      <field>
+        <length>-1</length>
+        <precision>-1</precision>
+        <set_empty_string>N</set_empty_string>
+        <name>id2</name>
+        <type>UUID</type>
+      </field>
+    </fields>
+    <attributes/>
+    <GUI>
+      <xloc>240</xloc>
+      <yloc>128</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>MongoDB Delete</name>
+    <type>MongoDbDelete</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <collection>uuid_insert</collection>
+    <connection>mongo</connection>
+    <execute_for_each_row>N</execute_for_each_row>
+    <fields>
+      <field>
+        <comparator>=</comparator>
+        <doc_path>ids[0]</doc_path>
+        <incoming_field_1>id1</incoming_field_1>
+        <incoming_field_2/>
+      </field>
+      <field>
+        <comparator>=</comparator>
+        <doc_path>ids[1]</doc_path>
+        <incoming_field_1>id2</incoming_field_1>
+        <incoming_field_2/>
+      </field>
+    </fields>
+    <json_query/>
+    <retries>5</retries>
+    <retry_delay>10</retry_delay>
+    <use_json_query>N</use_json_query>
+    <write_retries>5</write_retries>
+    <attributes/>
+    <GUI>
+      <xloc>432</xloc>
+      <yloc>128</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git a/integration-tests/mongo/tests/mongo-uuid/mongo-insert-uuid.hpl 
b/integration-tests/mongo/tests/mongo-uuid/mongo-insert-uuid.hpl
new file mode 100644
index 0000000000..6cfcd99661
--- /dev/null
+++ b/integration-tests/mongo/tests/mongo-uuid/mongo-insert-uuid.hpl
@@ -0,0 +1,183 @@
+<?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>mongo-insert-uuid</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/27 16:33:35.504</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2025/09/27 16:33:35.504</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+    <hop>
+      <from>Data grid</from>
+      <to>insert</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>ad5305ff-fc53-480b-8d0b-d8dfdf93e32e</item>
+        <item>e5703a5d-9384-476c-a673-d9bc347134f2</item>
+        <item>1</item>
+      </line>
+      <line>
+        <item>45528287-0cf6-4c9c-9228-ca90a182c2c7</item>
+        <item>d3956c05-1251-4f90-b730-1aee603d1499</item>
+        <item>2</item>
+      </line>
+      <line>
+        <item>ab6ce49a-5004-4582-b2c4-07e39738cebb</item>
+        <item>883a0ba5-8b11-474a-91f2-312fdb60dd9c</item>
+        <item>3</item>
+      </line>
+      <line>
+        <item>80132062-d0db-4146-a6d8-b634a7acd044</item>
+        <item>91045c8c-cf81-4a38-b429-40efdf128848</item>
+        <item>4</item>
+      </line>
+    </data>
+    <fields>
+      <field>
+        <length>-1</length>
+        <precision>-1</precision>
+        <currency/>
+        <set_empty_string>N</set_empty_string>
+        <name>id1</name>
+        <format/>
+        <group/>
+        <decimal/>
+        <type>UUID</type>
+      </field>
+      <field>
+        <length>-1</length>
+        <precision>-1</precision>
+        <currency/>
+        <set_empty_string>N</set_empty_string>
+        <name>id2</name>
+        <format/>
+        <group/>
+        <decimal/>
+        <type>UUID</type>
+      </field>
+      <field>
+        <length>-1</length>
+        <precision>-1</precision>
+        <currency/>
+        <set_empty_string>N</set_empty_string>
+        <name>amount</name>
+        <format/>
+        <group/>
+        <decimal/>
+        <type>Integer</type>
+      </field>
+    </fields>
+    <attributes/>
+    <GUI>
+      <xloc>224</xloc>
+      <yloc>128</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>insert</name>
+    <type>MongoDbOutput</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <connection>mongo</connection>
+    <mongo_collection>uuid_insert</mongo_collection>
+    <batch_insert_size>5</batch_insert_size>
+    <truncate>Y</truncate>
+    <update>N</update>
+    <upsert>N</upsert>
+    <multi>N</multi>
+    <modifier_update>N</modifier_update>
+    <write_retries>5</write_retries>
+    <write_retry_delay>10</write_retry_delay>
+    <mongo_fields>
+      <mongo_field>
+        <incoming_field_name>id1</incoming_field_name>
+        <mongo_doc_path>ids[0]</mongo_doc_path>
+        
<use_incoming_field_name_as_mongo_field_name>N</use_incoming_field_name_as_mongo_field_name>
+        <update_match_field>N</update_match_field>
+        <modifier_update_operation>N/A</modifier_update_operation>
+        <modifier_policy>Insert&amp;Update</modifier_policy>
+        <json_field>N</json_field>
+        <allow_null>N</allow_null>
+      </mongo_field>
+      <mongo_field>
+        <incoming_field_name>id2</incoming_field_name>
+        <mongo_doc_path>ids[1]</mongo_doc_path>
+        
<use_incoming_field_name_as_mongo_field_name>N</use_incoming_field_name_as_mongo_field_name>
+        <update_match_field>N</update_match_field>
+        <modifier_update_operation>N/A</modifier_update_operation>
+        <modifier_policy>Insert&amp;Update</modifier_policy>
+        <json_field>N</json_field>
+        <allow_null>N</allow_null>
+      </mongo_field>
+      <mongo_field>
+        <incoming_field_name>amount</incoming_field_name>
+        <mongo_doc_path>amount</mongo_doc_path>
+        
<use_incoming_field_name_as_mongo_field_name>N</use_incoming_field_name_as_mongo_field_name>
+        <update_match_field>N</update_match_field>
+        <modifier_update_operation>N/A</modifier_update_operation>
+        <modifier_policy>Insert&amp;Update</modifier_policy>
+        <json_field>N</json_field>
+        <allow_null>N</allow_null>
+      </mongo_field>
+    </mongo_fields>
+    <attributes/>
+    <GUI>
+      <xloc>416</xloc>
+      <yloc>128</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git a/integration-tests/mongo/tests/mongo-uuid/mongo-read-uuid.hpl 
b/integration-tests/mongo/tests/mongo-uuid/mongo-read-uuid.hpl
new file mode 100644
index 0000000000..15100dcd01
--- /dev/null
+++ b/integration-tests/mongo/tests/mongo-uuid/mongo-read-uuid.hpl
@@ -0,0 +1,295 @@
+<?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>mongo-read-uuid</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/27 16:54:15.987</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2025/09/27 16:54:15.987</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>42</height>
+      <xloc>176</xloc>
+      <yloc>64</yloc>
+      <note>If mongo-insert-uuid and mongo-delete-uuid successfully executed, 
uuid_insert will have 2 rows.
+If this fails, also check the prev transforms.</note>
+      <width>522</width>
+    </notepad>
+  </notepads>
+  <order>
+    <hop>
+      <from>MongoDB input</from>
+      <to>Data validator</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>MongoDB input</from>
+      <to>Group by</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Group by</from>
+      <to>Filter rows</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Filter rows</from>
+      <to>Abort</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>Abort</name>
+    <type>Abort</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <abort_option>ABORT_WITH_ERROR</abort_option>
+    <always_log_rows>Y</always_log_rows>
+    <row_threshold>0</row_threshold>
+    <attributes/>
+    <GUI>
+      <xloc>720</xloc>
+      <yloc>240</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Data validator</name>
+    <type>Validator</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <concat_errors>N</concat_errors>
+    <concat_separator/>
+    <validate_all>N</validate_all>
+    <validator_field>
+      <allowed_value>
+</allowed_value>
+      <conversion_mask/>
+      <data_type>UUID</data_type>
+      <data_type_verified>Y</data_type_verified>
+      <decimal_symbol/>
+      <end_string/>
+      <end_string_not_allowed/>
+      <error_code/>
+      <error_description/>
+      <grouping_symbol/>
+      <is_sourcing_values>N</is_sourcing_values>
+      <max_length/>
+      <max_value/>
+      <min_length/>
+      <min_value/>
+      <name>id1</name>
+      <null_allowed>N</null_allowed>
+      <only_null_allowed>N</only_null_allowed>
+      <only_numeric_allowed>N</only_numeric_allowed>
+      <regular_expression/>
+      <regular_expression_not_allowed/>
+      <sourcing_field/>
+      <sourcing_transform/>
+      <start_string/>
+      <start_string_not_allowed/>
+      <validation_name>uuid_type_id1</validation_name>
+    </validator_field>
+    <validator_field>
+      <allowed_value>
+</allowed_value>
+      <conversion_mask/>
+      <data_type>UUID</data_type>
+      <data_type_verified>Y</data_type_verified>
+      <decimal_symbol/>
+      <end_string/>
+      <end_string_not_allowed/>
+      <error_code/>
+      <error_description/>
+      <grouping_symbol/>
+      <is_sourcing_values>N</is_sourcing_values>
+      <max_length/>
+      <max_value/>
+      <min_length/>
+      <min_value/>
+      <name>id2</name>
+      <null_allowed>N</null_allowed>
+      <only_null_allowed>N</only_null_allowed>
+      <only_numeric_allowed>N</only_numeric_allowed>
+      <regular_expression/>
+      <regular_expression_not_allowed/>
+      <sourcing_field/>
+      <sourcing_transform/>
+      <start_string/>
+      <start_string_not_allowed/>
+      <validation_name>uuid_type_id2</validation_name>
+    </validator_field>
+    <attributes/>
+    <GUI>
+      <xloc>400</xloc>
+      <yloc>144</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Filter rows</name>
+    <type>FilterRows</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <compare>
+      <condition>
+        <conditions>
+</conditions>
+        <function>=</function>
+        <leftvalue>rows_count</leftvalue>
+        <negated>N</negated>
+        <operator>-</operator>
+        <value>
+          <isnull>N</isnull>
+          <length>-1</length>
+          <mask>####0;-####0</mask>
+          <name>constant</name>
+          <precision>0</precision>
+          <text>2</text>
+          <type>Integer</type>
+        </value>
+      </condition>
+    </compare>
+    <send_false_to>Abort</send_false_to>
+    <attributes/>
+    <GUI>
+      <xloc>560</xloc>
+      <yloc>240</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Group by</name>
+    <type>GroupBy</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <add_linenr>N</add_linenr>
+    <all_rows>N</all_rows>
+    <directory>${java.io.tmpdir}</directory>
+    <fields>
+      <field>
+        <aggregate>rows_count</aggregate>
+        <subject>id1</subject>
+        <type>COUNT_ALL</type>
+      </field>
+    </fields>
+    <give_back_row>Y</give_back_row>
+    <group>
+</group>
+    <ignore_aggregate>N</ignore_aggregate>
+    <prefix>grp</prefix>
+    <attributes/>
+    <GUI>
+      <xloc>400</xloc>
+      <yloc>240</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>MongoDB input</name>
+    <type>MongoDbInput</type>
+    <description/>
+    <distribute>N</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <connection>mongo</connection>
+    <fields_name/>
+    <collection>uuid_insert</collection>
+    <json_field_name>json</json_field_name>
+    <json_query/>
+    <output_json>N</output_json>
+    <query_is_pipeline>N</query_is_pipeline>
+    <execute_for_each_row>N</execute_for_each_row>
+    <mongo_fields>
+      <mongo_field>
+        <field_name>id1</field_name>
+        <field_path>ids[0]</field_path>
+        <field_type>UUID</field_type>
+      </mongo_field>
+      <mongo_field>
+        <field_name>id2</field_name>
+        <field_path>ids[1]</field_path>
+        <field_type>UUID</field_type>
+      </mongo_field>
+      <mongo_field>
+        <field_name>amount</field_name>
+        <field_path>amount</field_path>
+        <field_type>Integer</field_type>
+      </mongo_field>
+    </mongo_fields>
+    <attributes/>
+    <GUI>
+      <xloc>176</xloc>
+      <yloc>144</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git 
a/plugins/transforms/mongodb/src/main/java/org/apache/hop/mongo/MongoProp.java 
b/plugins/transforms/mongodb/src/main/java/org/apache/hop/mongo/MongoProp.java
index 85ec066d49..41fccd8e8f 100644
--- 
a/plugins/transforms/mongodb/src/main/java/org/apache/hop/mongo/MongoProp.java
+++ 
b/plugins/transforms/mongodb/src/main/java/org/apache/hop/mongo/MongoProp.java
@@ -19,6 +19,7 @@ package org.apache.hop.mongo;
 
 import com.mongodb.MongoClientOptions;
 import javax.net.ssl.SSLSocketFactory;
+import org.bson.UuidRepresentation;
 
 /**
  * Enumeration of the available properties that can be used when configuring a 
MongoDB client via a
@@ -217,7 +218,18 @@ public enum MongoProp {
   /** Specifies the write timeout in millis for the WriteConcern. */
   wTimeout,
   /** MongoDB 3.0 changed the default authentication mechanism from MONGODB-CR 
to SCRAM-SHA-1 */
-  AUTH_MECHA;
+  AUTH_MECHA,
+  /** Specifies how Mongo stores UUIDs * */
+  // TODO add a way for the user to specify the uuidRepresentation
+  uuidRepresentation {
+    @Override
+    public void setOption(
+        MongoClientOptions.Builder builder,
+        org.apache.hop.mongo.MongoProperties props,
+        MongoPropToOption propToOption) {
+      builder.uuidRepresentation(UuidRepresentation.STANDARD);
+    }
+  };
 
   public void setOption(
       MongoClientOptions.Builder builder,
diff --git 
a/plugins/transforms/mongodb/src/main/java/org/apache/hop/mongo/wrapper/field/MongoField.java
 
b/plugins/transforms/mongodb/src/main/java/org/apache/hop/mongo/wrapper/field/MongoField.java
index 94f746c39f..7d822a4884 100644
--- 
a/plugins/transforms/mongodb/src/main/java/org/apache/hop/mongo/wrapper/field/MongoField.java
+++ 
b/plugins/transforms/mongodb/src/main/java/org/apache/hop/mongo/wrapper/field/MongoField.java
@@ -237,6 +237,19 @@ public class MongoField implements Comparable<MongoField> {
       case IValueMeta.TYPE_STRING:
         return tempValueMeta.getString(fieldValue);
       default:
+        // UUID support
+        try {
+          int uuidTypeId = ValueMetaFactory.getIdForValueMeta("UUID");
+          if (tempValueMeta.getType() == uuidTypeId) {
+            if (fieldValue instanceof java.util.UUID uuid) {
+              return uuid;
+            } else {
+              return java.util.UUID.fromString(fieldValue.toString());
+            }
+          }
+        } catch (Exception ignore) {
+          // UUID plugin not present, fall through
+        }
         return null;
     }
   }
diff --git 
a/plugins/transforms/mongodb/src/main/java/org/apache/hop/mongo/wrapper/field/MongodbInputDiscoverFieldsImpl.java
 
b/plugins/transforms/mongodb/src/main/java/org/apache/hop/mongo/wrapper/field/MongodbInputDiscoverFieldsImpl.java
index 28ac50326a..91710b5633 100644
--- 
a/plugins/transforms/mongodb/src/main/java/org/apache/hop/mongo/wrapper/field/MongodbInputDiscoverFieldsImpl.java
+++ 
b/plugins/transforms/mongodb/src/main/java/org/apache/hop/mongo/wrapper/field/MongodbInputDiscoverFieldsImpl.java
@@ -31,10 +31,12 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
 import org.apache.commons.lang.StringUtils;
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.logging.LogChannel;
 import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.value.ValueMetaFactory;
 import org.apache.hop.core.variables.IVariables;
 import org.apache.hop.i18n.BaseMessages;
 import org.apache.hop.mongo.MongoDbException;
@@ -464,6 +466,8 @@ public class MongodbInputDiscoverFieldsImpl implements 
MongoDbInputDiscoverField
       return IValueMeta.TYPE_BINARY;
     } else if (fieldValue instanceof BSONTimestamp) {
       return IValueMeta.TYPE_INTEGER;
+    } else if (fieldValue instanceof UUID) {
+      return ValueMetaFactory.getIdForValueMeta("UUID");
     }
 
     return IValueMeta.TYPE_STRING;
diff --git 
a/plugins/transforms/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodbdelete/MongoDbDeleteData.java
 
b/plugins/transforms/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodbdelete/MongoDbDeleteData.java
index b84c1287a1..ecba113da9 100644
--- 
a/plugins/transforms/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodbdelete/MongoDbDeleteData.java
+++ 
b/plugins/transforms/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodbdelete/MongoDbDeleteData.java
@@ -22,10 +22,12 @@ import com.mongodb.DBObject;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
+import java.util.UUID;
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.exception.HopValueException;
 import org.apache.hop.core.row.IRowMeta;
 import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.value.ValueMetaFactory;
 import org.apache.hop.core.util.StringUtil;
 import org.apache.hop.core.variables.IVariables;
 import org.apache.hop.i18n.BaseMessages;
@@ -347,6 +349,17 @@ public class MongoDbDeleteData extends BaseTransformData 
implements ITransformDa
       mongoObject.put(lookup.toString(), val);
       return true;
     }
+    // UUID
+    try {
+      int uuidTypeId = ValueMetaFactory.getIdForValueMeta("UUID");
+      if (valueMeta.getType() == uuidTypeId) {
+        UUID val = (UUID) valueMeta.convertData(valueMeta, objectValue);
+        mongoObject.put(lookup.toString(), val);
+        return true;
+      }
+    } catch (Exception ignore) {
+      // UUID plugin not present, fall through
+    }
     if (valueMeta.isSerializableType()) {
       throw new HopValueException(
           BaseMessages.getString(PKG, 
"MongoDbDelete.ErrorMessage.CantStoreHopSerializableVals"));
diff --git 
a/plugins/transforms/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodbdelete/MongoDbDeleteDialog.java
 
b/plugins/transforms/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodbdelete/MongoDbDeleteDialog.java
index b710b1d286..e7a053428c 100644
--- 
a/plugins/transforms/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodbdelete/MongoDbDeleteDialog.java
+++ 
b/plugins/transforms/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodbdelete/MongoDbDeleteDialog.java
@@ -871,7 +871,16 @@ public class MongoDbDeleteDialog extends 
BaseTransformDialog {
           val = "<binary val>";
           break;
         default:
-          val = "<unsupported value type>";
+          try {
+            int uuidTypeId = ValueMetaFactory.getIdForValueMeta("UUID");
+            if (rmi.getValueMeta(index).getType() == uuidTypeId) {
+              val = "<UUID val>";
+            } else {
+              val = "<unsupported value type>";
+            }
+          } catch (Exception ignore) {
+            // UUID plugin not present, fall through
+          }
       }
     } else {
       val = "<value>";
diff --git 
a/plugins/transforms/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodboutput/MongoDbOutputData.java
 
b/plugins/transforms/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodboutput/MongoDbOutputData.java
index acaf187ea0..be7ead860e 100644
--- 
a/plugins/transforms/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodboutput/MongoDbOutputData.java
+++ 
b/plugins/transforms/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodboutput/MongoDbOutputData.java
@@ -28,12 +28,14 @@ import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
 import org.apache.commons.lang.StringUtils;
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.exception.HopValueException;
 import org.apache.hop.core.logging.ILogChannel;
 import org.apache.hop.core.row.IRowMeta;
 import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.value.ValueMetaFactory;
 import org.apache.hop.core.util.Utils;
 import org.apache.hop.core.variables.IVariables;
 import org.apache.hop.i18n.BaseMessages;
@@ -921,6 +923,17 @@ public class MongoDbOutputData extends BaseTransformData 
implements ITransformDa
       mongoObject.put(lookup.toString(), val);
       return true;
     }
+    // UUID
+    try {
+      int uuidTypeId = ValueMetaFactory.getIdForValueMeta("UUID");
+      if (hopType.getType() == uuidTypeId) {
+        UUID val = (UUID) hopType.convertData(hopType, hopValue);
+        mongoObject.put(lookup.toString(), val);
+        return true;
+      }
+    } catch (Exception ignore) {
+      // UUID plugin not present, fall through
+    }
     if (hopType.isSerializableType()) {
       throw new HopValueException(
           BaseMessages.getString(
diff --git 
a/plugins/transforms/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodboutput/MongoDbOutputDialog.java
 
b/plugins/transforms/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodboutput/MongoDbOutputDialog.java
index 797e0edb14..570d08242f 100644
--- 
a/plugins/transforms/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodboutput/MongoDbOutputDialog.java
+++ 
b/plugins/transforms/mongodb/src/main/java/org/apache/hop/pipeline/transforms/mongodboutput/MongoDbOutputDialog.java
@@ -1083,7 +1083,16 @@ public class MongoDbOutputDialog extends 
BaseTransformDialog {
               val = "<binary val>"; //
               break;
             default:
-              val = "<unsupported value type>"; //
+              try {
+                int uuidTypeId = ValueMetaFactory.getIdForValueMeta("UUID");
+                if (actualR.getValueMeta(index).getType() == uuidTypeId) {
+                  val = "<UUID val>"; //
+                } else {
+                  val = "<unsupported value type>"; //
+                }
+              } catch (Exception ignore) {
+                // UUID plugin not present, fall through
+              }
           }
         } else {
           val = "<value>"; //
diff --git 
a/plugins/transforms/mongodb/src/test/java/org/apache/hop/mongo/wrapper/MongoFieldTest.java
 
b/plugins/transforms/mongodb/src/test/java/org/apache/hop/mongo/wrapper/MongoFieldTest.java
index 96cb5a7f8b..34189be832 100644
--- 
a/plugins/transforms/mongodb/src/test/java/org/apache/hop/mongo/wrapper/MongoFieldTest.java
+++ 
b/plugins/transforms/mongodb/src/test/java/org/apache/hop/mongo/wrapper/MongoFieldTest.java
@@ -113,6 +113,11 @@ class MongoFieldTest {
     initField("String");
     assertEquals("foo", field.getHopValue("foo"));
     assertEquals("123", field.getHopValue(123));
+
+    initField("UUID");
+    java.util.UUID uuid = 
java.util.UUID.fromString("4d0e4aee-d845-4f5e-8c7d-9d5cff1c2a4d");
+    assertEquals(uuid, field.getHopValue(uuid));
+    assertEquals(uuid, 
field.getHopValue("4d0e4aee-d845-4f5e-8c7d-9d5cff1c2a4d"));
   }
 
   @Test
diff --git 
a/plugins/transforms/pgbulkloader/src/main/java/org/apache/hop/pipeline/transforms/pgbulkloader/PGBulkLoader.java
 
b/plugins/transforms/pgbulkloader/src/main/java/org/apache/hop/pipeline/transforms/pgbulkloader/PGBulkLoader.java
index 1c6df70923..b3b7cd63b4 100644
--- 
a/plugins/transforms/pgbulkloader/src/main/java/org/apache/hop/pipeline/transforms/pgbulkloader/PGBulkLoader.java
+++ 
b/plugins/transforms/pgbulkloader/src/main/java/org/apache/hop/pipeline/transforms/pgbulkloader/PGBulkLoader.java
@@ -40,6 +40,7 @@ import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.logging.ILoggingObject;
 import org.apache.hop.core.row.IRowMeta;
 import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.value.ValueMetaFactory;
 import org.apache.hop.core.util.Utils;
 import org.apache.hop.i18n.BaseMessages;
 import org.apache.hop.pipeline.Pipeline;
@@ -407,8 +408,21 @@ public class PGBulkLoader extends 
BaseTransform<PGBulkLoaderMeta, PGBulkLoaderDa
               }
               break;
             default:
-              throw new HopException(
-                  "PGBulkLoader doesn't handle the type " + 
valueMeta.getTypeDesc());
+
+              // UUID supports
+              if (valueMeta.getType() == 
ValueMetaFactory.getIdForValueMeta("UUID")) {
+                if (valueMeta.isStorageBinaryString()) {
+                  pgCopyOut.write((byte[]) valueData);
+                } else {
+                  String s = valueMeta.getString(valueData);
+                  if (s != null) {
+                    pgCopyOut.write(s.getBytes(clientEncoding));
+                  }
+                }
+              } else {
+                throw new HopException(
+                    "PGBulkLoader doesn't handle the type " + 
valueMeta.getTypeDesc());
+              }
           }
         }
       }
diff --git a/plugins/valuetypes/pom.xml b/plugins/valuetypes/pom.xml
index 4932cca4cc..fa9baf6b37 100644
--- a/plugins/valuetypes/pom.xml
+++ b/plugins/valuetypes/pom.xml
@@ -28,5 +28,7 @@
     <artifactId>hop-plugins-valuetypes</artifactId>
     <packaging>pom</packaging>
     <name>Hop Plugins Value Types</name>
-
+    <modules>
+        <module>uuid</module>
+    </modules>
 </project>
diff --git a/plugins/valuetypes/pom.xml b/plugins/valuetypes/uuid/pom.xml
similarity index 87%
copy from plugins/valuetypes/pom.xml
copy to plugins/valuetypes/uuid/pom.xml
index 4932cca4cc..8f954dcb7a 100644
--- a/plugins/valuetypes/pom.xml
+++ b/plugins/valuetypes/uuid/pom.xml
@@ -14,19 +14,17 @@
   ~ 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.
-  ~
   -->
 <project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
     <modelVersion>4.0.0</modelVersion>
-
     <parent>
         <groupId>org.apache.hop</groupId>
-        <artifactId>hop-plugins</artifactId>
+        <artifactId>hop-plugins-valuetypes</artifactId>
         <version>2.16.0-SNAPSHOT</version>
     </parent>
 
-    <artifactId>hop-plugins-valuetypes</artifactId>
-    <packaging>pom</packaging>
-    <name>Hop Plugins Value Types</name>
+    <artifactId>hop-valuetypes-uuid</artifactId>
+    <packaging>jar</packaging>
+    <name>Hop Plugins UUID Type</name>
 
 </project>
diff --git a/plugins/valuetypes/uuid/src/assembly/assembly.xml 
b/plugins/valuetypes/uuid/src/assembly/assembly.xml
new file mode 100644
index 0000000000..187c09de62
--- /dev/null
+++ b/plugins/valuetypes/uuid/src/assembly/assembly.xml
@@ -0,0 +1,43 @@
+<!--
+  ~ 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.
+  ~
+  -->
+
+<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.2.0";
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+          xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.2.0 
http://maven.apache.org/xsd/assembly-2.2.0.xsd";>
+    <id>hop-valuetypes-uuid</id>
+    <formats>
+        <format>zip</format>
+    </formats>
+    <baseDirectory>.</baseDirectory>
+    <files>
+        <file>
+            <source>${project.basedir}/src/main/resources/version.xml</source>
+            <outputDirectory>plugins/valuetypes/uuid</outputDirectory>
+            <filtered>true</filtered>
+        </file>
+    </files>
+
+    <dependencySets>
+        <dependencySet>
+            <includes>
+                <include>org.apache.hop:hop-valuetypes-uuid:jar</include>
+            </includes>
+            <outputDirectory>plugins/valuetypes/uuid</outputDirectory>
+        </dependencySet>
+    </dependencySets>
+</assembly>
\ No newline at end of file
diff --git 
a/plugins/valuetypes/uuid/src/main/java/org/apache/hop/uuid/ValueMetaUuid.java 
b/plugins/valuetypes/uuid/src/main/java/org/apache/hop/uuid/ValueMetaUuid.java
new file mode 100644
index 0000000000..f97531b38a
--- /dev/null
+++ 
b/plugins/valuetypes/uuid/src/main/java/org/apache/hop/uuid/ValueMetaUuid.java
@@ -0,0 +1,330 @@
+/*
+ * 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.uuid;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.SocketTimeoutException;
+import java.nio.charset.StandardCharsets;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.UUID;
+import org.apache.hop.core.Const;
+import org.apache.hop.core.database.DatabaseMeta;
+import org.apache.hop.core.database.IDatabase;
+import org.apache.hop.core.exception.HopDatabaseException;
+import org.apache.hop.core.exception.HopEofException;
+import org.apache.hop.core.exception.HopFileException;
+import org.apache.hop.core.exception.HopValueException;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.value.ValueMetaBase;
+import org.apache.hop.core.row.value.ValueMetaPlugin;
+
+@ValueMetaPlugin(
+    id = "32", // the number of digits in a UUID
+    name = "UUID",
+    description = "Universally Unique Identifier",
+    image = "")
+public class ValueMetaUuid extends ValueMetaBase {
+
+  public static final int TYPE_UUID = 32;
+
+  public ValueMetaUuid() {
+    super(null, TYPE_UUID);
+  }
+
+  public ValueMetaUuid(String name) {
+    super(name, TYPE_UUID);
+  }
+
+  public ValueMetaUuid(ValueMetaUuid meta) {
+    super(meta.name, TYPE_UUID);
+  }
+
+  @Override
+  public ValueMetaUuid clone() {
+    return (ValueMetaUuid) super.clone();
+  }
+
+  @Override
+  public Class<?> getNativeDataTypeClass() {
+    return UUID.class;
+  }
+
+  @Override
+  public Object convertData(IValueMeta meta2, Object data2) throws 
HopValueException {
+    return toUuid(meta2, data2);
+  }
+
+  /**
+   * Convert the specified data to a UUID. This method is used internally 
instead of convertData()
+   * to avoid upcasts/casts.
+   */
+  private UUID toUuid(IValueMeta meta2, Object data2) throws HopValueException 
{
+    if (data2 == null) {
+      return null;
+    }
+
+    // Already a UUID? Done.
+    if (data2 instanceof java.util.UUID) return (UUID) data2;
+
+    try {
+      switch (meta2.getType()) {
+        case TYPE_UUID:
+          {
+            switch (meta2.getStorageType()) {
+              case STORAGE_TYPE_NORMAL:
+                // This is reached only if the storage type is normal and the
+                // data2 type is String.
+                // if data2 type is UUID, code returns before this line
+                return UUID.fromString((String) data2);
+              case STORAGE_TYPE_BINARY_STRING:
+                return (UUID) convertBinaryStringToNativeType((byte[]) data2);
+              case STORAGE_TYPE_INDEXED:
+                return (UUID) meta2.getIndex()[(Integer) data2];
+            }
+          }
+        case TYPE_STRING:
+          {
+            switch (meta2.getStorageType()) {
+              case STORAGE_TYPE_NORMAL:
+                return UUID.fromString((String) data2);
+              case STORAGE_TYPE_BINARY_STRING:
+
+                // convertBinaryStringToNativeType will do a recursive call on 
convertData.
+                // the convertData will output a UUID,
+                // so no need of converting the String to a UUID
+                return (UUID) convertBinaryStringToNativeType((byte[]) data2);
+              case STORAGE_TYPE_INDEXED:
+                return UUID.fromString((String) meta2.getIndex()[(Integer) 
data2]);
+            }
+          }
+      }
+    } catch (IllegalArgumentException ignore) {
+    }
+    throw new HopValueException(
+        this + " : I can't convert the specified value to data type : UUID");
+  }
+
+  @Override
+  public int hashCode(Object object) throws HopValueException {
+    if (object instanceof java.util.UUID) return object.hashCode();
+
+    UUID u = toUuid(this, object);
+    return u == null ? 0 : u.hashCode();
+  }
+
+  @Override
+  public Object cloneValueData(Object object) throws HopValueException {
+    // UUIDs are immutable, cloning is unnecessary
+    return object;
+  }
+
+  @Override
+  public int compare(Object data1, Object data2) throws HopValueException {
+    boolean n1 = isNull(data1);
+    boolean n2 = isNull(data2);
+
+    if (n1 && !n2) {
+      if (isSortedDescending()) {
+        return 1;
+      } else {
+        return -1;
+      }
+    }
+    if (!n1 && n2) {
+      if (isSortedDescending()) {
+        return -1;
+      } else {
+        return 1;
+      }
+    }
+    if (n1 && n2) {
+      return 0;
+    }
+
+    int cmp = 0;
+
+    cmp = typeCompare(data1, data2);
+
+    if (isSortedDescending()) {
+      return -cmp;
+    } else {
+      return cmp;
+    }
+  }
+
+  protected int typeCompare(Object object1, Object object2) throws 
HopValueException {
+    if (object1 instanceof UUID u1 && object2 instanceof UUID u2) {
+      return u1.compareTo(u2);
+    }
+
+    UUID u1 = toUuid(this, object1);
+    UUID u2 = toUuid(this, object2);
+    // UUID implements Comparable
+    return u1.compareTo(u2);
+  }
+
+  @Override
+  public String getString(Object object) throws HopValueException {
+    UUID u = toUuid(this, object);
+    return u == null ? null : u.toString();
+  }
+
+  @Override
+  public void setPreparedStatementValue(
+      DatabaseMeta databaseMeta, PreparedStatement preparedStatement, int 
index, Object data)
+      throws HopDatabaseException {
+    try {
+      UUID u = toUuid(this, data);
+      if (u == null) {
+        preparedStatement.setNull(index, Types.OTHER);
+        return;
+      }
+
+      // Optimistic try: supposes the user uses uuid ONLY if the database 
supports it
+      try {
+        preparedStatement.setObject(index, u);
+        return;
+      } catch (Exception ignore) {
+        // fall through to string fallback
+      }
+
+      // generic fallback to String
+      preparedStatement.setString(index, u.toString());
+    } catch (Exception e) {
+      throw new HopDatabaseException("Unable to set UUID parameter", e);
+    }
+  }
+
+  @Override
+  public Object getValueFromResultSet(IDatabase iDatabase, ResultSet 
resultSet, int index)
+      throws HopDatabaseException {
+    try {
+      Object o = resultSet.getObject(index + 1);
+      return convertData(this, o);
+    } catch (SQLException e) {
+      throw new HopDatabaseException(
+          "Unable to get value '" + toStringMeta() + "' from database 
resultset, index " + index,
+          e);
+    } catch (Exception e) {
+      throw new HopDatabaseException("Unable to read UUID value", e);
+    }
+  }
+
+  @Override
+  public String getDatabaseColumnTypeDefinition(
+      IDatabase iDatabase,
+      String tk,
+      String pk,
+      boolean useAutoIncrement,
+      boolean addFieldName,
+      boolean addCr) {
+    final String col = addFieldName ? getName() + " " : "";
+    String def = "UUID";
+
+    if (iDatabase.isMsSqlServerNativeVariant()) {
+      def = "UNIQUEIDENTIFIER";
+    }
+    return col + def + (addCr ? Const.CR : "");
+  }
+
+  @Override
+  public byte[] getBinaryString(Object object) throws HopValueException {
+    if (isStorageBinaryString() && identicalFormat) {
+      return (byte[]) object;
+    }
+    UUID u = toUuid(this, object);
+    if (u == null) {
+      return null;
+    }
+
+    try {
+      return u.toString().getBytes(getStringEncoding() == null ? "UTF-8" : 
getStringEncoding());
+    } catch (UnsupportedEncodingException e) {
+      throw new HopValueException("Unsupported encoding for UUID", e);
+    } catch (Exception e) {
+      throw new HopValueException("Unable to get binary string for UUID", e);
+    }
+  }
+
+  @Override
+  public void writeData(DataOutputStream outputStream, Object object) throws 
HopFileException {
+    // Delegate non-NORMAL cases to the base class
+    if (getStorageType() != STORAGE_TYPE_NORMAL) {
+      super.writeData(outputStream, object);
+      return;
+    }
+    try {
+      outputStream.writeBoolean(object == null);
+
+      if (object != null) {
+        UUID u = toUuid(this, object);
+        byte[] b = getBinaryString(u);
+        outputStream.writeInt(b.length);
+        outputStream.write(b);
+      }
+    } catch (IOException e) {
+      throw new HopFileException(this + " : Unable to write value data to 
output stream", e);
+    } catch (Exception e) {
+      throw new HopFileException(
+          "Unable to convert data to UUID before writing to output stream", e);
+    }
+  }
+
+  protected UUID readUuid(DataInputStream inputStream) throws IOException {
+    int inputLength = inputStream.readInt();
+    if (inputLength < 0) {
+      return null;
+    }
+
+    byte[] chars = new byte[inputLength];
+    inputStream.readFully(chars);
+    String uuidStr = new String(chars, StandardCharsets.UTF_8);
+    return UUID.fromString(uuidStr);
+  }
+
+  @Override
+  public Object readData(DataInputStream inputStream)
+      throws HopFileException, SocketTimeoutException {
+    // Delegate non-NORMAL cases to the base class
+    if (getStorageType() != STORAGE_TYPE_NORMAL) {
+      return super.readData(inputStream);
+    }
+
+    try {
+      if (inputStream.readBoolean()) {
+        return null;
+      }
+
+      return readUuid(inputStream);
+
+    } catch (EOFException e) {
+      throw new HopEofException(e);
+    } catch (SocketTimeoutException e) {
+      throw e;
+    } catch (IOException e) {
+      throw new HopFileException(this + " : Unable to read UUID value data 
from input stream", e);
+    }
+  }
+}
diff --git a/plugins/valuetypes/pom.xml 
b/plugins/valuetypes/uuid/src/main/resources/version.xml
similarity index 61%
copy from plugins/valuetypes/pom.xml
copy to plugins/valuetypes/uuid/src/main/resources/version.xml
index 4932cca4cc..6be576acae 100644
--- a/plugins/valuetypes/pom.xml
+++ b/plugins/valuetypes/uuid/src/main/resources/version.xml
@@ -16,17 +16,5 @@
   ~ limitations under the License.
   ~
   -->
-<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
-    <modelVersion>4.0.0</modelVersion>
 
-    <parent>
-        <groupId>org.apache.hop</groupId>
-        <artifactId>hop-plugins</artifactId>
-        <version>2.16.0-SNAPSHOT</version>
-    </parent>
-
-    <artifactId>hop-plugins-valuetypes</artifactId>
-    <packaging>pom</packaging>
-    <name>Hop Plugins Value Types</name>
-
-</project>
+<version>${project.version}</version>
\ No newline at end of file
diff --git 
a/plugins/valuetypes/uuid/src/test/java/org/apache/hop/uuid/ValueMetaUuidTest.java
 
b/plugins/valuetypes/uuid/src/test/java/org/apache/hop/uuid/ValueMetaUuidTest.java
new file mode 100644
index 0000000000..5f1c3697de
--- /dev/null
+++ 
b/plugins/valuetypes/uuid/src/test/java/org/apache/hop/uuid/ValueMetaUuidTest.java
@@ -0,0 +1,216 @@
+/*
+ * 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.uuid;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.sql.ResultSet;
+import java.util.UUID;
+import org.apache.hop.core.HopClientEnvironment;
+import org.apache.hop.core.database.IDatabase;
+import org.apache.hop.core.exception.HopValueException;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.value.ValueMetaFactory;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.mockito.Mockito;
+
+class ValueMetaUuidTest {
+  @RegisterExtension
+  static RestoreHopEngineEnvironmentExtension env = new 
RestoreHopEngineEnvironmentExtension();
+
+  @BeforeAll
+  static void setupOnce() throws Exception {
+    HopClientEnvironment.init();
+  }
+
+  private ValueMetaUuid vm(String name) {
+    return new ValueMetaUuid(name);
+  }
+
+  @Test
+  void testUuidTypeDescription() {
+    int uuidId = ValueMetaFactory.getIdForValueMeta("UUID");
+    assertEquals("UUID", IValueMeta.getTypeDescription(uuidId));
+  }
+
+  @Test
+  void testNativeClassIsUuid() {
+    assertEquals(UUID.class, vm("id").getNativeDataTypeClass());
+  }
+
+  @Test
+  void testConvertToUuid() throws Exception {
+    ValueMetaUuid dst = vm("id");
+    // UUID is converted to UUID (kept the sa,e)
+    UUID u = UUID.randomUUID();
+    assertSame(u, dst.convertData(dst, u));
+
+    // String gets converted to UUID
+    IValueMeta src = new ValueMetaString("id");
+    u = UUID.randomUUID();
+    Object out = dst.convertData(src, u.toString());
+    assertInstanceOf(UUID.class, out);
+    assertEquals(u, out);
+
+    // UUID conversion is storage type is indexed
+    ValueMetaString indexSrc = new ValueMetaString("id");
+    indexSrc.setStorageType(IValueMeta.STORAGE_TYPE_INDEXED);
+    UUID u2 = UUID.randomUUID();
+    indexSrc.setIndex(new Object[] {u.toString(), u2.toString()});
+    out = dst.convertData(indexSrc, Integer.valueOf(1));
+    assertInstanceOf(UUID.class, out);
+    assertEquals(u2, out);
+
+    // string with BINARY_STRING storage (lazy conversion)
+    ValueMetaUuid binDst = vm("id");
+
+    // source meta: String stored as bytes
+    ValueMetaString binSrc = new ValueMetaString("id");
+    binSrc.setStorageType(IValueMeta.STORAGE_TYPE_BINARY_STRING);
+
+    // target meta storageMetadata tells how to turn bytes into String
+    ValueMetaString storage = new ValueMetaString("id");
+    storage.setStorageType(IValueMeta.STORAGE_TYPE_NORMAL);
+    storage.setStringEncoding("UTF-8");
+    binDst.setStorageMetadata(storage);
+
+    UUID u3 = UUID.randomUUID();
+    byte[] lazyBytes = 
u3.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
+
+    out = binDst.convertData(binSrc, lazyBytes);
+    assertInstanceOf(UUID.class, out);
+    assertEquals(u3, out);
+  }
+
+  @Test
+  void testConvertInvalidThrows() {
+    ValueMetaUuid dst = vm("id");
+    IValueMeta src = new ValueMetaString("id");
+    try {
+      dst.convertData(src, "not-a-uuid");
+      fail("Expected HopValueException");
+    } catch (HopValueException e) {
+      // ok
+    }
+  }
+
+  @Test
+  void testHashCode() throws Exception {
+    ValueMetaUuid dst = vm("id");
+    UUID u = UUID.randomUUID();
+    int h1 = dst.hashCode(u);
+    int h2 = dst.hashCode(u.toString());
+    assertEquals(h1, h2);
+
+    assertEquals(0, dst.hashCode(null));
+  }
+
+  @Test
+  void testCloneValueData() throws Exception {
+    ValueMetaUuid dst = vm("id");
+    UUID u = UUID.randomUUID();
+    assertSame(u, dst.cloneValueData(u));
+  }
+
+  @Test
+  void testCompare() throws Exception {
+    ValueMetaUuid dst = vm("id");
+    UUID u = UUID.randomUUID();
+
+    // equality across representations
+    assertEquals(0, dst.compare(u, u.toString()));
+
+    // ascending null handling
+    dst.setSortedDescending(false);
+    assertTrue(dst.compare(null, u) < 0);
+    assertTrue(dst.compare(u, null) > 0);
+    assertEquals(0, dst.compare(null, null));
+
+    // descending null handling
+    dst.setSortedDescending(true);
+    assertTrue(dst.compare(null, u) > 0);
+    assertTrue(dst.compare(u, null) < 0);
+    assertEquals(0, dst.compare(null, null));
+
+    UUID a = UUID.fromString("00000000-0000-0000-0000-000000000000");
+    UUID b = UUID.fromString("00000000-0000-0000-0000-000000000001");
+    // reset to ascending for ordering checks
+    dst.setSortedDescending(false);
+    assertTrue(dst.compare(a, b) < 0);
+    assertTrue(dst.compare(b, a) > 0);
+  }
+
+  @Test
+  void testWriteDataNormalStorage() throws Exception {
+    ValueMetaUuid dst = vm("id");
+    UUID u = UUID.randomUUID();
+
+    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+    DataOutputStream dos = new DataOutputStream(bos);
+
+    dst.writeData(dos, u);
+    dos.flush();
+
+    DataInputStream dis = new DataInputStream(new 
ByteArrayInputStream(bos.toByteArray()));
+
+    // Because writeData() writes a null flag first,
+    // true if the object is false
+    assertFalse(dis.readBoolean());
+    int len = dis.readInt();
+    assertEquals(36, len);
+    byte[] buf = new byte[len];
+    dis.readFully(buf);
+    assertEquals(u.toString(), new String(buf, StandardCharsets.UTF_8));
+  }
+
+  @Test
+  void testGetValueFromResultSetReadsString() throws Exception {
+    ValueMetaUuid dst = vm("id");
+    ResultSet rs = Mockito.mock(ResultSet.class);
+    
Mockito.when(rs.getObject(1)).thenReturn("123e4567-e89b-12d3-a456-426655440000");
+
+    Object out = dst.getValueFromResultSet(Mockito.mock(IDatabase.class), rs, 
0);
+    assertTrue(out instanceof UUID);
+    assertEquals(UUID.fromString("123e4567-e89b-12d3-a456-426655440000"), out);
+  }
+
+  @Test
+  void testGetValueFromResultSetReadsUuid() throws Exception {
+    ValueMetaUuid dst = vm("id");
+    ResultSet rs = Mockito.mock(ResultSet.class);
+    UUID u = UUID.randomUUID();
+    Mockito.when(rs.getObject(1)).thenReturn(u);
+
+    Object out = dst.getValueFromResultSet(Mockito.mock(IDatabase.class), rs, 
0);
+    assertSame(u, out);
+  }
+}

Reply via email to