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 84bf266fe2 fix MDI constant injection, fixes #7246 (#7248)
84bf266fe2 is described below
commit 84bf266fe258bfcf69c370ca2f46cc9f3132aba3
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Wed Jun 10 10:01:51 2026 +0200
fix MDI constant injection, fixes #7246 (#7248)
* fix MDI constant injection, fixes #7246
* fix injection from multiple streams in one group, fixes #7246
* update Netty
* extra integration tests
---
.../mdi/0088-set-constant-value-child.hpl | 126 ++++++++++++++
.../mdi/0088-set-constant-value-parent.hpl | 151 +++++++++++++++++
.../mdi/0089-mdi-multi-datagrid-template.hpl | 144 ++++++++++++++++
integration-tests/mdi/0089-mdi-multi-datagrid.hpl | 165 ++++++++++++++++++
.../mdi/0090-mdi-shared-field-template.hpl | 178 +++++++++++++++++++
integration-tests/mdi/0090-mdi-shared-field.hpl | 133 +++++++++++++++
.../mdi/0091-mdi-edge-cases-template.hpl | 162 ++++++++++++++++++
integration-tests/mdi/0091-mdi-edge-cases.hpl | 188 +++++++++++++++++++++
.../mdi/datasets/golden-mdi-edge-cases.csv | 3 +
.../mdi/datasets/golden-mdi-multi-datagrid.csv | 3 +
.../mdi/datasets/golden-mdi-shared-field.csv | 3 +
.../mdi/datasets/golden-set-constant-value.csv | 3 +
.../mdi/main-0088-set-constant-value.hwf | 79 +++++++++
.../mdi/main-0089-mdi-multi-datagrid.hwf | 131 ++++++++++++++
.../mdi/main-0090-mdi-shared-field.hwf | 131 ++++++++++++++
integration-tests/mdi/main-0091-mdi-edge-cases.hwf | 131 ++++++++++++++
.../metadata/dataset/golden-mdi-edge-cases.json | 40 +++++
.../dataset/golden-mdi-multi-datagrid.json | 24 +++
.../metadata/dataset/golden-mdi-shared-field.json | 24 +++
.../dataset/golden-set-constant-value.json | 16 ++
.../0088-set-constant-value-parent UNIT.json | 28 +++
.../0089-mdi-multi-datagrid-template UNIT.json | 33 ++++
.../0090-mdi-shared-field-template UNIT.json | 33 ++++
.../0091-mdi-edge-cases-template UNIT.json | 43 +++++
.../pipeline/transforms/metainject/MetaInject.java | 169 +++++++++++-------
.../transforms/metainject/MetaInjectTest.java | 170 +++++++++++++++++++
pom.xml | 2 +-
27 files changed, 2255 insertions(+), 58 deletions(-)
diff --git a/integration-tests/mdi/0088-set-constant-value-child.hpl
b/integration-tests/mdi/0088-set-constant-value-child.hpl
new file mode 100644
index 0000000000..298f23220e
--- /dev/null
+++ b/integration-tests/mdi/0088-set-constant-value-child.hpl
@@ -0,0 +1,126 @@
+<?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>0088-set-constant-value-child</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description/>
+ <extended_description/>
+ <pipeline_version/>
+ <pipeline_type>Normal</pipeline_type>
+ <parameters>
+ </parameters>
+ <capture_transform_performance>N</capture_transform_performance>
+
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+ <created_user>-</created_user>
+ <created_date>2026/06/09 10:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/06/09 10:00:00.000</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>Sample data</from>
+ <to>Set field value to a constant</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Set field value to a constant</from>
+ <to>Output</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <transform>
+ <name>Output</name>
+ <type>Dummy</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ <GUI>
+ <xloc>480</xloc>
+ <yloc>112</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Set field value to a constant</name>
+ <type>SetValueConstant</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <fields>
+</fields>
+ <usevar>N</usevar>
+ <attributes/>
+ <GUI>
+ <xloc>320</xloc>
+ <yloc>112</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Sample data</name>
+ <type>DataGrid</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <data>
+ <line>
+ <item>original</item>
+ </line>
+ <line>
+ <item>untouched</item>
+ </line>
+ </data>
+ <fields>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>x</name>
+ <type>String</type>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>160</xloc>
+ <yloc>112</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/mdi/0088-set-constant-value-parent.hpl
b/integration-tests/mdi/0088-set-constant-value-parent.hpl
new file mode 100644
index 0000000000..fa619b3fbb
--- /dev/null
+++ b/integration-tests/mdi/0088-set-constant-value-parent.hpl
@@ -0,0 +1,151 @@
+<?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>
+ <pipeline_version/>
+ <capture_transform_performance>N</capture_transform_performance>
+
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+ <pipeline_type>Normal</pipeline_type>
+ <pipeline_status>0</pipeline_status>
+ <parameters/>
+ <name>0088-set-constant-value-parent</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Regression test for issue #7246: a constant value (a mapping
without a source transform) must be injected into the template, even when
another key in the same injection group is fed from a source
transform.</description>
+ <extended_description/>
+ <created_user>-</created_user>
+ <modified_user>-</modified_user>
+ <created_date>2026/06/09 10:00:00.000</created_date>
+ <modified_date>2026/06/09 10:00:00.000</modified_date>
+ </info>
+ <transform>
+ <type>MetaInject</type>
+ <name>0088-set-constant-value-child.hpl</name>
+ <filename>${PROJECT_HOME}/0088-set-constant-value-child.hpl</filename>
+ <source_transform>Output</source_transform>
+ <source_output_fields>
+ <source_output_field>
+ <source_output_field_name>x</source_output_field_name>
+ <source_output_field_type>String</source_output_field_type>
+ <source_output_field_length>-1</source_output_field_length>
+ <source_output_field_precision>-1</source_output_field_precision>
+ </source_output_field>
+ </source_output_fields>
+ <mappings>
+ <mapping>
+ <source_transform>fields</source_transform>
+ <source_field>field name</source_field>
+ <target_transform_name>Set field value to a
constant</target_transform_name>
+ <target_attribute_key>FIELD_NAME</target_attribute_key>
+ <target_detail>Y</target_detail>
+ </mapping>
+ <mapping>
+ <source_field>INJECTED</source_field>
+ <target_transform_name>Set field value to a
constant</target_transform_name>
+ <target_attribute_key>REPLACE_VALUE</target_attribute_key>
+ <target_detail>Y</target_detail>
+ </mapping>
+ </mappings>
+ <target_file/>
+ <no_execution>N</no_execution>
+ <allow_empty_stream_on_execution>N</allow_empty_stream_on_execution>
+ <stream_source_transform/>
+ <stream_target_transform/>
+ <run_configuration>local</run_configuration>
+ <create_parent_folder>Y</create_parent_folder>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>304</xloc>
+ <yloc>80</yloc>
+ </GUI>
+ <description/>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>Dummy</type>
+ <name>Verify</name>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>512</xloc>
+ <yloc>80</yloc>
+ </GUI>
+ <description/>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>DataGrid</type>
+ <name>fields</name>
+ <fields>
+ <field>
+ <currency/>
+ <decimal/>
+ <group/>
+ <name>field name</name>
+ <type>String</type>
+ <format/>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ </fields>
+ <data>
+ <line>
+ <item>x</item>
+ </line>
+ </data>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>112</xloc>
+ <yloc>80</yloc>
+ </GUI>
+ <description/>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <order>
+ <hop>
+ <from>fields</from>
+ <to>0088-set-constant-value-child.hpl</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>0088-set-constant-value-child.hpl</from>
+ <to>Verify</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <notepads/>
+ <attributes/>
+ <transform_error_handling/>
+</pipeline>
diff --git a/integration-tests/mdi/0089-mdi-multi-datagrid-template.hpl
b/integration-tests/mdi/0089-mdi-multi-datagrid-template.hpl
new file mode 100644
index 0000000000..d10eafb72c
--- /dev/null
+++ b/integration-tests/mdi/0089-mdi-multi-datagrid-template.hpl
@@ -0,0 +1,144 @@
+<?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>0089-mdi-multi-datagrid-template</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description/>
+ <extended_description/>
+ <pipeline_version/>
+ <pipeline_type>Normal</pipeline_type>
+ <parameters>
+ </parameters>
+ <capture_transform_performance>N</capture_transform_performance>
+
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+ <created_user>-</created_user>
+ <created_date>2026/06/09 15:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/06/09 15:00:00.000</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>input rows</from>
+ <to>Select values</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Select values</from>
+ <to>output</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <transform>
+ <name>input rows</name>
+ <type>DataGrid</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <data>
+ <line>
+ <item>a1</item>
+ <item>b1</item>
+ <item>c1</item>
+ </line>
+ <line>
+ <item>a2</item>
+ <item>b2</item>
+ <item>c2</item>
+ </line>
+ </data>
+ <fields>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>field1</name>
+ <type>String</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>field2</name>
+ <type>String</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>field3</name>
+ <type>String</type>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>320</xloc>
+ <yloc>192</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Select values</name>
+ <type>SelectValues</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <fields>
+ <select_unspecified>N</select_unspecified>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>560</xloc>
+ <yloc>192</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>output</name>
+ <type>Dummy</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ <GUI>
+ <xloc>800</xloc>
+ <yloc>192</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/mdi/0089-mdi-multi-datagrid.hpl
b/integration-tests/mdi/0089-mdi-multi-datagrid.hpl
new file mode 100644
index 0000000000..ee0153cd62
--- /dev/null
+++ b/integration-tests/mdi/0089-mdi-multi-datagrid.hpl
@@ -0,0 +1,165 @@
+<?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>0089-mdi-multi-datagrid</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Injects a single Select Values "fields" group from two
separate data grids. Reproduces the regression where the second grid's source
field can not be found because the group row layout is cached from the first
grid only.</description>
+ <extended_description/>
+ <pipeline_version/>
+ <pipeline_type>Normal</pipeline_type>
+ <parameters>
+ </parameters>
+ <capture_transform_performance>N</capture_transform_performance>
+
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+ <created_user>-</created_user>
+ <created_date>2026/06/09 15:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/06/09 15:00:00.000</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>field names</from>
+ <to>ETL metadata injection</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>field renames</from>
+ <to>ETL metadata injection</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <transform>
+ <name>ETL metadata injection</name>
+ <type>MetaInject</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <filename>${PROJECT_HOME}/0089-mdi-multi-datagrid-template.hpl</filename>
+ <run_configuration>local</run_configuration>
+ <source_transform/>
+ <source_output_fields> </source_output_fields>
+
<target_file>${PROJECT_HOME}/0089-mdi-multi-datagrid-template-injected.hpl</target_file>
+ <create_parent_folder>Y</create_parent_folder>
+ <no_execution>N</no_execution>
+ <stream_source_transform/>
+ <stream_target_transform/>
+ <mappings>
+ <mapping>
+ <target_transform_name>Select values</target_transform_name>
+ <target_attribute_key>FIELD_NAME</target_attribute_key>
+ <target_detail>Y</target_detail>
+ <source_transform>field names</source_transform>
+ <source_field>name</source_field>
+ </mapping>
+ <mapping>
+ <target_transform_name>Select values</target_transform_name>
+ <target_attribute_key>FIELD_RENAME</target_attribute_key>
+ <target_detail>Y</target_detail>
+ <source_transform>field renames</source_transform>
+ <source_field>newname</source_field>
+ </mapping>
+ </mappings>
+ <attributes/>
+ <GUI>
+ <xloc>544</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>field names</name>
+ <type>DataGrid</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <data>
+ <line>
+ <item>field1</item>
+ </line>
+ <line>
+ <item>field2</item>
+ </line>
+ </data>
+ <fields>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>name</name>
+ <type>String</type>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>224</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>field renames</name>
+ <type>DataGrid</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <data>
+ <line>
+ <item>renamed1</item>
+ </line>
+ <line>
+ <item>renamed2</item>
+ </line>
+ </data>
+ <fields>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>newname</name>
+ <type>String</type>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>224</xloc>
+ <yloc>240</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/mdi/0090-mdi-shared-field-template.hpl
b/integration-tests/mdi/0090-mdi-shared-field-template.hpl
new file mode 100644
index 0000000000..5894c4795e
--- /dev/null
+++ b/integration-tests/mdi/0090-mdi-shared-field-template.hpl
@@ -0,0 +1,178 @@
+<?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>0090-mdi-shared-field-template</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description/>
+ <extended_description/>
+ <pipeline_version/>
+ <pipeline_type>Normal</pipeline_type>
+ <parameters>
+ </parameters>
+ <capture_transform_performance>N</capture_transform_performance>
+
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+ <created_user>-</created_user>
+ <created_date>2026/06/09 16:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/06/09 16:00:00.000</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>input rows</from>
+ <to>Select values</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Select values</from>
+ <to>Select values 2</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Select values 2</from>
+ <to>output</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <transform>
+ <name>input rows</name>
+ <type>DataGrid</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <data>
+ <line>
+ <item>a1</item>
+ <item>b1</item>
+ <item>c1</item>
+ <item>d1</item>
+ </line>
+ <line>
+ <item>a2</item>
+ <item>b2</item>
+ <item>c2</item>
+ <item>d2</item>
+ </line>
+ </data>
+ <fields>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>col1</name>
+ <type>String</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>col2</name>
+ <type>String</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>col3</name>
+ <type>String</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>col4</name>
+ <type>String</type>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>272</xloc>
+ <yloc>192</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Select values</name>
+ <type>SelectValues</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <fields>
+ <select_unspecified>N</select_unspecified>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>480</xloc>
+ <yloc>192</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Select values 2</name>
+ <type>SelectValues</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <fields>
+ <select_unspecified>N</select_unspecified>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>688</xloc>
+ <yloc>192</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>output</name>
+ <type>Dummy</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ <GUI>
+ <xloc>896</xloc>
+ <yloc>192</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/mdi/0090-mdi-shared-field.hpl
b/integration-tests/mdi/0090-mdi-shared-field.hpl
new file mode 100644
index 0000000000..e92f7531ed
--- /dev/null
+++ b/integration-tests/mdi/0090-mdi-shared-field.hpl
@@ -0,0 +1,133 @@
+<?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>0090-mdi-shared-field</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Injects one and the same source field into several injection
targets: two keys (FIELD_NAME and FIELD_RENAME) of one Select Values group and
the FIELD_NAME key of a second Select Values transform. Verifies that reusing a
single input column for multiple injection transforms/groups
works.</description>
+ <extended_description/>
+ <pipeline_version/>
+ <pipeline_type>Normal</pipeline_type>
+ <parameters>
+ </parameters>
+ <capture_transform_performance>N</capture_transform_performance>
+
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+ <created_user>-</created_user>
+ <created_date>2026/06/09 16:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/06/09 16:00:00.000</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>field names</from>
+ <to>ETL metadata injection</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <transform>
+ <name>ETL metadata injection</name>
+ <type>MetaInject</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <filename>${PROJECT_HOME}/0090-mdi-shared-field-template.hpl</filename>
+ <run_configuration>local</run_configuration>
+ <source_transform/>
+ <source_output_fields> </source_output_fields>
+
<target_file>${PROJECT_HOME}/0090-mdi-shared-field-template-injected.hpl</target_file>
+ <create_parent_folder>Y</create_parent_folder>
+ <no_execution>N</no_execution>
+ <stream_source_transform/>
+ <stream_target_transform/>
+ <mappings>
+ <mapping>
+ <target_transform_name>Select values</target_transform_name>
+ <target_attribute_key>FIELD_NAME</target_attribute_key>
+ <target_detail>Y</target_detail>
+ <source_transform>field names</source_transform>
+ <source_field>n</source_field>
+ </mapping>
+ <mapping>
+ <target_transform_name>Select values</target_transform_name>
+ <target_attribute_key>FIELD_RENAME</target_attribute_key>
+ <target_detail>Y</target_detail>
+ <source_transform>field names</source_transform>
+ <source_field>n</source_field>
+ </mapping>
+ <mapping>
+ <target_transform_name>Select values 2</target_transform_name>
+ <target_attribute_key>FIELD_NAME</target_attribute_key>
+ <target_detail>Y</target_detail>
+ <source_transform>field names</source_transform>
+ <source_field>n</source_field>
+ </mapping>
+ </mappings>
+ <attributes/>
+ <GUI>
+ <xloc>544</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>field names</name>
+ <type>DataGrid</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <data>
+ <line>
+ <item>col1</item>
+ </line>
+ <line>
+ <item>col3</item>
+ </line>
+ </data>
+ <fields>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>n</name>
+ <type>String</type>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>224</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/mdi/0091-mdi-edge-cases-template.hpl
b/integration-tests/mdi/0091-mdi-edge-cases-template.hpl
new file mode 100644
index 0000000000..a4e0679ac6
--- /dev/null
+++ b/integration-tests/mdi/0091-mdi-edge-cases-template.hpl
@@ -0,0 +1,162 @@
+<?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>0091-mdi-edge-cases-template</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description/>
+ <extended_description/>
+ <pipeline_version/>
+ <pipeline_type>Normal</pipeline_type>
+ <parameters>
+ </parameters>
+ <capture_transform_performance>N</capture_transform_performance>
+
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+ <created_user>-</created_user>
+ <created_date>2026/06/10 09:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/06/10 09:00:00.000</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>input rows</from>
+ <to>Select values</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Select values</from>
+ <to>output</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <transform>
+ <name>input rows</name>
+ <type>DataGrid</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <data>
+ <line>
+ <item>1</item>
+ <item>11</item>
+ <item>21</item>
+ <item>31</item>
+ <item>99</item>
+ </line>
+ <line>
+ <item>2</item>
+ <item>12</item>
+ <item>22</item>
+ <item>32</item>
+ <item>98</item>
+ </line>
+ </data>
+ <fields>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>id</name>
+ <type>String</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>a</name>
+ <type>String</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>b</name>
+ <type>String</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>c</name>
+ <type>String</type>
+ </field>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>extra</name>
+ <type>String</type>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>272</xloc>
+ <yloc>192</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Select values</name>
+ <type>SelectValues</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <fields>
+ <select_unspecified>N</select_unspecified>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>560</xloc>
+ <yloc>192</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>output</name>
+ <type>Dummy</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ <GUI>
+ <xloc>800</xloc>
+ <yloc>192</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/mdi/0091-mdi-edge-cases.hpl
b/integration-tests/mdi/0091-mdi-edge-cases.hpl
new file mode 100644
index 0000000000..53cb679e1f
--- /dev/null
+++ b/integration-tests/mdi/0091-mdi-edge-cases.hpl
@@ -0,0 +1,188 @@
+<?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>
+ <pipeline_version/>
+ <capture_transform_performance>N</capture_transform_performance>
+
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+ <pipeline_type>Normal</pipeline_type>
+ <pipeline_status>0</pipeline_status>
+ <parameters/>
+ <name>0091-mdi-edge-cases</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Stress test for metadata injection group handling. The Select
Values METAS group is fed from two source transforms with a different number of
rows (select fields = 4 rows, renames = 2 rows), mixed with a constant
(META_TYPE), while the 'select fields' grid is reused across two different
groups (FIELDS via FIELD_NAME and METAS via META_NAME).</description>
+ <extended_description/>
+ <created_user>-</created_user>
+ <modified_user>-</modified_user>
+ <created_date>2026/06/10 09:00:00.000</created_date>
+ <modified_date>2026/06/10 09:00:00.000</modified_date>
+ </info>
+ <transform>
+ <type>MetaInject</type>
+ <name>ETL metadata injection</name>
+ <filename>${PROJECT_HOME}/0091-mdi-edge-cases-template.hpl</filename>
+ <source_transform/>
+ <source_output_fields/>
+ <mappings>
+ <mapping>
+ <source_transform>select fields</source_transform>
+ <source_field>fname</source_field>
+ <target_transform_name>Select values</target_transform_name>
+ <target_attribute_key>FIELD_NAME</target_attribute_key>
+ <target_detail>Y</target_detail>
+ </mapping>
+ <mapping>
+ <source_transform>select fields</source_transform>
+ <source_field>fname</source_field>
+ <target_transform_name>Select values</target_transform_name>
+ <target_attribute_key>META_NAME</target_attribute_key>
+ <target_detail>Y</target_detail>
+ </mapping>
+ <mapping>
+ <source_transform>renames</source_transform>
+ <source_field>rname</source_field>
+ <target_transform_name>Select values</target_transform_name>
+ <target_attribute_key>META_RENAME</target_attribute_key>
+ <target_detail>Y</target_detail>
+ </mapping>
+ <mapping>
+ <source_field>Integer</source_field>
+ <target_transform_name>Select values</target_transform_name>
+ <target_attribute_key>META_TYPE</target_attribute_key>
+ <target_detail>Y</target_detail>
+ </mapping>
+ </mappings>
+
<target_file>${PROJECT_HOME}/0091-mdi-edge-cases-template-injected.hpl</target_file>
+ <no_execution>N</no_execution>
+ <allow_empty_stream_on_execution>N</allow_empty_stream_on_execution>
+ <stream_source_transform/>
+ <stream_target_transform/>
+ <run_configuration>local</run_configuration>
+ <create_parent_folder>Y</create_parent_folder>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>560</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ <description/>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>DataGrid</type>
+ <name>select fields</name>
+ <fields>
+ <field>
+ <currency/>
+ <decimal/>
+ <group/>
+ <name>fname</name>
+ <type>String</type>
+ <format/>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ </fields>
+ <data>
+ <line>
+ <item>id</item>
+ </line>
+ <line>
+ <item>a</item>
+ </line>
+ <line>
+ <item>b</item>
+ </line>
+ <line>
+ <item>c</item>
+ </line>
+ </data>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>224</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ <description/>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>DataGrid</type>
+ <name>renames</name>
+ <fields>
+ <field>
+ <currency/>
+ <decimal/>
+ <group/>
+ <name>rname</name>
+ <type>String</type>
+ <format/>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ </fields>
+ <data>
+ <line>
+ <item>ID</item>
+ </line>
+ <line>
+ <item>AA</item>
+ </line>
+ </data>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>224</xloc>
+ <yloc>240</yloc>
+ </GUI>
+ <description/>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <order>
+ <hop>
+ <from>select fields</from>
+ <to>ETL metadata injection</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>renames</from>
+ <to>ETL metadata injection</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <notepads/>
+ <attributes/>
+ <transform_error_handling/>
+</pipeline>
diff --git a/integration-tests/mdi/datasets/golden-mdi-edge-cases.csv
b/integration-tests/mdi/datasets/golden-mdi-edge-cases.csv
new file mode 100644
index 0000000000..90118a087c
--- /dev/null
+++ b/integration-tests/mdi/datasets/golden-mdi-edge-cases.csv
@@ -0,0 +1,3 @@
+ID,AA,b,c
+1,11,21,31
+2,12,22,32
diff --git a/integration-tests/mdi/datasets/golden-mdi-multi-datagrid.csv
b/integration-tests/mdi/datasets/golden-mdi-multi-datagrid.csv
new file mode 100644
index 0000000000..078011df7d
--- /dev/null
+++ b/integration-tests/mdi/datasets/golden-mdi-multi-datagrid.csv
@@ -0,0 +1,3 @@
+renamed1,renamed2
+a1,b1
+a2,b2
diff --git a/integration-tests/mdi/datasets/golden-mdi-shared-field.csv
b/integration-tests/mdi/datasets/golden-mdi-shared-field.csv
new file mode 100644
index 0000000000..e24493adca
--- /dev/null
+++ b/integration-tests/mdi/datasets/golden-mdi-shared-field.csv
@@ -0,0 +1,3 @@
+col1,col3
+a1,c1
+a2,c2
diff --git a/integration-tests/mdi/datasets/golden-set-constant-value.csv
b/integration-tests/mdi/datasets/golden-set-constant-value.csv
new file mode 100644
index 0000000000..72f66b70fa
--- /dev/null
+++ b/integration-tests/mdi/datasets/golden-set-constant-value.csv
@@ -0,0 +1,3 @@
+x
+INJECTED
+INJECTED
diff --git a/integration-tests/mdi/main-0088-set-constant-value.hwf
b/integration-tests/mdi/main-0088-set-constant-value.hwf
new file mode 100644
index 0000000000..602ca5c15e
--- /dev/null
+++ b/integration-tests/mdi/main-0088-set-constant-value.hwf
@@ -0,0 +1,79 @@
+<?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-0088-set-constant-value</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description/>
+ <extended_description/>
+ <workflow_version/>
+ <created_user>-</created_user>
+ <created_date>2026/06/09 10:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/06/09 10:00:00.000</modified_date>
+ <parameters>
+ </parameters>
+ <actions>
+ <action>
+ <name>Start</name>
+ <description/>
+ <type>SPECIAL</type>
+ <attributes/>
+ <DayOfMonth>1</DayOfMonth>
+ <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>96</xloc>
+ <yloc>80</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>Run Pipeline Unit Tests</name>
+ <description/>
+ <type>RunPipelineTests</type>
+ <attributes/>
+ <test_names>
+ <test_name>
+ <name>0088-set-constant-value-parent UNIT</name>
+ </test_name>
+ </test_names>
+ <parallel>N</parallel>
+ <xloc>304</xloc>
+ <yloc>80</yloc>
+ <attributes_hac/>
+ </action>
+ </actions>
+ <hops>
+ <hop>
+ <from>Start</from>
+ <to>Run Pipeline Unit Tests</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>Y</unconditional>
+ </hop>
+ </hops>
+ <notepads>
+ </notepads>
+ <attributes/>
+</workflow>
diff --git a/integration-tests/mdi/main-0089-mdi-multi-datagrid.hwf
b/integration-tests/mdi/main-0089-mdi-multi-datagrid.hwf
new file mode 100644
index 0000000000..d040b6dd49
--- /dev/null
+++ b/integration-tests/mdi/main-0089-mdi-multi-datagrid.hwf
@@ -0,0 +1,131 @@
+<?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-0089-mdi-multi-datagrid</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description/>
+ <extended_description/>
+ <workflow_version/>
+ <created_user>-</created_user>
+ <created_date>2026/06/09 15:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/06/09 15:00:00.000</modified_date>
+ <parameters>
+ </parameters>
+ <actions>
+ <action>
+ <name>Start</name>
+ <description/>
+ <type>SPECIAL</type>
+ <attributes/>
+ <DayOfMonth>1</DayOfMonth>
+ <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>48</xloc>
+ <yloc>128</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>0089-mdi-multi-datagrid.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}/0089-mdi-multi-datagrid.hpl</filename>
+ <loglevel>Basic</loglevel>
+ <parameters>
+ <pass_all_parameters>Y</pass_all_parameters>
+ </parameters>
+ <params_from_previous>N</params_from_previous>
+ <run_configuration>local</run_configuration>
+ <set_append_logfile>N</set_append_logfile>
+ <set_logfile>N</set_logfile>
+ <wait_until_finished>Y</wait_until_finished>
+ <parallel>N</parallel>
+ <xloc>256</xloc>
+ <yloc>128</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>Run Pipeline Unit Tests</name>
+ <description/>
+ <type>RunPipelineTests</type>
+ <attributes/>
+ <test_names>
+ <test_name>
+ <name>0089-mdi-multi-datagrid-template UNIT</name>
+ </test_name>
+ </test_names>
+ <parallel>N</parallel>
+ <xloc>464</xloc>
+ <yloc>128</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>delete created pipeline</name>
+ <description/>
+ <type>DELETE_FILE</type>
+ <attributes/>
+ <fail_if_file_not_exists>N</fail_if_file_not_exists>
+
<filename>${PROJECT_HOME}/0089-mdi-multi-datagrid-template-injected.hpl</filename>
+ <parallel>N</parallel>
+ <xloc>464</xloc>
+ <yloc>272</yloc>
+ <attributes_hac/>
+ </action>
+ </actions>
+ <hops>
+ <hop>
+ <from>Start</from>
+ <to>0089-mdi-multi-datagrid.hpl</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>Y</unconditional>
+ </hop>
+ <hop>
+ <from>0089-mdi-multi-datagrid.hpl</from>
+ <to>Run Pipeline Unit Tests</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>Run Pipeline Unit Tests</from>
+ <to>delete created pipeline</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ </hops>
+ <notepads>
+ </notepads>
+ <attributes/>
+</workflow>
diff --git a/integration-tests/mdi/main-0090-mdi-shared-field.hwf
b/integration-tests/mdi/main-0090-mdi-shared-field.hwf
new file mode 100644
index 0000000000..0de808ab38
--- /dev/null
+++ b/integration-tests/mdi/main-0090-mdi-shared-field.hwf
@@ -0,0 +1,131 @@
+<?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-0090-mdi-shared-field</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description/>
+ <extended_description/>
+ <workflow_version/>
+ <created_user>-</created_user>
+ <created_date>2026/06/09 16:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/06/09 16:00:00.000</modified_date>
+ <parameters>
+ </parameters>
+ <actions>
+ <action>
+ <name>Start</name>
+ <description/>
+ <type>SPECIAL</type>
+ <attributes/>
+ <DayOfMonth>1</DayOfMonth>
+ <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>48</xloc>
+ <yloc>128</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>0090-mdi-shared-field.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}/0090-mdi-shared-field.hpl</filename>
+ <loglevel>Basic</loglevel>
+ <parameters>
+ <pass_all_parameters>Y</pass_all_parameters>
+ </parameters>
+ <params_from_previous>N</params_from_previous>
+ <run_configuration>local</run_configuration>
+ <set_append_logfile>N</set_append_logfile>
+ <set_logfile>N</set_logfile>
+ <wait_until_finished>Y</wait_until_finished>
+ <parallel>N</parallel>
+ <xloc>256</xloc>
+ <yloc>128</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>Run Pipeline Unit Tests</name>
+ <description/>
+ <type>RunPipelineTests</type>
+ <attributes/>
+ <test_names>
+ <test_name>
+ <name>0090-mdi-shared-field-template UNIT</name>
+ </test_name>
+ </test_names>
+ <parallel>N</parallel>
+ <xloc>464</xloc>
+ <yloc>128</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>delete created pipeline</name>
+ <description/>
+ <type>DELETE_FILE</type>
+ <attributes/>
+ <fail_if_file_not_exists>N</fail_if_file_not_exists>
+
<filename>${PROJECT_HOME}/0090-mdi-shared-field-template-injected.hpl</filename>
+ <parallel>N</parallel>
+ <xloc>464</xloc>
+ <yloc>272</yloc>
+ <attributes_hac/>
+ </action>
+ </actions>
+ <hops>
+ <hop>
+ <from>Start</from>
+ <to>0090-mdi-shared-field.hpl</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>Y</unconditional>
+ </hop>
+ <hop>
+ <from>0090-mdi-shared-field.hpl</from>
+ <to>Run Pipeline Unit Tests</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>Run Pipeline Unit Tests</from>
+ <to>delete created pipeline</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ </hops>
+ <notepads>
+ </notepads>
+ <attributes/>
+</workflow>
diff --git a/integration-tests/mdi/main-0091-mdi-edge-cases.hwf
b/integration-tests/mdi/main-0091-mdi-edge-cases.hwf
new file mode 100644
index 0000000000..84f3cf408d
--- /dev/null
+++ b/integration-tests/mdi/main-0091-mdi-edge-cases.hwf
@@ -0,0 +1,131 @@
+<?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-0091-mdi-edge-cases</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description/>
+ <extended_description/>
+ <workflow_version/>
+ <created_user>-</created_user>
+ <created_date>2026/06/10 09:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/06/10 09:00:00.000</modified_date>
+ <parameters>
+ </parameters>
+ <actions>
+ <action>
+ <name>Start</name>
+ <description/>
+ <type>SPECIAL</type>
+ <attributes/>
+ <DayOfMonth>1</DayOfMonth>
+ <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>48</xloc>
+ <yloc>128</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>0091-mdi-edge-cases.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}/0091-mdi-edge-cases.hpl</filename>
+ <loglevel>Basic</loglevel>
+ <parameters>
+ <pass_all_parameters>Y</pass_all_parameters>
+ </parameters>
+ <params_from_previous>N</params_from_previous>
+ <run_configuration>local</run_configuration>
+ <set_append_logfile>N</set_append_logfile>
+ <set_logfile>N</set_logfile>
+ <wait_until_finished>Y</wait_until_finished>
+ <parallel>N</parallel>
+ <xloc>256</xloc>
+ <yloc>128</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>Run Pipeline Unit Tests</name>
+ <description/>
+ <type>RunPipelineTests</type>
+ <attributes/>
+ <test_names>
+ <test_name>
+ <name>0091-mdi-edge-cases-template UNIT</name>
+ </test_name>
+ </test_names>
+ <parallel>N</parallel>
+ <xloc>464</xloc>
+ <yloc>128</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>delete created pipeline</name>
+ <description/>
+ <type>DELETE_FILE</type>
+ <attributes/>
+ <fail_if_file_not_exists>N</fail_if_file_not_exists>
+
<filename>${PROJECT_HOME}/0091-mdi-edge-cases-template-injected.hpl</filename>
+ <parallel>N</parallel>
+ <xloc>464</xloc>
+ <yloc>272</yloc>
+ <attributes_hac/>
+ </action>
+ </actions>
+ <hops>
+ <hop>
+ <from>Start</from>
+ <to>0091-mdi-edge-cases.hpl</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>Y</unconditional>
+ </hop>
+ <hop>
+ <from>0091-mdi-edge-cases.hpl</from>
+ <to>Run Pipeline Unit Tests</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>Run Pipeline Unit Tests</from>
+ <to>delete created pipeline</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ </hops>
+ <notepads>
+ </notepads>
+ <attributes/>
+</workflow>
diff --git a/integration-tests/mdi/metadata/dataset/golden-mdi-edge-cases.json
b/integration-tests/mdi/metadata/dataset/golden-mdi-edge-cases.json
new file mode 100644
index 0000000000..1a24cd4c9f
--- /dev/null
+++ b/integration-tests/mdi/metadata/dataset/golden-mdi-edge-cases.json
@@ -0,0 +1,40 @@
+{
+ "base_filename": "golden-mdi-edge-cases.csv",
+ "name": "golden-mdi-edge-cases",
+ "description": "",
+ "dataset_fields": [
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 5,
+ "field_precision": 0,
+ "field_name": "ID",
+ "field_format": "####0;-####0"
+ },
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 5,
+ "field_precision": 0,
+ "field_name": "AA",
+ "field_format": "####0;-####0"
+ },
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 5,
+ "field_precision": 0,
+ "field_name": "b",
+ "field_format": "####0;-####0"
+ },
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 5,
+ "field_precision": 0,
+ "field_name": "c",
+ "field_format": "####0;-####0"
+ }
+ ],
+ "folder_name": ""
+}
diff --git
a/integration-tests/mdi/metadata/dataset/golden-mdi-multi-datagrid.json
b/integration-tests/mdi/metadata/dataset/golden-mdi-multi-datagrid.json
new file mode 100644
index 0000000000..b30f5f21c9
--- /dev/null
+++ b/integration-tests/mdi/metadata/dataset/golden-mdi-multi-datagrid.json
@@ -0,0 +1,24 @@
+{
+ "base_filename": "golden-mdi-multi-datagrid.csv",
+ "name": "golden-mdi-multi-datagrid",
+ "description": "",
+ "dataset_fields": [
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 2,
+ "field_precision": -1,
+ "field_name": "renamed1",
+ "field_format": ""
+ },
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 2,
+ "field_precision": -1,
+ "field_name": "renamed2",
+ "field_format": ""
+ }
+ ],
+ "folder_name": ""
+}
diff --git
a/integration-tests/mdi/metadata/dataset/golden-mdi-shared-field.json
b/integration-tests/mdi/metadata/dataset/golden-mdi-shared-field.json
new file mode 100644
index 0000000000..61b4d1d34c
--- /dev/null
+++ b/integration-tests/mdi/metadata/dataset/golden-mdi-shared-field.json
@@ -0,0 +1,24 @@
+{
+ "base_filename": "golden-mdi-shared-field.csv",
+ "name": "golden-mdi-shared-field",
+ "description": "",
+ "dataset_fields": [
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 2,
+ "field_precision": -1,
+ "field_name": "col1",
+ "field_format": ""
+ },
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 2,
+ "field_precision": -1,
+ "field_name": "col3",
+ "field_format": ""
+ }
+ ],
+ "folder_name": ""
+}
diff --git
a/integration-tests/mdi/metadata/dataset/golden-set-constant-value.json
b/integration-tests/mdi/metadata/dataset/golden-set-constant-value.json
new file mode 100644
index 0000000000..c49e68c338
--- /dev/null
+++ b/integration-tests/mdi/metadata/dataset/golden-set-constant-value.json
@@ -0,0 +1,16 @@
+{
+ "base_filename": "golden-set-constant-value.csv",
+ "name": "golden-set-constant-value",
+ "description": "",
+ "dataset_fields": [
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 2,
+ "field_precision": -1,
+ "field_format": "",
+ "field_name": "x"
+ }
+ ],
+ "folder_name": ""
+}
diff --git
a/integration-tests/mdi/metadata/unit-test/0088-set-constant-value-parent
UNIT.json
b/integration-tests/mdi/metadata/unit-test/0088-set-constant-value-parent
UNIT.json
new file mode 100644
index 0000000000..1ce759f19c
--- /dev/null
+++ b/integration-tests/mdi/metadata/unit-test/0088-set-constant-value-parent
UNIT.json
@@ -0,0 +1,28 @@
+{
+ "variableValues": [],
+ "database_replacements": [],
+ "autoOpening": true,
+ "basePath": "",
+ "golden_data_sets": [
+ {
+ "field_mappings": [
+ {
+ "transform_field": "x",
+ "data_set_field": "x"
+ }
+ ],
+ "field_order": [
+ "x"
+ ],
+ "transform_name": "Verify",
+ "data_set_name": "golden-set-constant-value"
+ }
+ ],
+ "input_data_sets": [],
+ "name": "0088-set-constant-value-parent UNIT",
+ "description": "",
+ "trans_test_tweaks": [],
+ "persist_filename": "",
+ "pipeline_filename": "./0088-set-constant-value-parent.hpl",
+ "test_type": "UNIT_TEST"
+}
diff --git
a/integration-tests/mdi/metadata/unit-test/0089-mdi-multi-datagrid-template
UNIT.json
b/integration-tests/mdi/metadata/unit-test/0089-mdi-multi-datagrid-template
UNIT.json
new file mode 100644
index 0000000000..c97178eeca
--- /dev/null
+++ b/integration-tests/mdi/metadata/unit-test/0089-mdi-multi-datagrid-template
UNIT.json
@@ -0,0 +1,33 @@
+{
+ "database_replacements": [],
+ "autoOpening": true,
+ "description": "",
+ "persist_filename": "",
+ "test_type": "UNIT_TEST",
+ "variableValues": [],
+ "basePath": "",
+ "golden_data_sets": [
+ {
+ "field_mappings": [
+ {
+ "transform_field": "renamed1",
+ "data_set_field": "renamed1"
+ },
+ {
+ "transform_field": "renamed2",
+ "data_set_field": "renamed2"
+ }
+ ],
+ "field_order": [
+ "renamed1",
+ "renamed2"
+ ],
+ "data_set_name": "golden-mdi-multi-datagrid",
+ "transform_name": "output"
+ }
+ ],
+ "input_data_sets": [],
+ "name": "0089-mdi-multi-datagrid-template UNIT",
+ "trans_test_tweaks": [],
+ "pipeline_filename": "./0089-mdi-multi-datagrid-template-injected.hpl"
+}
diff --git
a/integration-tests/mdi/metadata/unit-test/0090-mdi-shared-field-template
UNIT.json
b/integration-tests/mdi/metadata/unit-test/0090-mdi-shared-field-template
UNIT.json
new file mode 100644
index 0000000000..d8807be526
--- /dev/null
+++ b/integration-tests/mdi/metadata/unit-test/0090-mdi-shared-field-template
UNIT.json
@@ -0,0 +1,33 @@
+{
+ "database_replacements": [],
+ "autoOpening": true,
+ "description": "",
+ "persist_filename": "",
+ "test_type": "UNIT_TEST",
+ "variableValues": [],
+ "basePath": "",
+ "golden_data_sets": [
+ {
+ "field_mappings": [
+ {
+ "transform_field": "col1",
+ "data_set_field": "col1"
+ },
+ {
+ "transform_field": "col3",
+ "data_set_field": "col3"
+ }
+ ],
+ "field_order": [
+ "col1",
+ "col3"
+ ],
+ "data_set_name": "golden-mdi-shared-field",
+ "transform_name": "output"
+ }
+ ],
+ "input_data_sets": [],
+ "name": "0090-mdi-shared-field-template UNIT",
+ "trans_test_tweaks": [],
+ "pipeline_filename": "./0090-mdi-shared-field-template-injected.hpl"
+}
diff --git
a/integration-tests/mdi/metadata/unit-test/0091-mdi-edge-cases-template
UNIT.json
b/integration-tests/mdi/metadata/unit-test/0091-mdi-edge-cases-template
UNIT.json
new file mode 100644
index 0000000000..b0e6adf20c
--- /dev/null
+++ b/integration-tests/mdi/metadata/unit-test/0091-mdi-edge-cases-template
UNIT.json
@@ -0,0 +1,43 @@
+{
+ "database_replacements": [],
+ "autoOpening": true,
+ "description": "",
+ "persist_filename": "",
+ "test_type": "UNIT_TEST",
+ "variableValues": [],
+ "basePath": "",
+ "golden_data_sets": [
+ {
+ "field_mappings": [
+ {
+ "transform_field": "ID",
+ "data_set_field": "ID"
+ },
+ {
+ "transform_field": "AA",
+ "data_set_field": "AA"
+ },
+ {
+ "transform_field": "b",
+ "data_set_field": "b"
+ },
+ {
+ "transform_field": "c",
+ "data_set_field": "c"
+ }
+ ],
+ "field_order": [
+ "ID",
+ "AA",
+ "b",
+ "c"
+ ],
+ "data_set_name": "golden-mdi-edge-cases",
+ "transform_name": "output"
+ }
+ ],
+ "input_data_sets": [],
+ "name": "0091-mdi-edge-cases-template UNIT",
+ "trans_test_tweaks": [],
+ "pipeline_filename": "./0091-mdi-edge-cases-template-injected.hpl"
+}
diff --git
a/plugins/transforms/metainject/src/main/java/org/apache/hop/pipeline/transforms/metainject/MetaInject.java
b/plugins/transforms/metainject/src/main/java/org/apache/hop/pipeline/transforms/metainject/MetaInject.java
index c348deb162..db6d8e417a 100644
---
a/plugins/transforms/metainject/src/main/java/org/apache/hop/pipeline/transforms/metainject/MetaInject.java
+++
b/plugins/transforms/metainject/src/main/java/org/apache/hop/pipeline/transforms/metainject/MetaInject.java
@@ -45,6 +45,7 @@ import org.apache.hop.core.row.IRowMeta;
import org.apache.hop.core.row.IValueMeta;
import org.apache.hop.core.row.RowBuffer;
import org.apache.hop.core.row.RowDataUtil;
+import org.apache.hop.core.row.RowMeta;
import org.apache.hop.core.row.value.ValueMetaString;
import org.apache.hop.core.util.ExecutorUtil;
import org.apache.hop.core.util.Utils;
@@ -185,8 +186,8 @@ public class MetaInject extends
BaseTransform<MetaInjectMeta, MetaInjectData> {
// Wait a little bit.
try {
Thread.sleep(50);
- } catch (Exception e) {
- // Ignore errors
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
}
}
copyResult(injectPipeline);
@@ -333,14 +334,7 @@ public class MetaInject extends
BaseTransform<MetaInjectMeta, MetaInjectData> {
// These are the values to inject into keys belonging to the groups.
//
Map<String, RowBuffer> injectionGroupData = new HashMap<>();
-
- // What is the row layout for a particular group data?
- //
- Map<String, IRowMeta> groupRowMetaMap = new HashMap<>();
-
- // We keep track of where an injection group data comes from.
- //
- Map<String, String> groupSourceMap = new HashMap<>();
+ Map<String, List<GroupColumn>> groupColumnsMap = new HashMap<>();
// We already have the rows. We just need to rename the fields to the
injection key
//
@@ -349,26 +343,20 @@ public class MetaInject extends
BaseTransform<MetaInjectMeta, MetaInjectData> {
targetTransformName,
mapping,
injectionKeyGroupMap,
- groupRowMetaMap,
- groupSourceMap,
+ groupColumnsMap,
injectionGroupData,
injectionKeyData,
targetTransformMeta.getClass());
}
- // Now all source rows are mapped and we can construct the group data map
- //
- for (Entry<String, String> e : groupSourceMap.entrySet()) {
+ for (Entry<String, List<GroupColumn>> e : groupColumnsMap.entrySet()) {
String groupKey = e.getKey();
- String sourceTransformName = e.getValue();
- IRowMeta rowMeta = groupRowMetaMap.get(groupKey);
- List<RowMetaAndData> rowMetaAndData =
data.rowMap.get(sourceTransformName);
- // Make a copy of the row metadata and data to avoid any risk of
- // another transform modifying the rows during injection.
- //
- RowBuffer rowBuffer = new RowBuffer(rowMeta.clone());
- for (RowMetaAndData rd : rowMetaAndData) {
- rowBuffer.getBuffer().add(rowMeta.cloneRow(rd.getData()));
+ RowBuffer rowBuffer = buildGroupRowBuffer(e.getValue(), data.rowMap);
+
+ // A group can mix streamed keys and constant keys. Merge them
+ RowBuffer constantBuffer = injectionGroupData.get(groupKey);
+ if (constantBuffer != null) {
+ mergeConstantsIntoGroupBuffer(rowBuffer, constantBuffer);
}
injectionGroupData.put(groupKey, rowBuffer);
}
@@ -385,8 +373,7 @@ public class MetaInject extends
BaseTransform<MetaInjectMeta, MetaInjectData> {
String targetTransformName,
MetaInjectMapping mapping,
Map<String, Set<String>> injectionKeyGroupMap,
- Map<String, IRowMeta> groupRowMetaMap,
- Map<String, String> groupSourceMap,
+ Map<String, List<GroupColumn>> groupColumnsMap,
Map<String, RowBuffer> injectionGroupData,
Map<String, Object> injectionKeyData,
Class<?> targetMetaClass)
@@ -404,8 +391,7 @@ public class MetaInject extends
BaseTransform<MetaInjectMeta, MetaInjectData> {
if (sourceRows != null && !sourceRows.isEmpty()) {
// We need to collect the data for the mapping
//
- collectDataForOneMappingGroup(
- mapping, groupRowMetaMap, groupSourceMap, groupKey, sourceRows);
+ collectDataForOneMappingGroup(mapping, groupColumnsMap, groupKey,
sourceRows);
} else {
// See if this is a constant without a source
//
@@ -435,22 +421,15 @@ public class MetaInject extends
BaseTransform<MetaInjectMeta, MetaInjectData> {
}
}
- private static void collectDataForOneMappingGroup(
+ // Package-private for unit testing (see MetaInjectTest).
+ static void collectDataForOneMappingGroup(
MetaInjectMapping mapping,
- Map<String, IRowMeta> groupRowMetaMap,
- Map<String, String> groupSourceMap,
+ Map<String, List<GroupColumn>> groupColumnsMap,
String groupKey,
List<RowMetaAndData> sourceRows)
throws HopTransformException {
- IRowMeta rowMeta;
- rowMeta =
- groupRowMetaMap.computeIfAbsent(groupKey, f ->
sourceRows.getFirst().getRowMeta().clone());
- int index = rowMeta.indexOfValue(mapping.getSourceField());
- if (index < 0) {
- // Is the field already renamed to the target?
- //
- index = rowMeta.indexOfValue(mapping.getTargetAttributeKey());
- }
+ IRowMeta sourceRowMeta = sourceRows.getFirst().getRowMeta();
+ int index = sourceRowMeta.indexOfValue(mapping.getSourceField());
if (index < 0) {
throw new HopTransformException(
"The field '"
@@ -459,16 +438,56 @@ public class MetaInject extends
BaseTransform<MetaInjectMeta, MetaInjectData> {
+ mapping.getSourceTransformName()
+ "' to inject could not be found");
}
- // We give the value the name of the target injection key
- //
- IValueMeta valueMeta = rowMeta.getValueMeta(index);
- valueMeta.setName(mapping.getTargetAttributeKey());
+ groupColumnsMap
+ .computeIfAbsent(groupKey, f -> new ArrayList<>())
+ .add(
+ new GroupColumn(
+ mapping.getTargetAttributeKey(),
mapping.getSourceTransformName(), index));
+ }
- // Let's keep track of where the rows are coming from for the current group
+ /**
+ * Build the row buffer for a single injection group by reading each column
from its own source
+ * transform and zipping them together by row index. Columns may come from
different source
+ * transforms with different row counts; shorter columns leave a {@code
null} (which the injector
+ * skips) for the missing rows.
+ */
+ static RowBuffer buildGroupRowBuffer(
+ List<GroupColumn> columns, Map<String, List<RowMetaAndData>> rowMap) {
+ // Build the group's row layout (one value per column, named after its
injection key) and find
+ // the longest column so every row of every source is represented.
//
- groupSourceMap.put(groupKey, mapping.getSourceTransformName());
+ RowMeta groupRowMeta = new RowMeta();
+ int rowCount = 0;
+ for (GroupColumn column : columns) {
+ List<RowMetaAndData> sourceRows =
rowMap.get(column.sourceTransformName());
+ IValueMeta valueMeta =
+
sourceRows.getFirst().getRowMeta().getValueMeta(column.columnIndex()).clone();
+ valueMeta.setName(column.targetKey());
+ groupRowMeta.addValueMeta(valueMeta);
+ rowCount = Math.max(rowCount, sourceRows.size());
+ }
+
+ RowBuffer rowBuffer = new RowBuffer(groupRowMeta);
+ for (int r = 0; r < rowCount; r++) {
+ Object[] row = RowDataUtil.allocateRowData(columns.size());
+ for (int c = 0; c < columns.size(); c++) {
+ GroupColumn column = columns.get(c);
+ List<RowMetaAndData> sourceRows =
rowMap.get(column.sourceTransformName());
+ if (r < sourceRows.size()) {
+ row[c] = sourceRows.get(r).getData()[column.columnIndex()];
+ }
+ }
+ rowBuffer.getBuffer().add(row);
+ }
+ return rowBuffer;
}
+ /**
+ * A single streamed column of an injection group: which injection key it
feeds, which source
+ * transform it is read from, and the index of the source field in that
transform's row.
+ */
+ record GroupColumn(String targetKey, String sourceTransformName, int
columnIndex) {}
+
private static void addConstantToGroupData(
MetaInjectMapping mapping, Map<String, RowBuffer> injectionGroupData,
String groupKey) {
// We need to add or extend a single row in a row buffer for the given
group.
@@ -494,6 +513,41 @@ public class MetaInject extends
BaseTransform<MetaInjectMeta, MetaInjectData> {
rows.set(0, row);
}
+ /**
+ * Merge the constant values collected for a group (a single row holding one
value per constant
+ * key) into every streamed row of that same group. Without this, a group
that mixes streamed keys
+ * and constant keys would lose its constants when the streamed buffer
replaces the constant
+ * buffer in injectionGroupData
+ */
+ // Package-private for unit testing (see MetaInjectTest).
+ static void mergeConstantsIntoGroupBuffer(RowBuffer streamedBuffer,
RowBuffer constantBuffer) {
+ List<Object[]> constantRows = constantBuffer.getBuffer();
+ if (constantRows.isEmpty()) {
+ return;
+ }
+ IRowMeta streamedMeta = streamedBuffer.getRowMeta();
+ IRowMeta constantMeta = constantBuffer.getRowMeta();
+ Object[] constantValues = constantRows.getFirst();
+ int streamedSize = streamedMeta.size();
+ int constantSize = constantMeta.size();
+
+ // Extend the metadata with the constant columns.
+ //
+ for (int c = 0; c < constantSize; c++) {
+ streamedMeta.addValueMeta(constantMeta.getValueMeta(c).clone());
+ }
+ // Extend every streamed row with the (identical) constant values.
+ //
+ List<Object[]> streamedRows = streamedBuffer.getBuffer();
+ for (int r = 0; r < streamedRows.size(); r++) {
+ Object[] row = RowDataUtil.createResizedCopy(streamedRows.get(r),
streamedMeta.size());
+ for (int c = 0; c < constantSize; c++) {
+ row[streamedSize + c] = constantValues[c];
+ }
+ streamedRows.set(r, row);
+ }
+ }
+
private static String lookupGroupKey(
Map<String, Set<String>> groupKeysMap, String targetAttributeKey) {
// Find the first group that matches.
@@ -640,8 +694,8 @@ public class MetaInject extends
BaseTransform<MetaInjectMeta, MetaInjectData> {
if (isDetailed()) {
logDetailed("Handing transform '" + targetTransform + "' injection!");
}
- BeanInjectionInfo injectionInfo = new
BeanInjectionInfo(targetTransformMeta.getClass());
- BeanInjector injector = new BeanInjector(injectionInfo, metadataProvider);
+ BeanInjectionInfo<?> injectionInfo = new
BeanInjectionInfo<>(targetTransformMeta.getClass());
+ BeanInjector<?> injector = new BeanInjector<>(injectionInfo,
metadataProvider);
// Collect all the metadata for this target transform...
//
@@ -706,21 +760,22 @@ public class MetaInject extends
BaseTransform<MetaInjectMeta, MetaInjectData> {
* @param targetTransform
* @param targetTransformMeta
*/
- private void newInjectionConstants(
+ // Package-private for unit testing of the constant-injection path (see
MetaInjectTest).
+ void newInjectionConstants(
IVariables variables, String targetTransform, ITransformMeta
targetTransformMeta)
throws HopException {
if (isDetailed()) {
logDetailed("Handing transform '" + targetTransform + "' constants
injection!");
}
- BeanInjectionInfo injectionInfo = new
BeanInjectionInfo(targetTransformMeta.getClass());
- BeanInjector injector = new BeanInjector(injectionInfo, metadataProvider);
+ BeanInjectionInfo<?> injectionInfo = new
BeanInjectionInfo<>(targetTransformMeta.getClass());
+ BeanInjector<?> injector = new BeanInjector<>(injectionInfo,
metadataProvider);
// Collect all the metadata for this target transform...
//
for (MetaInjectMapping mapping : meta.getMappings()) {
if (mapping.getTargetTransformName().equalsIgnoreCase(targetTransform)
- && StringUtils.isNotEmpty(mapping.getSourceTransformName())) {
+ && StringUtils.isEmpty(mapping.getSourceTransformName())) {
// This is the transform to collect data for...
// We also know which transform to read the data from. (source)
//
@@ -828,10 +883,10 @@ public class MetaInject extends
BaseTransform<MetaInjectMeta, MetaInjectData> {
PipelineMeta injectedPipelineMeta,
Set<String> unavailableTargetTransforms) {
Set<MetaInjectMapping> missingKeys = new HashSet<>();
- Map<String, BeanInjectionInfo> beanInfos =
getUsedTransformBeanInfos(injectedPipelineMeta);
+ Map<String, BeanInjectionInfo<?>> beanInfos =
getUsedTransformBeanInfos(injectedPipelineMeta);
for (MetaInjectMapping mapping : mappings) {
if
(!unavailableTargetTransforms.contains(mapping.getTargetTransformName())) {
- BeanInjectionInfo info =
beanInfos.get(mapping.getTargetTransformName().toUpperCase());
+ BeanInjectionInfo<?> info =
beanInfos.get(mapping.getTargetTransformName().toUpperCase());
if (info != null &&
!info.getProperties().containsKey(mapping.getTargetAttributeKey())) {
missingKeys.add(mapping);
}
@@ -840,13 +895,13 @@ public class MetaInject extends
BaseTransform<MetaInjectMeta, MetaInjectData> {
return missingKeys;
}
- private static Map<String, BeanInjectionInfo> getUsedTransformBeanInfos(
+ private static Map<String, BeanInjectionInfo<?>> getUsedTransformBeanInfos(
PipelineMeta pipelineMeta) {
- Map<String, BeanInjectionInfo> res = new HashMap<>();
+ Map<String, BeanInjectionInfo<?>> res = new HashMap<>();
for (TransformMeta transformMeta : pipelineMeta.getUsedTransforms()) {
Class<? extends ITransformMeta> transformMetaClass =
transformMeta.getTransform().getClass();
if (BeanInjectionInfo.isInjectionSupported(transformMetaClass)) {
- res.put(transformMeta.getName().toUpperCase(), new
BeanInjectionInfo(transformMetaClass));
+ res.put(transformMeta.getName().toUpperCase(), new
BeanInjectionInfo<>(transformMetaClass));
}
}
return res;
diff --git
a/plugins/transforms/metainject/src/test/java/org/apache/hop/pipeline/transforms/metainject/MetaInjectTest.java
b/plugins/transforms/metainject/src/test/java/org/apache/hop/pipeline/transforms/metainject/MetaInjectTest.java
index d49f94ca2c..e364b40c9a 100644
---
a/plugins/transforms/metainject/src/test/java/org/apache/hop/pipeline/transforms/metainject/MetaInjectTest.java
+++
b/plugins/transforms/metainject/src/test/java/org/apache/hop/pipeline/transforms/metainject/MetaInjectTest.java
@@ -17,20 +17,31 @@
package org.apache.hop.pipeline.transforms.metainject;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
+import org.apache.hop.core.RowMetaAndData;
import org.apache.hop.core.injection.Injection;
import org.apache.hop.core.injection.InjectionSupported;
import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.RowBuffer;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaString;
import org.apache.hop.metadata.api.IHopMetadataProvider;
import org.apache.hop.pipeline.Pipeline;
import org.apache.hop.pipeline.PipelineMeta;
@@ -132,6 +143,165 @@ class MetaInjectTest {
}
}
+ /**
+ * Regression test for <a
href="https://github.com/apache/hop/issues/7246">#7246</a>: a constant
+ * value (a mapping without a source transform) must be injected into a
legacy
+ * {@code @InjectionSupported} template transform.
+ */
+ @Test
+ void newInjectionConstants_injectsConstantWithoutSourceTransform() throws
Exception {
+ InjectableTestTransformMeta targetMeta = new InjectableTestTransformMeta();
+
+ // A "constant" mapping has no source transform; the literal value to
inject is held in the
+ // source field of the mapping.
+ MetaInjectMapping constantMapping = new MetaInjectMapping();
+ constantMapping.setTargetTransformName(TEST_TARGET_TRANSFORM_NAME);
+ constantMapping.setTargetAttributeKey("THERE");
+ constantMapping.setSourceTransformName(null);
+ constantMapping.setSourceField(TEST_VALUE);
+
+
when(metaInject.getMeta().getMappings()).thenReturn(Collections.singletonList(constantMapping));
+
+ metaInject.newInjectionConstants(metaInject, TEST_TARGET_TRANSFORM_NAME,
targetMeta);
+
+ assertEquals(TEST_VALUE, targetMeta.there);
+ }
+
+ /**
+ * A mapping that streams from a source transform is handled by {@code
newInjection()} and must
+ * not be treated as a constant here. Otherwise the source field
<em>name</em> would be injected
+ * as a literal value, corrupting the streamed injection.
+ */
+ @Test
+ void newInjectionConstants_ignoresMappingWithSourceTransform() throws
Exception {
+ InjectableTestTransformMeta targetMeta = new InjectableTestTransformMeta();
+
+ MetaInjectMapping streamedMapping = new MetaInjectMapping();
+ streamedMapping.setTargetTransformName(TEST_TARGET_TRANSFORM_NAME);
+ streamedMapping.setTargetAttributeKey("THERE");
+ streamedMapping.setSourceTransformName(TEST_SOURCE_TRANSFORM_NAME);
+ streamedMapping.setSourceField(TEST_FIELD);
+
+
when(metaInject.getMeta().getMappings()).thenReturn(Collections.singletonList(streamedMapping));
+
+ metaInject.newInjectionConstants(metaInject, TEST_TARGET_TRANSFORM_NAME,
targetMeta);
+
+ assertNull(targetMeta.there);
+ }
+
+ /**
+ * Regression test for <a
href="https://github.com/apache/hop/issues/7246">#7246</a>: when an
+ * injection group mixes a streamed key and a constant key, the constant
value must be merged into
+ * every streamed row instead of being dropped when the streamed buffer is
stored.
+ */
+ @Test
+ void mergeConstantsIntoGroupBuffer_addsConstantToEveryStreamedRow() {
+ RowMeta streamedMeta = new RowMeta();
+ streamedMeta.addValueMeta(new ValueMetaString("FIELD_NAME"));
+ List<Object[]> streamedRows = new ArrayList<>();
+ streamedRows.add(new Object[] {"x"});
+ streamedRows.add(new Object[] {"y"});
+ RowBuffer streamed = new RowBuffer(streamedMeta, streamedRows);
+
+ RowMeta constantMeta = new RowMeta();
+ constantMeta.addValueMeta(new ValueMetaString("REPLACE_VALUE"));
+ List<Object[]> constantRows = new ArrayList<>();
+ constantRows.add(new Object[] {"INJECTED"});
+ RowBuffer constant = new RowBuffer(constantMeta, constantRows);
+
+ MetaInject.mergeConstantsIntoGroupBuffer(streamed, constant);
+
+ // The constant column is appended to the metadata...
+ assertEquals(2, streamed.getRowMeta().size());
+ assertEquals("FIELD_NAME",
streamed.getRowMeta().getValueMeta(0).getName());
+ assertEquals("REPLACE_VALUE",
streamed.getRowMeta().getValueMeta(1).getName());
+ // ...and the constant value is present in every streamed row. (Hop
over-allocates the row
+ // array, so we check the cells by index rather than comparing the whole
array.)
+ assertEquals(2, streamed.getBuffer().size());
+ assertEquals("x", streamed.getBuffer().get(0)[0]);
+ assertEquals("INJECTED", streamed.getBuffer().get(0)[1]);
+ assertEquals("y", streamed.getBuffer().get(1)[0]);
+ assertEquals("INJECTED", streamed.getBuffer().get(1)[1]);
+ }
+
+ /**
+ * Regression test for the multi-data-grid scenario reported on top of <a
+ * href="https://github.com/apache/hop/issues/7246">#7246</a>: a single
injection group of a
+ * {@code @HopMetadataProperty} template transform (e.g. the Select Values
"fields" group) can be
+ * fed from two <em>different</em> source transforms - FIELD_NAME from one
data grid, FIELD_RENAME
+ * from another. Every mapping must resolve its own source field and the
columns must be zipped
+ * together by row index. The rewrite used to cache the group row layout
from the first source
+ * only, so the second source's field was looked up in the wrong layout and
injection failed with
+ * "... to inject could not be found". This worked in 2.17 (legacy
BeanInjector path).
+ *
+ * <p>This mirrors {@code injectMetadataIntoTemplateTransform}, which calls
{@code
+ * collectDataForOneMappingGroup} once per mapping and then {@code
buildGroupRowBuffer} to
+ * materialize the group.
+ */
+ @Test
+ void groupFedFromTwoSourcesZipsBothColumns() {
+ String groupKey = "FIELDS";
+
+ // FIELD_NAME is injected from the "field names" data grid (column "name").
+ IRowMeta namesMeta = new RowMeta();
+ namesMeta.addValueMeta(new ValueMetaString("name"));
+ List<RowMetaAndData> nameRows = new ArrayList<>();
+ nameRows.add(new RowMetaAndData(namesMeta, "field1"));
+ nameRows.add(new RowMetaAndData(namesMeta, "field2"));
+
+ MetaInjectMapping nameMapping = new MetaInjectMapping();
+ nameMapping.setTargetTransformName("Select values");
+ nameMapping.setTargetAttributeKey("FIELD_NAME");
+ nameMapping.setTargetDetail(true);
+ nameMapping.setSourceTransformName("field names");
+ nameMapping.setSourceField("name");
+
+ // FIELD_RENAME is injected from a *different* data grid ("field renames",
column "newname").
+ IRowMeta renamesMeta = new RowMeta();
+ renamesMeta.addValueMeta(new ValueMetaString("newname"));
+ List<RowMetaAndData> renameRows = new ArrayList<>();
+ renameRows.add(new RowMetaAndData(renamesMeta, "renamed1"));
+ renameRows.add(new RowMetaAndData(renamesMeta, "renamed2"));
+
+ MetaInjectMapping renameMapping = new MetaInjectMapping();
+ renameMapping.setTargetTransformName("Select values");
+ renameMapping.setTargetAttributeKey("FIELD_RENAME");
+ renameMapping.setTargetDetail(true);
+ renameMapping.setSourceTransformName("field renames");
+ renameMapping.setSourceField("newname");
+
+ Map<String, List<RowMetaAndData>> rowMap = new HashMap<>();
+ rowMap.put("field names", nameRows);
+ rowMap.put("field renames", renameRows);
+
+ // Both keys belong to the same injection group, so they accumulate into
the same column list.
+ Map<String, List<MetaInject.GroupColumn>> groupColumnsMap = new
HashMap<>();
+
+ // Each mapping must resolve its own source field; neither call may throw
"could not be found".
+ assertDoesNotThrow(
+ () ->
+ MetaInject.collectDataForOneMappingGroup(
+ nameMapping, groupColumnsMap, groupKey, nameRows));
+ assertDoesNotThrow(
+ () ->
+ MetaInject.collectDataForOneMappingGroup(
+ renameMapping, groupColumnsMap, groupKey, renameRows),
+ "Injecting a group from a second data grid must not fail with "
+ + "'The field ... to inject could not be found'");
+
+ // The two columns, from two different grids, must zip together into the
group rows.
+ RowBuffer groupBuffer =
MetaInject.buildGroupRowBuffer(groupColumnsMap.get(groupKey), rowMap);
+
+ assertEquals(2, groupBuffer.getRowMeta().size());
+ assertEquals("FIELD_NAME",
groupBuffer.getRowMeta().getValueMeta(0).getName());
+ assertEquals("FIELD_RENAME",
groupBuffer.getRowMeta().getValueMeta(1).getName());
+ assertEquals(2, groupBuffer.getBuffer().size());
+ assertEquals("field1", groupBuffer.getBuffer().get(0)[0]);
+ assertEquals("renamed1", groupBuffer.getBuffer().get(0)[1]);
+ assertEquals("field2", groupBuffer.getBuffer().get(1)[0]);
+ assertEquals("renamed2", groupBuffer.getBuffer().get(1)[1]);
+ }
+
private PipelineMeta mockSingleTransformPipelineMeta(
final String targetTransformName, ITransformMeta smi) {
TransformMeta transformMeta = mock(TransformMeta.class);
diff --git a/pom.xml b/pom.xml
index 40a5225210..f3da4b6cd8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -153,7 +153,7 @@
<maven.compiler.target>${target.jdk.version}</maven.compiler.target>
<minimalMavenBuildVersion>3.6.3</minimalMavenBuildVersion>
<mockito-core.version>5.22.0</mockito-core.version>
- <netty.version>4.2.13.Final</netty.version>
+ <netty.version>4.2.15.Final</netty.version>
<objenesis.version>3.5</objenesis.version>
<opentelemetry.version>1.62.0</opentelemetry.version>
<org.eclipse.platform.version>3.132.0</org.eclipse.platform.version>