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