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 f7d6103dd0 fix multiple copies of pipeline executor, fixes #6340
(#7009)
f7d6103dd0 is described below
commit f7d6103dd06ef2cde10ed9e197788a3fcfff3984
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Thu Apr 16 15:26:17 2026 +0200
fix multiple copies of pipeline executor, fixes #6340 (#7009)
---
.../hop/pipeline/TransformWithMappingMeta.java | 25 +-
...peline-executor-multicopy-fromfield-child-a.hpl | 202 +++++++++
...peline-executor-multicopy-fromfield-child-b.hpl | 202 +++++++++
...-pipeline-executor-multicopy-fromfield-main.hpl | 481 +++++++++++++++++++++
...pipeline-executor-group10-segmented-child-a.hpl | 158 +++++++
...pipeline-executor-group10-segmented-child-b.hpl | 158 +++++++
...eline-executor-group10-segmented-main-1copy.hpl | 362 ++++++++++++++++
...eline-executor-group10-segmented-main-4copy.hpl | 362 ++++++++++++++++
...cutor-group10-segmented-main-midgroup-1copy.hpl | 312 +++++++++++++
...cutor-group10-segmented-main-midgroup-4copy.hpl | 312 +++++++++++++
...-0097-pipeline-executor-multicopy-fromfield.hwf | 85 ++++
...in-0098-pipeline-executor-group10-segmented.hwf | 181 ++++++++
.../pipelineexecutor/PipelineExecutor.java | 13 +-
.../pipelineexecutor/PipelineExecutorData.java | 7 +
.../PipelineExecutorMetaCoverageTest.java | 357 +++++++++++++++
.../pipelineexecutor/PipelineExecutorMetaTest.java | 40 ++
.../pipelineexecutor/PipelineExecutorTest.java | 355 +++++++++++++++
.../transforms/pipelineexecutor/minimal-child.hpl | 103 +++++
18 files changed, 3711 insertions(+), 4 deletions(-)
diff --git
a/engine/src/main/java/org/apache/hop/pipeline/TransformWithMappingMeta.java
b/engine/src/main/java/org/apache/hop/pipeline/TransformWithMappingMeta.java
index e0b78410d6..96f9ebd906 100644
--- a/engine/src/main/java/org/apache/hop/pipeline/TransformWithMappingMeta.java
+++ b/engine/src/main/java/org/apache/hop/pipeline/TransformWithMappingMeta.java
@@ -98,8 +98,29 @@ public abstract class TransformWithMappingMeta<Main extends
ITransform, Data ext
IHopMetadataProvider metadataProvider,
IVariables variables)
throws HopException {
+ return loadMappingMeta(executorMeta, null, metadataProvider, variables);
+ }
+
+ /**
+ * Loads child pipeline metadata from a file.
+ *
+ * @param explicitPipelineFilename when non-empty, this path is loaded and
resolved instead of the
+ * filename stored on {@code executorMeta}. Use when the pipeline path
is taken from an
+ * incoming row at runtime so multiple transform copies must not mutate
shared meta.
+ */
+ public static synchronized PipelineMeta loadMappingMeta(
+ TransformWithMappingMeta executorMeta,
+ String explicitPipelineFilename,
+ IHopMetadataProvider metadataProvider,
+ IVariables variables)
+ throws HopException {
PipelineMeta mappingPipelineMeta = null;
+ String filenameToUse =
+ !Utils.isEmpty(explicitPipelineFilename)
+ ? explicitPipelineFilename
+ : executorMeta.getFilename();
+
CurrentDirectoryResolver r = new CurrentDirectoryResolver();
// send restricted parentVariables with several important options
// Otherwise we destroy child variables and the option "Inherit all
variables from the pipeline"
@@ -108,9 +129,9 @@ public abstract class TransformWithMappingMeta<Main extends
ITransform, Data ext
r.resolveCurrentDirectory(
getVarSpaceOnlyWithRequiredParentVars(variables),
executorMeta.getParentTransformMeta(),
- executorMeta.getFilename());
+ filenameToUse);
- String realFilename = tmpSpace.resolve(executorMeta.getFilename());
+ String realFilename = tmpSpace.resolve(filenameToUse);
if (variables != null) {
// This is a parent pipeline and parent variable should work here. A
child file name can be
// resolved via parent variables.
diff --git
a/integration-tests/transforms/0097-pipeline-executor-multicopy-fromfield-child-a.hpl
b/integration-tests/transforms/0097-pipeline-executor-multicopy-fromfield-child-a.hpl
new file mode 100644
index 0000000000..8ec4bf50b3
--- /dev/null
+++
b/integration-tests/transforms/0097-pipeline-executor-multicopy-fromfield-child-a.hpl
@@ -0,0 +1,202 @@
+<?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>
+ <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>
+ <parameter>
+ <name>P_EXPECTED_CHILD</name>
+ <description>Marker from parent row; must be A in this
pipeline.</description>
+ </parameter>
+ </parameters>
+ <name>0097-pipeline-executor-multicopy-fromfield-child-a</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Child A: succeeds only when P_EXPECTED_CHILD is
A.</description>
+ <created_user>-</created_user>
+ <modified_user>-</modified_user>
+ <created_date>2026/04/16 12:00:00.000</created_date>
+ <modified_date>2026/04/16 12:00:00.000</modified_date>
+ </info>
+ <transform>
+ <type>Abort</type>
+ <name>Abort wrong pipeline or parameter</name>
+ <row_threshold>0</row_threshold>
+ <always_log_rows>Y</always_log_rows>
+ <abort_option>ABORT_WITH_ERROR</abort_option>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>752</xloc>
+ <yloc>272</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>RowGenerator</type>
+ <name>Generate rows</name>
+ <never_ending>N</never_ending>
+ <interval_in_ms>5000</interval_in_ms>
+ <row_time_field>now</row_time_field>
+ <last_time_field>FiveSecondsAgo</last_time_field>
+ <limit>1</limit>
+ <fields/>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>128</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>Constant</type>
+ <name>Label this pipeline A</name>
+ <fields>
+ <field>
+ <nullif>A</nullif>
+ <name>local_label</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ </fields>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>544</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>Dummy</type>
+ <name>OK</name>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>960</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>FilterRows</type>
+ <name>Parameter matches this child</name>
+ <compare>
+ <condition>
+ <negated>N</negated>
+ <operator>-</operator>
+ <leftvalue>expected_from_param</leftvalue>
+ <function>=</function>
+ <rightvalue>local_label</rightvalue>
+ <conditions/>
+ </condition>
+ </compare>
+ <send_true_to>OK</send_true_to>
+ <send_false_to>Abort wrong pipeline or parameter</send_false_to>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>752</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>GetVariable</type>
+ <name>Read expected from parameter</name>
+ <fields>
+ <field>
+ <name>expected_from_param</name>
+ <variable>${P_EXPECTED_CHILD}</variable>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <trim_type>none</trim_type>
+ </field>
+ </fields>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>336</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <order>
+ <hop>
+ <from>Generate rows</from>
+ <to>Read expected from parameter</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Read expected from parameter</from>
+ <to>Label this pipeline A</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Label this pipeline A</from>
+ <to>Parameter matches this child</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Parameter matches this child</from>
+ <to>OK</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Parameter matches this child</from>
+ <to>Abort wrong pipeline or parameter</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <notepads/>
+ <attributes/>
+ <transform_error_handling/>
+</pipeline>
diff --git
a/integration-tests/transforms/0097-pipeline-executor-multicopy-fromfield-child-b.hpl
b/integration-tests/transforms/0097-pipeline-executor-multicopy-fromfield-child-b.hpl
new file mode 100644
index 0000000000..12aa97dff2
--- /dev/null
+++
b/integration-tests/transforms/0097-pipeline-executor-multicopy-fromfield-child-b.hpl
@@ -0,0 +1,202 @@
+<?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>
+ <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>
+ <parameter>
+ <name>P_EXPECTED_CHILD</name>
+ <description>Marker from parent row; must be B in this
pipeline.</description>
+ </parameter>
+ </parameters>
+ <name>0097-pipeline-executor-multicopy-fromfield-child-b</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Child B: succeeds only when P_EXPECTED_CHILD is
B.</description>
+ <created_user>-</created_user>
+ <modified_user>-</modified_user>
+ <created_date>2026/04/16 12:00:00.000</created_date>
+ <modified_date>2026/04/16 12:00:00.000</modified_date>
+ </info>
+ <transform>
+ <type>Abort</type>
+ <name>Abort wrong pipeline or parameter</name>
+ <row_threshold>0</row_threshold>
+ <always_log_rows>Y</always_log_rows>
+ <abort_option>ABORT_WITH_ERROR</abort_option>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>740</xloc>
+ <yloc>288</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>RowGenerator</type>
+ <name>Generate rows</name>
+ <never_ending>N</never_ending>
+ <interval_in_ms>5000</interval_in_ms>
+ <row_time_field>now</row_time_field>
+ <last_time_field>FiveSecondsAgo</last_time_field>
+ <limit>1</limit>
+ <fields/>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>128</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>Constant</type>
+ <name>Label this pipeline B</name>
+ <fields>
+ <field>
+ <nullif>B</nullif>
+ <name>local_label</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ </fields>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>536</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>Dummy</type>
+ <name>OK</name>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>944</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>FilterRows</type>
+ <name>Parameter matches this child</name>
+ <compare>
+ <condition>
+ <negated>N</negated>
+ <operator>-</operator>
+ <leftvalue>expected_from_param</leftvalue>
+ <function>=</function>
+ <rightvalue>local_label</rightvalue>
+ <conditions/>
+ </condition>
+ </compare>
+ <send_true_to>OK</send_true_to>
+ <send_false_to>Abort wrong pipeline or parameter</send_false_to>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>740</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>GetVariable</type>
+ <name>Read expected from parameter</name>
+ <fields>
+ <field>
+ <name>expected_from_param</name>
+ <variable>${P_EXPECTED_CHILD}</variable>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <trim_type>none</trim_type>
+ </field>
+ </fields>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>332</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <order>
+ <hop>
+ <from>Generate rows</from>
+ <to>Read expected from parameter</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Read expected from parameter</from>
+ <to>Label this pipeline B</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Label this pipeline B</from>
+ <to>Parameter matches this child</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Parameter matches this child</from>
+ <to>OK</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Parameter matches this child</from>
+ <to>Abort wrong pipeline or parameter</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <notepads/>
+ <attributes/>
+ <transform_error_handling/>
+</pipeline>
diff --git
a/integration-tests/transforms/0097-pipeline-executor-multicopy-fromfield-main.hpl
b/integration-tests/transforms/0097-pipeline-executor-multicopy-fromfield-main.hpl
new file mode 100644
index 0000000000..8dbcc78be4
--- /dev/null
+++
b/integration-tests/transforms/0097-pipeline-executor-multicopy-fromfield-main.hpl
@@ -0,0 +1,481 @@
+<?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>
+ <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>0097-pipeline-executor-multicopy-fromfield-main</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Regression: Pipeline executor with multiple copies must run
the pipeline path from the incoming row field, and parameters must stay aligned
with that row. A static filename in the transform meta points at child-b only
(similar to misconfiguration); execution must still follow child_path per
row.</description>
+ <created_user>-</created_user>
+ <modified_user>-</modified_user>
+ <created_date>2026/04/16 12:00:00.000</created_date>
+ <modified_date>2026/04/16 12:00:00.000</modified_date>
+ </info>
+ <transform>
+ <type>DataGrid</type>
+ <name>Data grid</name>
+ <fields>
+ <field>
+ <currency/>
+ <decimal/>
+ <group/>
+ <name>sort_key</name>
+ <type>Integer</type>
+ <format/>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <currency/>
+ <decimal/>
+ <group/>
+ <name>child_path</name>
+ <type>String</type>
+ <format/>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <currency/>
+ <decimal/>
+ <group/>
+ <name>expected_child</name>
+ <type>String</type>
+ <format/>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ </fields>
+ <data>
+ <line>
+ <item>1</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>2</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>3</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>4</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>5</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>6</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>7</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>8</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>9</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>10</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>11</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>12</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>13</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>14</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>15</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>16</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>17</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>18</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>19</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>20</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>21</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>22</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>23</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>24</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>25</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>26</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>27</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>28</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>29</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>30</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>31</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>32</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>33</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>34</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>35</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>36</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>37</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>38</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>39</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>40</item>
+
<item>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ </data>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>128</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>SortRows</type>
+ <name>Sort rows</name>
+ <fields>
+ <field>
+ <name>sort_key</name>
+ <ascending>Y</ascending>
+ <case_sensitive>N</case_sensitive>
+ <collator_enabled>N</collator_enabled>
+ <collator_strength>0</collator_strength>
+ <presorted>N</presorted>
+ </field>
+ </fields>
+ <directory>${java.io.tmpdir}</directory>
+ <sort_prefix>out</sort_prefix>
+ <sort_size>1000000</sort_size>
+ <unique_rows>N</unique_rows>
+ <compress>N</compress>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>352</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>PipelineExecutor</type>
+ <name>Pipeline executor multicopy</name>
+ <run_configuration>local</run_configuration>
+ <filenameInField>Y</filenameInField>
+ <filenameField>child_path</filenameField>
+ <group_size>1</group_size>
+ <parameters>
+ <variable_mapping>
+ <variable>P_EXPECTED_CHILD</variable>
+ <field>expected_child</field>
+ </variable_mapping>
+ </parameters>
+ <inherit_all_vars>N</inherit_all_vars>
+ <execution_result_target_transform>Assert child pipelines
succeeded</execution_result_target_transform>
+ <execution_time_field>ExecutionTime</execution_time_field>
+ <execution_result_field>ExecutionResult</execution_result_field>
+ <execution_errors_field>ExecutionNrErrors</execution_errors_field>
+ <execution_lines_read_field>ExecutionLinesRead</execution_lines_read_field>
+
<execution_lines_written_field>ExecutionLinesWritten</execution_lines_written_field>
+
<execution_lines_input_field>ExecutionLinesInput</execution_lines_input_field>
+
<execution_lines_output_field>ExecutionLinesOutput</execution_lines_output_field>
+
<execution_lines_rejected_field>ExecutionLinesRejected</execution_lines_rejected_field>
+
<execution_lines_updated_field>ExecutionLinesUpdated</execution_lines_updated_field>
+
<execution_lines_deleted_field>ExecutionLinesDeleted</execution_lines_deleted_field>
+
<execution_files_retrieved_field>ExecutionFilesRetrieved</execution_files_retrieved_field>
+
<execution_exit_status_field>ExecutionExitStatus</execution_exit_status_field>
+ <execution_log_text_field>ExecutionLogText</execution_log_text_field>
+
<execution_log_channelid_field>ExecutionLogChannelId</execution_log_channelid_field>
+
<filename>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-child-b.hpl</filename>
+ <distribute>Y</distribute>
+ <copies>10</copies>
+ <GUI>
+ <xloc>576</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>Abort</type>
+ <name>Abort on child failure</name>
+ <row_threshold>0</row_threshold>
+ <always_log_rows>Y</always_log_rows>
+ <abort_option>ABORT_WITH_ERROR</abort_option>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>800</xloc>
+ <yloc>320</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>FilterRows</type>
+ <name>Assert child pipelines succeeded</name>
+ <compare>
+ <condition>
+ <negated>Y</negated>
+ <operator>-</operator>
+ <leftvalue>ExecutionNrErrors</leftvalue>
+ <function>></function>
+ <value>
+ <name>constant</name>
+ <type>Integer</type>
+ <text>0</text>
+ <length>-1</length>
+ <precision>0</precision>
+ <isnull>N</isnull>
+ <mask>####0;-####0</mask>
+ </value>
+ <conditions/>
+ </condition>
+ </compare>
+ <send_true_to>OK</send_true_to>
+ <send_false_to>Abort on child failure</send_false_to>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>800</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>Dummy</type>
+ <name>OK</name>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>1008</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <order>
+ <hop>
+ <from>Data grid</from>
+ <to>Sort rows</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Sort rows</from>
+ <to>Pipeline executor multicopy</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Pipeline executor multicopy</from>
+ <to>Assert child pipelines succeeded</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Assert child pipelines succeeded</from>
+ <to>OK</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Assert child pipelines succeeded</from>
+ <to>Abort on child failure</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <notepads>
+ <notepad>
+ <note>40 rows alternate child A / child B. Pipeline executor uses 10
copies, filename from field child_path, and maps expected_child to
P_EXPECTED_CHILD.
+Each child pipeline aborts unless P_EXPECTED_CHILD matches that child (A vs
B).
+This fails when copies share the wrong pipeline template or mismatched
parameters.</note>
+ <fontname>.AppleSystemUIFont</fontname>
+ <fontsize>13</fontsize>
+ <fontbold>N</fontbold>
+ <fontitalic>N</fontitalic>
+ <fontcolorred>14</fontcolorred>
+ <fontcolorgreen>58</fontcolorgreen>
+ <fontcolorblue>90</fontcolorblue>
+ <backgroundcolorred>201</backgroundcolorred>
+ <backgroundcolorgreen>232</backgroundcolorgreen>
+ <backgroundcolorblue>251</backgroundcolorblue>
+ <bordercolorred>14</bordercolorred>
+ <bordercolorgreen>58</bordercolorgreen>
+ <bordercolorblue>90</bordercolorblue>
+ <xloc>48</xloc>
+ <yloc>16</yloc>
+ <width>520</width>
+ <height>120</height>
+ </notepad>
+ </notepads>
+ <attributes/>
+ <transform_error_handling/>
+</pipeline>
diff --git
a/integration-tests/transforms/0098-pipeline-executor-group10-segmented-child-a.hpl
b/integration-tests/transforms/0098-pipeline-executor-group10-segmented-child-a.hpl
new file mode 100644
index 0000000000..8d8868f9bd
--- /dev/null
+++
b/integration-tests/transforms/0098-pipeline-executor-group10-segmented-child-a.hpl
@@ -0,0 +1,158 @@
+<?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>
+ <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>0098-pipeline-executor-group10-segmented-child-a</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Validates result rows from parent: every row must have
expected_child=A (batch size may be less than parent group_size when the child
path changes mid-group).</description>
+ <created_user>-</created_user>
+ <modified_user>-</modified_user>
+ <created_date>2026/04/16 12:00:00.000</created_date>
+ <modified_date>2026/04/16 12:00:00.000</modified_date>
+ </info>
+ <transform>
+ <type>Abort</type>
+ <name>Abort wrong row</name>
+ <row_threshold>0</row_threshold>
+ <always_log_rows>Y</always_log_rows>
+ <abort_option>ABORT_WITH_ERROR</abort_option>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>352</xloc>
+ <yloc>272</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>FilterRows</type>
+ <name>expected_child is A</name>
+ <compare>
+ <condition>
+ <negated>N</negated>
+ <operator>-</operator>
+ <leftvalue>expected_child</leftvalue>
+ <function>=</function>
+ <value>
+ <name>constant</name>
+ <type>String</type>
+ <text>A</text>
+ <length>-1</length>
+ <precision>-1</precision>
+ <isnull>N</isnull>
+ </value>
+ <conditions/>
+ </condition>
+ </compare>
+ <send_true_to>OK</send_true_to>
+ <send_false_to>Abort wrong row</send_false_to>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>352</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>RowsFromResult</type>
+ <name>Get rows from result</name>
+ <fields>
+ <field>
+ <name>sort_key</name>
+ <type>Integer</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ </field>
+ <field>
+ <name>child_path</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ </field>
+ <field>
+ <name>expected_child</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ </field>
+ </fields>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>128</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>Dummy</type>
+ <name>OK</name>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>496</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <order>
+ <hop>
+ <from>Get rows from result</from>
+ <to>expected_child is A</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>expected_child is A</from>
+ <to>OK</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>expected_child is A</from>
+ <to>Abort wrong row</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <notepads/>
+ <attributes/>
+ <transform_error_handling/>
+</pipeline>
diff --git
a/integration-tests/transforms/0098-pipeline-executor-group10-segmented-child-b.hpl
b/integration-tests/transforms/0098-pipeline-executor-group10-segmented-child-b.hpl
new file mode 100644
index 0000000000..2d989c3556
--- /dev/null
+++
b/integration-tests/transforms/0098-pipeline-executor-group10-segmented-child-b.hpl
@@ -0,0 +1,158 @@
+<?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>
+ <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>0098-pipeline-executor-group10-segmented-child-b</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Validates result rows from parent: every row must have
expected_child=B (batch size may be less than parent group_size when the child
path changes mid-group).</description>
+ <created_user>-</created_user>
+ <modified_user>-</modified_user>
+ <created_date>2026/04/16 12:00:00.000</created_date>
+ <modified_date>2026/04/16 12:00:00.000</modified_date>
+ </info>
+ <transform>
+ <type>Abort</type>
+ <name>Abort wrong row</name>
+ <row_threshold>0</row_threshold>
+ <always_log_rows>Y</always_log_rows>
+ <abort_option>ABORT_WITH_ERROR</abort_option>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>352</xloc>
+ <yloc>272</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>FilterRows</type>
+ <name>expected_child is B</name>
+ <compare>
+ <condition>
+ <negated>N</negated>
+ <operator>-</operator>
+ <leftvalue>expected_child</leftvalue>
+ <function>=</function>
+ <value>
+ <name>constant</name>
+ <type>String</type>
+ <text>B</text>
+ <length>-1</length>
+ <precision>-1</precision>
+ <isnull>N</isnull>
+ </value>
+ <conditions/>
+ </condition>
+ </compare>
+ <send_true_to>OK</send_true_to>
+ <send_false_to>Abort wrong row</send_false_to>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>352</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>RowsFromResult</type>
+ <name>Get rows from result</name>
+ <fields>
+ <field>
+ <name>sort_key</name>
+ <type>Integer</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ </field>
+ <field>
+ <name>child_path</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ </field>
+ <field>
+ <name>expected_child</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ </field>
+ </fields>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>128</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>Dummy</type>
+ <name>OK</name>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>512</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <order>
+ <hop>
+ <from>Get rows from result</from>
+ <to>expected_child is B</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>expected_child is B</from>
+ <to>OK</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>expected_child is B</from>
+ <to>Abort wrong row</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <notepads/>
+ <attributes/>
+ <transform_error_handling/>
+</pipeline>
diff --git
a/integration-tests/transforms/0098-pipeline-executor-group10-segmented-main-1copy.hpl
b/integration-tests/transforms/0098-pipeline-executor-group10-segmented-main-1copy.hpl
new file mode 100644
index 0000000000..247e984d35
--- /dev/null
+++
b/integration-tests/transforms/0098-pipeline-executor-group10-segmented-main-1copy.hpl
@@ -0,0 +1,362 @@
+<?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>
+ <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>0098-pipeline-executor-group10-segmented-main-1copy</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Regression: Pipeline executor group_size=10 with filename
from field. Twenty rows in two homogeneous batches (rows 1-10 child A, 11-20
child B). Each child run must receive only rows whose expected_child matches
that pipeline.</description>
+ <created_user>-</created_user>
+ <modified_user>-</modified_user>
+ <created_date>2026/04/16 12:00:00.000</created_date>
+ <modified_date>2026/04/16 12:00:00.000</modified_date>
+ </info>
+ <transform>
+ <type>DataGrid</type>
+ <name>Data grid</name>
+ <fields>
+ <field>
+ <name>sort_key</name>
+ <type>Integer</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <name>child_path</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <name>expected_child</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ </fields>
+ <data>
+ <line>
+ <item>1</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>2</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>3</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>4</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>5</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>6</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>7</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>8</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>9</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>10</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>11</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>12</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>13</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>14</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>15</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>16</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>17</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>18</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>19</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>20</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ </data>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>128</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>SortRows</type>
+ <name>Sort rows</name>
+ <fields>
+ <field>
+ <name>sort_key</name>
+ <ascending>Y</ascending>
+ <case_sensitive>N</case_sensitive>
+ <collator_enabled>N</collator_enabled>
+ <collator_strength>0</collator_strength>
+ <presorted>N</presorted>
+ </field>
+ </fields>
+ <directory>${java.io.tmpdir}</directory>
+ <sort_prefix>out0098</sort_prefix>
+ <sort_size>1000000</sort_size>
+ <unique_rows>N</unique_rows>
+ <compress>N</compress>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>352</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>PipelineExecutor</type>
+ <name>Pipeline executor grouped</name>
+ <run_configuration>local</run_configuration>
+ <filenameInField>Y</filenameInField>
+ <filenameField>child_path</filenameField>
+ <group_size>10</group_size>
+ <parameters/>
+ <inherit_all_vars>N</inherit_all_vars>
+ <execution_result_target_transform>Assert child pipelines
succeeded</execution_result_target_transform>
+ <execution_time_field>ExecutionTime</execution_time_field>
+ <execution_result_field>ExecutionResult</execution_result_field>
+ <execution_errors_field>ExecutionNrErrors</execution_errors_field>
+ <execution_lines_read_field>ExecutionLinesRead</execution_lines_read_field>
+
<execution_lines_written_field>ExecutionLinesWritten</execution_lines_written_field>
+
<execution_lines_input_field>ExecutionLinesInput</execution_lines_input_field>
+
<execution_lines_output_field>ExecutionLinesOutput</execution_lines_output_field>
+
<execution_lines_rejected_field>ExecutionLinesRejected</execution_lines_rejected_field>
+
<execution_lines_updated_field>ExecutionLinesUpdated</execution_lines_updated_field>
+
<execution_lines_deleted_field>ExecutionLinesDeleted</execution_lines_deleted_field>
+
<execution_files_retrieved_field>ExecutionFilesRetrieved</execution_files_retrieved_field>
+
<execution_exit_status_field>ExecutionExitStatus</execution_exit_status_field>
+ <execution_log_text_field>ExecutionLogText</execution_log_text_field>
+
<execution_log_channelid_field>ExecutionLogChannelId</execution_log_channelid_field>
+
<filename>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</filename>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>576</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>Abort</type>
+ <name>Abort on child failure</name>
+ <row_threshold>0</row_threshold>
+ <always_log_rows>Y</always_log_rows>
+ <abort_option>ABORT_WITH_ERROR</abort_option>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>800</xloc>
+ <yloc>320</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>FilterRows</type>
+ <name>Assert child pipelines succeeded</name>
+ <compare>
+ <condition>
+ <negated>Y</negated>
+ <operator>-</operator>
+ <leftvalue>ExecutionNrErrors</leftvalue>
+ <function>></function>
+ <value>
+ <name>constant</name>
+ <type>Integer</type>
+ <text>0</text>
+ <length>-1</length>
+ <precision>0</precision>
+ <isnull>N</isnull>
+ <mask>####0;-####0</mask>
+ </value>
+ <conditions/>
+ </condition>
+ </compare>
+ <send_true_to>OK</send_true_to>
+ <send_false_to>Abort on child failure</send_false_to>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>800</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>Dummy</type>
+ <name>OK</name>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>1024</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <order>
+ <hop>
+ <from>Data grid</from>
+ <to>Sort rows</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Sort rows</from>
+ <to>Pipeline executor grouped</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Pipeline executor grouped</from>
+ <to>Assert child pipelines succeeded</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Assert child pipelines succeeded</from>
+ <to>OK</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Assert child pipelines succeeded</from>
+ <to>Abort on child failure</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <notepads>
+ <notepad>
+ <note>group_size=10: two full batches (rows 1-10 child A, 11-20 child
B). filenameInField uses child_path; static filename in meta is child-b. Child
pipelines abort if any result row has the wrong expected_child. copies=1.</note>
+ <fontname>.AppleSystemUIFont</fontname>
+ <fontsize>13</fontsize>
+ <fontbold>N</fontbold>
+ <fontitalic>N</fontitalic>
+ <fontcolorred>14</fontcolorred>
+ <fontcolorgreen>58</fontcolorgreen>
+ <fontcolorblue>90</fontcolorblue>
+ <backgroundcolorred>201</backgroundcolorred>
+ <backgroundcolorgreen>232</backgroundcolorgreen>
+ <backgroundcolorblue>251</backgroundcolorblue>
+ <bordercolorred>14</bordercolorred>
+ <bordercolorgreen>58</bordercolorgreen>
+ <bordercolorblue>90</bordercolorblue>
+ <xloc>48</xloc>
+ <yloc>16</yloc>
+ <width>560</width>
+ <height>132</height>
+ </notepad>
+ </notepads>
+ <attributes/>
+ <transform_error_handling/>
+</pipeline>
diff --git
a/integration-tests/transforms/0098-pipeline-executor-group10-segmented-main-4copy.hpl
b/integration-tests/transforms/0098-pipeline-executor-group10-segmented-main-4copy.hpl
new file mode 100644
index 0000000000..30a0141a24
--- /dev/null
+++
b/integration-tests/transforms/0098-pipeline-executor-group10-segmented-main-4copy.hpl
@@ -0,0 +1,362 @@
+<?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>
+ <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>0098-pipeline-executor-group10-segmented-main-4copy</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Regression: Pipeline executor group_size=10 with filename
from field. Twenty rows in two homogeneous batches (rows 1-10 child A, 11-20
child B). Each child run must receive only rows whose expected_child matches
that pipeline. Four executor copies stress shared-meta races.</description>
+ <created_user>-</created_user>
+ <modified_user>-</modified_user>
+ <created_date>2026/04/16 12:00:00.000</created_date>
+ <modified_date>2026/04/16 12:00:00.000</modified_date>
+ </info>
+ <transform>
+ <type>DataGrid</type>
+ <name>Data grid</name>
+ <fields>
+ <field>
+ <name>sort_key</name>
+ <type>Integer</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <name>child_path</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <name>expected_child</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ </fields>
+ <data>
+ <line>
+ <item>1</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>2</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>3</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>4</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>5</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>6</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>7</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>8</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>9</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>10</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>11</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>12</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>13</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>14</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>15</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>16</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>17</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>18</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>19</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>20</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ </data>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>128</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>SortRows</type>
+ <name>Sort rows</name>
+ <fields>
+ <field>
+ <name>sort_key</name>
+ <ascending>Y</ascending>
+ <case_sensitive>N</case_sensitive>
+ <collator_enabled>N</collator_enabled>
+ <collator_strength>0</collator_strength>
+ <presorted>N</presorted>
+ </field>
+ </fields>
+ <directory>${java.io.tmpdir}</directory>
+ <sort_prefix>out0098</sort_prefix>
+ <sort_size>1000000</sort_size>
+ <unique_rows>N</unique_rows>
+ <compress>N</compress>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>352</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>PipelineExecutor</type>
+ <name>Pipeline executor grouped</name>
+ <run_configuration>local</run_configuration>
+ <filenameInField>Y</filenameInField>
+ <filenameField>child_path</filenameField>
+ <group_size>10</group_size>
+ <parameters/>
+ <inherit_all_vars>N</inherit_all_vars>
+ <execution_result_target_transform>Assert child pipelines
succeeded</execution_result_target_transform>
+ <execution_time_field>ExecutionTime</execution_time_field>
+ <execution_result_field>ExecutionResult</execution_result_field>
+ <execution_errors_field>ExecutionNrErrors</execution_errors_field>
+ <execution_lines_read_field>ExecutionLinesRead</execution_lines_read_field>
+
<execution_lines_written_field>ExecutionLinesWritten</execution_lines_written_field>
+
<execution_lines_input_field>ExecutionLinesInput</execution_lines_input_field>
+
<execution_lines_output_field>ExecutionLinesOutput</execution_lines_output_field>
+
<execution_lines_rejected_field>ExecutionLinesRejected</execution_lines_rejected_field>
+
<execution_lines_updated_field>ExecutionLinesUpdated</execution_lines_updated_field>
+
<execution_lines_deleted_field>ExecutionLinesDeleted</execution_lines_deleted_field>
+
<execution_files_retrieved_field>ExecutionFilesRetrieved</execution_files_retrieved_field>
+
<execution_exit_status_field>ExecutionExitStatus</execution_exit_status_field>
+ <execution_log_text_field>ExecutionLogText</execution_log_text_field>
+
<execution_log_channelid_field>ExecutionLogChannelId</execution_log_channelid_field>
+
<filename>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</filename>
+ <distribute>Y</distribute>
+ <copies>4</copies>
+ <GUI>
+ <xloc>576</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>Abort</type>
+ <name>Abort on child failure</name>
+ <row_threshold>0</row_threshold>
+ <always_log_rows>Y</always_log_rows>
+ <abort_option>ABORT_WITH_ERROR</abort_option>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>800</xloc>
+ <yloc>320</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>FilterRows</type>
+ <name>Assert child pipelines succeeded</name>
+ <compare>
+ <condition>
+ <negated>Y</negated>
+ <operator>-</operator>
+ <leftvalue>ExecutionNrErrors</leftvalue>
+ <function>></function>
+ <value>
+ <name>constant</name>
+ <type>Integer</type>
+ <text>0</text>
+ <length>-1</length>
+ <precision>0</precision>
+ <isnull>N</isnull>
+ <mask>####0;-####0</mask>
+ </value>
+ <conditions/>
+ </condition>
+ </compare>
+ <send_true_to>OK</send_true_to>
+ <send_false_to>Abort on child failure</send_false_to>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>800</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>Dummy</type>
+ <name>OK</name>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>1008</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <order>
+ <hop>
+ <from>Data grid</from>
+ <to>Sort rows</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Sort rows</from>
+ <to>Pipeline executor grouped</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Pipeline executor grouped</from>
+ <to>Assert child pipelines succeeded</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Assert child pipelines succeeded</from>
+ <to>OK</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Assert child pipelines succeeded</from>
+ <to>Abort on child failure</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <notepads>
+ <notepad>
+ <note>group_size=10: two full batches (rows 1-10 child A, 11-20 child
B). filenameInField uses child_path; static filename in meta is child-b. Child
pipelines abort if any result row has the wrong expected_child. copies=4.</note>
+ <fontname>.AppleSystemUIFont</fontname>
+ <fontsize>13</fontsize>
+ <fontbold>N</fontbold>
+ <fontitalic>N</fontitalic>
+ <fontcolorred>14</fontcolorred>
+ <fontcolorgreen>58</fontcolorgreen>
+ <fontcolorblue>90</fontcolorblue>
+ <backgroundcolorred>201</backgroundcolorred>
+ <backgroundcolorgreen>232</backgroundcolorgreen>
+ <backgroundcolorblue>251</backgroundcolorblue>
+ <bordercolorred>14</bordercolorred>
+ <bordercolorgreen>58</bordercolorgreen>
+ <bordercolorblue>90</bordercolorblue>
+ <xloc>48</xloc>
+ <yloc>16</yloc>
+ <width>560</width>
+ <height>132</height>
+ </notepad>
+ </notepads>
+ <attributes/>
+ <transform_error_handling/>
+</pipeline>
diff --git
a/integration-tests/transforms/0098-pipeline-executor-group10-segmented-main-midgroup-1copy.hpl
b/integration-tests/transforms/0098-pipeline-executor-group10-segmented-main-midgroup-1copy.hpl
new file mode 100644
index 0000000000..43f440fdc9
--- /dev/null
+++
b/integration-tests/transforms/0098-pipeline-executor-group10-segmented-main-midgroup-1copy.hpl
@@ -0,0 +1,312 @@
+<?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>
+ <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>0098-pipeline-executor-group10-segmented-main-midgroup-1copy</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Regression: group_size=10 with child_path switching after row
5 in one batch (5x A then 5x B). The executor must flush the partial group when
the path changes so each child receives only matching rows.
copies=1.</description>
+ <created_user>-</created_user>
+ <modified_user>-</modified_user>
+ <created_date>2026/04/16 12:00:00.000</created_date>
+ <modified_date>2026/04/16 12:00:00.000</modified_date>
+ </info>
+ <transform>
+ <type>DataGrid</type>
+ <name>Data grid</name>
+ <fields>
+ <field>
+ <name>sort_key</name>
+ <type>Integer</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <name>child_path</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <name>expected_child</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ </fields>
+ <data>
+ <line>
+ <item>1</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>2</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>3</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>4</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>5</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>6</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>7</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>8</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>9</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>10</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ </data>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>128</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>SortRows</type>
+ <name>Sort rows</name>
+ <fields>
+ <field>
+ <name>sort_key</name>
+ <ascending>Y</ascending>
+ <case_sensitive>N</case_sensitive>
+ <collator_enabled>N</collator_enabled>
+ <collator_strength>0</collator_strength>
+ <presorted>N</presorted>
+ </field>
+ </fields>
+ <directory>${java.io.tmpdir}</directory>
+ <sort_prefix>out0098</sort_prefix>
+ <sort_size>1000000</sort_size>
+ <unique_rows>N</unique_rows>
+ <compress>N</compress>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>352</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>PipelineExecutor</type>
+ <name>Pipeline executor grouped</name>
+ <run_configuration>local</run_configuration>
+ <filenameInField>Y</filenameInField>
+ <filenameField>child_path</filenameField>
+ <group_size>10</group_size>
+ <parameters/>
+ <inherit_all_vars>N</inherit_all_vars>
+ <execution_result_target_transform>Assert child pipelines
succeeded</execution_result_target_transform>
+ <execution_time_field>ExecutionTime</execution_time_field>
+ <execution_result_field>ExecutionResult</execution_result_field>
+ <execution_errors_field>ExecutionNrErrors</execution_errors_field>
+ <execution_lines_read_field>ExecutionLinesRead</execution_lines_read_field>
+
<execution_lines_written_field>ExecutionLinesWritten</execution_lines_written_field>
+
<execution_lines_input_field>ExecutionLinesInput</execution_lines_input_field>
+
<execution_lines_output_field>ExecutionLinesOutput</execution_lines_output_field>
+
<execution_lines_rejected_field>ExecutionLinesRejected</execution_lines_rejected_field>
+
<execution_lines_updated_field>ExecutionLinesUpdated</execution_lines_updated_field>
+
<execution_lines_deleted_field>ExecutionLinesDeleted</execution_lines_deleted_field>
+
<execution_files_retrieved_field>ExecutionFilesRetrieved</execution_files_retrieved_field>
+
<execution_exit_status_field>ExecutionExitStatus</execution_exit_status_field>
+ <execution_log_text_field>ExecutionLogText</execution_log_text_field>
+
<execution_log_channelid_field>ExecutionLogChannelId</execution_log_channelid_field>
+
<filename>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</filename>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>576</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>Abort</type>
+ <name>Abort on child failure</name>
+ <row_threshold>0</row_threshold>
+ <always_log_rows>Y</always_log_rows>
+ <abort_option>ABORT_WITH_ERROR</abort_option>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>800</xloc>
+ <yloc>320</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>FilterRows</type>
+ <name>Assert child pipelines succeeded</name>
+ <compare>
+ <condition>
+ <negated>Y</negated>
+ <operator>-</operator>
+ <leftvalue>ExecutionNrErrors</leftvalue>
+ <function>></function>
+ <value>
+ <name>constant</name>
+ <type>Integer</type>
+ <text>0</text>
+ <length>-1</length>
+ <precision>0</precision>
+ <isnull>N</isnull>
+ <mask>####0;-####0</mask>
+ </value>
+ <conditions/>
+ </condition>
+ </compare>
+ <send_true_to>OK</send_true_to>
+ <send_false_to>Abort on child failure</send_false_to>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>800</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>Dummy</type>
+ <name>OK</name>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>1008</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <order>
+ <hop>
+ <from>Data grid</from>
+ <to>Sort rows</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Sort rows</from>
+ <to>Pipeline executor grouped</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Pipeline executor grouped</from>
+ <to>Assert child pipelines succeeded</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Assert child pipelines succeeded</from>
+ <to>OK</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Assert child pipelines succeeded</from>
+ <to>Abort on child failure</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <notepads>
+ <notepad>
+ <note>Single batch of 10: path switches after row 5. Expect child A with
rows 1-5 only, then child B with rows 6-10 only (partial flush on path change).
copies=1.</note>
+ <fontname>.AppleSystemUIFont</fontname>
+ <fontsize>13</fontsize>
+ <fontbold>N</fontbold>
+ <fontitalic>N</fontitalic>
+ <fontcolorred>14</fontcolorred>
+ <fontcolorgreen>58</fontcolorgreen>
+ <fontcolorblue>90</fontcolorblue>
+ <backgroundcolorred>201</backgroundcolorred>
+ <backgroundcolorgreen>232</backgroundcolorgreen>
+ <backgroundcolorblue>251</backgroundcolorblue>
+ <bordercolorred>14</bordercolorred>
+ <bordercolorgreen>58</bordercolorgreen>
+ <bordercolorblue>90</bordercolorblue>
+ <xloc>48</xloc>
+ <yloc>16</yloc>
+ <width>560</width>
+ <height>132</height>
+ </notepad>
+ </notepads>
+ <attributes/>
+ <transform_error_handling/>
+</pipeline>
diff --git
a/integration-tests/transforms/0098-pipeline-executor-group10-segmented-main-midgroup-4copy.hpl
b/integration-tests/transforms/0098-pipeline-executor-group10-segmented-main-midgroup-4copy.hpl
new file mode 100644
index 0000000000..15d0c84956
--- /dev/null
+++
b/integration-tests/transforms/0098-pipeline-executor-group10-segmented-main-midgroup-4copy.hpl
@@ -0,0 +1,312 @@
+<?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>
+ <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>0098-pipeline-executor-group10-segmented-main-midgroup-4copy</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Regression: group_size=10 with child_path switching after row
5 in one batch (5x A then 5x B). The executor must flush the partial group when
the path changes so each child receives only matching rows.
copies=4.</description>
+ <created_user>-</created_user>
+ <modified_user>-</modified_user>
+ <created_date>2026/04/16 12:00:00.000</created_date>
+ <modified_date>2026/04/16 12:00:00.000</modified_date>
+ </info>
+ <transform>
+ <type>DataGrid</type>
+ <name>Data grid</name>
+ <fields>
+ <field>
+ <name>sort_key</name>
+ <type>Integer</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <name>child_path</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <name>expected_child</name>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ </fields>
+ <data>
+ <line>
+ <item>1</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>2</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>3</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>4</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>5</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-a.hpl</item>
+ <item>A</item>
+ </line>
+ <line>
+ <item>6</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>7</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>8</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>9</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ <line>
+ <item>10</item>
+
<item>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</item>
+ <item>B</item>
+ </line>
+ </data>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>128</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>SortRows</type>
+ <name>Sort rows</name>
+ <fields>
+ <field>
+ <name>sort_key</name>
+ <ascending>Y</ascending>
+ <case_sensitive>N</case_sensitive>
+ <collator_enabled>N</collator_enabled>
+ <collator_strength>0</collator_strength>
+ <presorted>N</presorted>
+ </field>
+ </fields>
+ <directory>${java.io.tmpdir}</directory>
+ <sort_prefix>out0098</sort_prefix>
+ <sort_size>1000000</sort_size>
+ <unique_rows>N</unique_rows>
+ <compress>N</compress>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>352</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>PipelineExecutor</type>
+ <name>Pipeline executor grouped</name>
+ <run_configuration>local</run_configuration>
+ <filenameInField>Y</filenameInField>
+ <filenameField>child_path</filenameField>
+ <group_size>10</group_size>
+ <parameters/>
+ <inherit_all_vars>N</inherit_all_vars>
+ <execution_result_target_transform>Assert child pipelines
succeeded</execution_result_target_transform>
+ <execution_time_field>ExecutionTime</execution_time_field>
+ <execution_result_field>ExecutionResult</execution_result_field>
+ <execution_errors_field>ExecutionNrErrors</execution_errors_field>
+ <execution_lines_read_field>ExecutionLinesRead</execution_lines_read_field>
+
<execution_lines_written_field>ExecutionLinesWritten</execution_lines_written_field>
+
<execution_lines_input_field>ExecutionLinesInput</execution_lines_input_field>
+
<execution_lines_output_field>ExecutionLinesOutput</execution_lines_output_field>
+
<execution_lines_rejected_field>ExecutionLinesRejected</execution_lines_rejected_field>
+
<execution_lines_updated_field>ExecutionLinesUpdated</execution_lines_updated_field>
+
<execution_lines_deleted_field>ExecutionLinesDeleted</execution_lines_deleted_field>
+
<execution_files_retrieved_field>ExecutionFilesRetrieved</execution_files_retrieved_field>
+
<execution_exit_status_field>ExecutionExitStatus</execution_exit_status_field>
+ <execution_log_text_field>ExecutionLogText</execution_log_text_field>
+
<execution_log_channelid_field>ExecutionLogChannelId</execution_log_channelid_field>
+
<filename>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-child-b.hpl</filename>
+ <distribute>Y</distribute>
+ <copies>4</copies>
+ <GUI>
+ <xloc>576</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>Abort</type>
+ <name>Abort on child failure</name>
+ <row_threshold>0</row_threshold>
+ <always_log_rows>Y</always_log_rows>
+ <abort_option>ABORT_WITH_ERROR</abort_option>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>800</xloc>
+ <yloc>320</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>FilterRows</type>
+ <name>Assert child pipelines succeeded</name>
+ <compare>
+ <condition>
+ <negated>Y</negated>
+ <operator>-</operator>
+ <leftvalue>ExecutionNrErrors</leftvalue>
+ <function>></function>
+ <value>
+ <name>constant</name>
+ <type>Integer</type>
+ <text>0</text>
+ <length>-1</length>
+ <precision>0</precision>
+ <isnull>N</isnull>
+ <mask>####0;-####0</mask>
+ </value>
+ <conditions/>
+ </condition>
+ </compare>
+ <send_true_to>OK</send_true_to>
+ <send_false_to>Abort on child failure</send_false_to>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>800</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <transform>
+ <type>Dummy</type>
+ <name>OK</name>
+ <distribute>Y</distribute>
+ <copies>1</copies>
+ <GUI>
+ <xloc>1008</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ </transform>
+ <order>
+ <hop>
+ <from>Data grid</from>
+ <to>Sort rows</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Sort rows</from>
+ <to>Pipeline executor grouped</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Pipeline executor grouped</from>
+ <to>Assert child pipelines succeeded</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Assert child pipelines succeeded</from>
+ <to>OK</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Assert child pipelines succeeded</from>
+ <to>Abort on child failure</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <notepads>
+ <notepad>
+ <note>Single batch of 10: path switches after row 5. Expect child A with
rows 1-5 only, then child B with rows 6-10 only (partial flush on path change).
copies=4.</note>
+ <fontname>.AppleSystemUIFont</fontname>
+ <fontsize>13</fontsize>
+ <fontbold>N</fontbold>
+ <fontitalic>N</fontitalic>
+ <fontcolorred>14</fontcolorred>
+ <fontcolorgreen>58</fontcolorgreen>
+ <fontcolorblue>90</fontcolorblue>
+ <backgroundcolorred>201</backgroundcolorred>
+ <backgroundcolorgreen>232</backgroundcolorgreen>
+ <backgroundcolorblue>251</backgroundcolorblue>
+ <bordercolorred>14</bordercolorred>
+ <bordercolorgreen>58</bordercolorgreen>
+ <bordercolorblue>90</bordercolorblue>
+ <xloc>48</xloc>
+ <yloc>16</yloc>
+ <width>560</width>
+ <height>132</height>
+ </notepad>
+ </notepads>
+ <attributes/>
+ <transform_error_handling/>
+</pipeline>
diff --git
a/integration-tests/transforms/main-0097-pipeline-executor-multicopy-fromfield.hwf
b/integration-tests/transforms/main-0097-pipeline-executor-multicopy-fromfield.hwf
new file mode 100644
index 0000000000..8b514fef08
--- /dev/null
+++
b/integration-tests/transforms/main-0097-pipeline-executor-multicopy-fromfield.hwf
@@ -0,0 +1,85 @@
+<?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-0097-pipeline-executor-multicopy-fromfield</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <created_user>-</created_user>
+ <modified_user>-</modified_user>
+ <created_date>2026/04/16 12:00:00.000</created_date>
+ <modified_date>2026/04/16 12:00:00.000</modified_date>
+ <parameters/>
+ <actions>
+ <action>
+ <repeat>N</repeat>
+ <schedulerType>0</schedulerType>
+ <intervalSeconds>0</intervalSeconds>
+ <intervalMinutes>60</intervalMinutes>
+ <DayOfMonth>1</DayOfMonth>
+ <weekDay>1</weekDay>
+ <minutes>0</minutes>
+ <hour>12</hour>
+ <doNotWaitOnFirstExecution>N</doNotWaitOnFirstExecution>
+ <name>Start</name>
+ <description/>
+ <type>SPECIAL</type>
+ <attributes/>
+ <xloc>128</xloc>
+ <yloc>96</yloc>
+ <parallel>N</parallel>
+ <attributes_hac/>
+ </action>
+ <action>
+
<filename>${PROJECT_HOME}/0097-pipeline-executor-multicopy-fromfield-main.hpl</filename>
+ <params_from_previous>N</params_from_previous>
+ <exec_per_row>N</exec_per_row>
+ <clear_rows>N</clear_rows>
+ <clear_files>N</clear_files>
+ <create_parent_folder>N</create_parent_folder>
+ <set_logfile>N</set_logfile>
+ <set_append_logfile>N</set_append_logfile>
+ <add_date>N</add_date>
+ <add_time>N</add_time>
+ <loglevel>Basic</loglevel>
+ <wait_until_finished>Y</wait_until_finished>
+ <parameters>
+ <pass_all_parameters>Y</pass_all_parameters>
+ </parameters>
+ <run_configuration>local</run_configuration>
+ <name>0097-pipeline-executor-multicopy-fromfield-main.hpl</name>
+ <type>PIPELINE</type>
+ <attributes/>
+ <xloc>384</xloc>
+ <yloc>96</yloc>
+ <parallel>N</parallel>
+ <attributes_hac/>
+ </action>
+ </actions>
+ <hops>
+ <hop>
+ <from>Start</from>
+ <to>0097-pipeline-executor-multicopy-fromfield-main.hpl</to>
+ <evaluation>Y</evaluation>
+ <unconditional>Y</unconditional>
+ <enabled>Y</enabled>
+ </hop>
+ </hops>
+ <notepads/>
+ <attributes/>
+</workflow>
diff --git
a/integration-tests/transforms/main-0098-pipeline-executor-group10-segmented.hwf
b/integration-tests/transforms/main-0098-pipeline-executor-group10-segmented.hwf
new file mode 100644
index 0000000000..5ad93febc6
--- /dev/null
+++
b/integration-tests/transforms/main-0098-pipeline-executor-group10-segmented.hwf
@@ -0,0 +1,181 @@
+<?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-0098-pipeline-executor-group10-segmented</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <created_user>-</created_user>
+ <modified_user>-</modified_user>
+ <created_date>2026/04/16 12:00:00.000</created_date>
+ <modified_date>2026/04/16 12:00:00.000</modified_date>
+ <parameters/>
+ <actions>
+ <action>
+ <repeat>N</repeat>
+ <schedulerType>0</schedulerType>
+ <intervalSeconds>0</intervalSeconds>
+ <intervalMinutes>60</intervalMinutes>
+ <DayOfMonth>1</DayOfMonth>
+ <weekDay>1</weekDay>
+ <minutes>0</minutes>
+ <hour>12</hour>
+ <doNotWaitOnFirstExecution>N</doNotWaitOnFirstExecution>
+ <name>Start</name>
+ <description/>
+ <type>SPECIAL</type>
+ <attributes/>
+ <xloc>80</xloc>
+ <yloc>160</yloc>
+ <parallel>N</parallel>
+ <attributes_hac/>
+ </action>
+ <action>
+
<filename>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-main-1copy.hpl</filename>
+ <params_from_previous>N</params_from_previous>
+ <exec_per_row>N</exec_per_row>
+ <clear_rows>N</clear_rows>
+ <clear_files>N</clear_files>
+ <create_parent_folder>N</create_parent_folder>
+ <set_logfile>N</set_logfile>
+ <set_append_logfile>N</set_append_logfile>
+ <add_date>N</add_date>
+ <add_time>N</add_time>
+ <loglevel>Basic</loglevel>
+ <wait_until_finished>Y</wait_until_finished>
+ <parameters>
+ <pass_all_parameters>Y</pass_all_parameters>
+ </parameters>
+ <run_configuration>local</run_configuration>
+ <name>0098-pipeline-executor-group10-segmented-main-1copy.hpl</name>
+ <type>PIPELINE</type>
+ <attributes/>
+ <xloc>352</xloc>
+ <yloc>160</yloc>
+ <parallel>N</parallel>
+ <attributes_hac/>
+ </action>
+ <action>
+
<filename>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-main-4copy.hpl</filename>
+ <params_from_previous>N</params_from_previous>
+ <exec_per_row>N</exec_per_row>
+ <clear_rows>N</clear_rows>
+ <clear_files>N</clear_files>
+ <create_parent_folder>N</create_parent_folder>
+ <set_logfile>N</set_logfile>
+ <set_append_logfile>N</set_append_logfile>
+ <add_date>N</add_date>
+ <add_time>N</add_time>
+ <loglevel>Basic</loglevel>
+ <wait_until_finished>Y</wait_until_finished>
+ <parameters>
+ <pass_all_parameters>Y</pass_all_parameters>
+ </parameters>
+ <run_configuration>local</run_configuration>
+ <name>0098-pipeline-executor-group10-segmented-main-4copy.hpl</name>
+ <type>PIPELINE</type>
+ <attributes/>
+ <xloc>832</xloc>
+ <yloc>160</yloc>
+ <parallel>N</parallel>
+ <attributes_hac/>
+ </action>
+ <action>
+
<filename>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-main-midgroup-1copy.hpl</filename>
+ <params_from_previous>N</params_from_previous>
+ <exec_per_row>N</exec_per_row>
+ <clear_rows>N</clear_rows>
+ <clear_files>N</clear_files>
+ <create_parent_folder>N</create_parent_folder>
+ <set_logfile>N</set_logfile>
+ <set_append_logfile>N</set_append_logfile>
+ <add_date>N</add_date>
+ <add_time>N</add_time>
+ <loglevel>Basic</loglevel>
+ <wait_until_finished>Y</wait_until_finished>
+ <parameters>
+ <pass_all_parameters>Y</pass_all_parameters>
+ </parameters>
+ <run_configuration>local</run_configuration>
+
<name>0098-pipeline-executor-group10-segmented-main-midgroup-1copy.hpl</name>
+ <type>PIPELINE</type>
+ <attributes/>
+ <xloc>1360</xloc>
+ <yloc>160</yloc>
+ <parallel>N</parallel>
+ <attributes_hac/>
+ </action>
+ <action>
+
<filename>${PROJECT_HOME}/0098-pipeline-executor-group10-segmented-main-midgroup-4copy.hpl</filename>
+ <params_from_previous>N</params_from_previous>
+ <exec_per_row>N</exec_per_row>
+ <clear_rows>N</clear_rows>
+ <clear_files>N</clear_files>
+ <create_parent_folder>N</create_parent_folder>
+ <set_logfile>N</set_logfile>
+ <set_append_logfile>N</set_append_logfile>
+ <add_date>N</add_date>
+ <add_time>N</add_time>
+ <loglevel>Basic</loglevel>
+ <wait_until_finished>Y</wait_until_finished>
+ <parameters>
+ <pass_all_parameters>Y</pass_all_parameters>
+ </parameters>
+ <run_configuration>local</run_configuration>
+
<name>0098-pipeline-executor-group10-segmented-main-midgroup-4copy.hpl</name>
+ <type>PIPELINE</type>
+ <attributes/>
+ <xloc>1952</xloc>
+ <yloc>160</yloc>
+ <parallel>N</parallel>
+ <attributes_hac/>
+ </action>
+ </actions>
+ <hops>
+ <hop>
+ <from>Start</from>
+ <to>0098-pipeline-executor-group10-segmented-main-1copy.hpl</to>
+ <evaluation>Y</evaluation>
+ <unconditional>Y</unconditional>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>0098-pipeline-executor-group10-segmented-main-1copy.hpl</from>
+ <to>0098-pipeline-executor-group10-segmented-main-4copy.hpl</to>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>0098-pipeline-executor-group10-segmented-main-4copy.hpl</from>
+ <to>0098-pipeline-executor-group10-segmented-main-midgroup-1copy.hpl</to>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+
<from>0098-pipeline-executor-group10-segmented-main-midgroup-1copy.hpl</from>
+ <to>0098-pipeline-executor-group10-segmented-main-midgroup-4copy.hpl</to>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ <enabled>Y</enabled>
+ </hop>
+ </hops>
+ <notepads/>
+ <attributes/>
+</workflow>
diff --git
a/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutor.java
b/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutor.java
index c2d0d48706..42d9c61e22 100644
---
a/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutor.java
+++
b/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutor.java
@@ -101,10 +101,18 @@ public class PipelineExecutor extends
BaseTransform<PipelineExecutorMeta, Pipeli
String filename = (String) row[pos];
if (pipelineExecutorData.prevFilename == null
|| !pipelineExecutorData.prevFilename.equals(filename)) {
+ // When grouping by size, flush a partial group before switching
child pipeline so each
+ // execution only receives rows for the previous path. Per-copy
buffer; shared meta is
+ // not touched (see runtimeMappingFilename).
+ if (pipelineExecutorData.prevFilename != null
+ && pipelineExecutorData.groupSize > 0
+ && !pipelineExecutorData.groupBuffer.isEmpty()) {
+ executePipeline(incomingFieldValues);
+ }
if (isDetailed()) {
logDetailed("Identified a new pipeline to execute: '" + filename +
"'");
}
- meta.setFilename(filename);
+ pipelineExecutorData.runtimeMappingFilename = filename;
pipelineExecutorData.prevFilename = filename;
initPipeline(pipelineExecutorData);
initOnFirstProcessingIteration();
@@ -559,7 +567,8 @@ public class PipelineExecutor extends
BaseTransform<PipelineExecutorMeta, Pipeli
@VisibleForTesting
PipelineMeta loadExecutorPipelineMeta() throws HopException {
- return TransformWithMappingMeta.loadMappingMeta(meta, metadataProvider,
this);
+ String explicit = meta.isFilenameInField() ?
getData().runtimeMappingFilename : null;
+ return TransformWithMappingMeta.loadMappingMeta(meta, explicit,
metadataProvider, this);
}
@Override
diff --git
a/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorData.java
b/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorData.java
index df2b4fa87a..40a921ff20 100644
---
a/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorData.java
+++
b/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorData.java
@@ -52,6 +52,13 @@ public class PipelineExecutorData extends BaseTransformData
implements ITransfor
public IValueMeta groupFieldMeta;
public String prevFilename;
+ /**
+ * When the child pipeline path comes from a field, the resolved path for
this transform copy
+ * only. Shared transform meta filename must not be updated at runtime so
multiple copies stay
+ * isolated.
+ */
+ public String runtimeMappingFilename;
+
public Object prevGroupFieldData;
private IRowSet executorTransformOutputRowSet;
diff --git
a/plugins/transforms/pipelineexecutor/src/test/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorMetaCoverageTest.java
b/plugins/transforms/pipelineexecutor/src/test/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorMetaCoverageTest.java
new file mode 100644
index 0000000000..a89c6f3421
--- /dev/null
+++
b/plugins/transforms/pipelineexecutor/src/test/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorMetaCoverageTest.java
@@ -0,0 +1,357 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.pipeline.transforms.pipelineexecutor;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+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.mockito.Mockito.mock;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.ICheckResult;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.exception.HopTransformException;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.apache.hop.metadata.api.IHopMetadataProvider;
+import org.apache.hop.metadata.serializer.memory.MemoryMetadataProvider;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.PipelineMeta.PipelineType;
+import org.apache.hop.pipeline.transform.ITransformMeta;
+import org.apache.hop.pipeline.transform.TransformMeta;
+import org.apache.hop.resource.ResourceEntry;
+import org.apache.hop.resource.ResourceReference;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+/** Broader coverage for {@link PipelineExecutorMeta} behaviour beyond
serialization. */
+class PipelineExecutorMetaCoverageTest {
+
+ @RegisterExtension
+ static final RestoreHopEngineEnvironmentExtension env =
+ new RestoreHopEngineEnvironmentExtension();
+
+ @BeforeEach
+ void initHop() throws HopException {
+ HopEnvironment.init();
+ }
+
+ @Test
+ void searchInfoAndTargetTransformsResolvesOptionalTargets() {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+
+ TransformMeta execResult = new TransformMeta("t_exec",
mock(ITransformMeta.class));
+ TransformMeta rowsOut = new TransformMeta("t_rows",
mock(ITransformMeta.class));
+ TransformMeta filesOut = new TransformMeta("t_files",
mock(ITransformMeta.class));
+ TransformMeta copyOut = new TransformMeta("t_copy",
mock(ITransformMeta.class));
+ List<TransformMeta> transforms =
+ new ArrayList<>(List.of(execResult, rowsOut, filesOut, copyOut));
+
+ meta.setExecutionResultTargetTransform("t_exec");
+ meta.setOutputRowsSourceTransform("t_rows");
+ meta.setResultFilesTargetTransform("t_files");
+ meta.setExecutorsOutputTransform("t_copy");
+
+ meta.searchInfoAndTargetTransforms(transforms);
+
+ assertEquals(execResult, meta.getExecutionResultTargetTransformMeta());
+ assertEquals(rowsOut, meta.getOutputRowsSourceTransformMeta());
+ assertEquals(filesOut, meta.getResultFilesTargetTransformMeta());
+ assertEquals(copyOut, meta.getExecutorsOutputTransformMeta());
+ }
+
+ @Test
+ void cleanAfterHopFromRemoveClearsAllOptionalTargets() {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ TransformMeta dummy = new TransformMeta("x", mock(ITransformMeta.class));
+ meta.setExecutionResultTargetTransformMeta(dummy);
+ meta.setExecutionResultTargetTransform("x");
+ meta.setOutputRowsSourceTransformMeta(dummy);
+ meta.setOutputRowsSourceTransform("x");
+
+ assertTrue(meta.cleanAfterHopFromRemove());
+
+ assertNull(meta.getExecutionResultTargetTransformMeta());
+ assertNull(meta.getExecutionResultTargetTransform());
+ assertNull(meta.getOutputRowsSourceTransformMeta());
+ assertNull(meta.getOutputRowsSourceTransform());
+ }
+
+ @Test
+ void cleanAfterHopFromRemoveByTransformClearsMatchingTarget() {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ TransformMeta toClear = new TransformMeta("gone",
mock(ITransformMeta.class));
+ meta.setResultFilesTargetTransformMeta(toClear);
+ meta.setResultFilesTargetTransform("gone");
+
+ assertTrue(meta.cleanAfterHopFromRemove(toClear));
+ assertNull(meta.getResultFilesTargetTransformMeta());
+ assertNull(meta.getResultFilesTargetTransform());
+ }
+
+ @Test
+ void cleanAfterHopFromRemoveByTransformReturnsFalseForUnknown() {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ assertFalse(
+ meta.cleanAfterHopFromRemove(new TransformMeta("other",
mock(ITransformMeta.class))));
+ assertFalse(meta.cleanAfterHopFromRemove(null));
+ }
+
+ @Test
+ void getSupportedPipelineTypesReturnsNormal() {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ assertArrayEquals(new PipelineType[] {PipelineType.Normal},
meta.getSupportedPipelineTypes());
+ }
+
+ @Test
+ void flagsAndCapabilities() {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ assertTrue(meta.excludeFromCopyDistributeVerification());
+ assertTrue(meta.supportsDrillDown());
+ }
+
+ @Test
+ void getReferencedObjectDescriptionsAndEnabledFlags() {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ meta.setFilename("");
+ assertEquals(1, meta.getReferencedObjectDescriptions().length);
+ assertFalse(meta.isReferencedObjectEnabled()[0]);
+
+ meta.setFilename("/some/path/child.hpl");
+ assertTrue(meta.isReferencedObjectEnabled()[0]);
+ }
+
+ @Test
+ void getResourceDependenciesIncludesResolvedFilename() {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ meta.setFilename("${MY_CHILD}");
+ TransformMeta self = new TransformMeta("pe", meta);
+ IVariables variables = new org.apache.hop.core.variables.Variables();
+ variables.setVariable("MY_CHILD", "/tmp/child.hpl");
+
+ List<ResourceReference> refs = meta.getResourceDependencies(variables,
self);
+ assertEquals(1, refs.size());
+ List<ResourceEntry> entries = new ArrayList<>(refs.get(0).getEntries());
+ assertFalse(entries.isEmpty());
+ assertEquals("/tmp/child.hpl", entries.get(0).getResource());
+ }
+
+ @Test
+ void getInfoTransformsReturnsNullWhenEmpty() {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ assertNull(meta.getInfoTransforms());
+ }
+
+ @Test
+ void getTransformIOMetaCreatesOptionalTargetStreams() {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ assertNotNull(meta.getTransformIOMeta());
+ assertTrue(meta.getTransformIOMeta().getTargetStreams().size() >= 4);
+ }
+
+ @Test
+ void checkAddsWarningWhenNoInputFields() {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ TransformMeta self = new TransformMeta("pe", meta);
+ PipelineMeta pipelineMeta = new PipelineMeta();
+ List<ICheckResult> remarks = new ArrayList<>();
+ IRowMeta emptyPrev = new RowMeta();
+
+ meta.check(
+ remarks,
+ pipelineMeta,
+ self,
+ emptyPrev,
+ new String[] {"upstream"},
+ new String[] {},
+ null,
+ new org.apache.hop.core.variables.Variables(),
+ new MemoryMetadataProvider());
+
+ assertTrue(
+ remarks.stream().anyMatch(r -> r.getType() ==
ICheckResult.TYPE_RESULT_WARNING),
+ "expected warning when previous transform sends no fields");
+ assertTrue(
+ remarks.stream().anyMatch(r -> r.getType() ==
ICheckResult.TYPE_RESULT_OK),
+ "expected OK when input hops exist");
+ }
+
+ @Test
+ void checkAddsErrorWhenNoInputTransforms() {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ TransformMeta self = new TransformMeta("pe", meta);
+ PipelineMeta pipelineMeta = new PipelineMeta();
+ List<ICheckResult> remarks = new ArrayList<>();
+ RowMeta prev = new RowMeta();
+ prev.addValueMeta(new ValueMetaString("x"));
+
+ meta.check(
+ remarks,
+ pipelineMeta,
+ self,
+ prev,
+ new String[] {},
+ new String[] {},
+ null,
+ new org.apache.hop.core.variables.Variables(),
+ new MemoryMetadataProvider());
+
+ assertTrue(
+ remarks.stream().anyMatch(r -> r.getType() ==
ICheckResult.TYPE_RESULT_ERROR),
+ "expected error when no input hops");
+ }
+
+ @Test
+ void getFieldsClearsAndBuildsExecutionResultRow() throws
HopTransformException {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ TransformMeta parent = new TransformMeta("parent", meta);
+ meta.setParentTransformMeta(parent);
+
+ TransformMeta execTarget = new TransformMeta("exec_target",
mock(ITransformMeta.class));
+ meta.setExecutionResultTargetTransformMeta(execTarget);
+
+ RowMeta row = new RowMeta();
+ row.addValueMeta(new ValueMetaString("keep_me"));
+ IVariables variables = new org.apache.hop.core.variables.Variables();
+ IHopMetadataProvider provider = new MemoryMetadataProvider();
+
+ meta.getFields(row, "pe", new IRowMeta[] {}, execTarget, variables,
provider);
+
+ assertTrue(row.size() > 0);
+ assertNotNull(row.searchValueMeta(meta.getExecutionTimeField()));
+ assertNotNull(row.searchValueMeta(meta.getExecutionNrErrorsField()));
+ }
+
+ @Test
+ void getFieldsClearsAndBuildsResultFilesRow() throws HopTransformException {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ TransformMeta parent = new TransformMeta("parent", meta);
+ meta.setParentTransformMeta(parent);
+
+ TransformMeta fileTarget = new TransformMeta("file_target",
mock(ITransformMeta.class));
+ meta.setResultFilesTargetTransformMeta(fileTarget);
+
+ RowMeta row = new RowMeta();
+ row.addValueMeta(new ValueMetaString("x"));
+ IVariables variables = new org.apache.hop.core.variables.Variables();
+ IHopMetadataProvider provider = new MemoryMetadataProvider();
+
+ meta.getFields(row, "pe", new IRowMeta[] {}, fileTarget, variables,
provider);
+
+ assertEquals(1, row.size());
+ assertNotNull(row.searchValueMeta(meta.getResultFilesFileNameField()));
+ }
+
+ @Test
+ void getFieldsClearsAndBuildsConfiguredResultRows() throws
HopTransformException {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ TransformMeta parent = new TransformMeta("parent", meta);
+ meta.setParentTransformMeta(parent);
+
+ PipelineExecutorResultRows rr = new PipelineExecutorResultRows();
+ rr.setName("out_col");
+ rr.setType("Integer");
+ rr.setLength(9);
+ rr.setPrecision(0);
+ meta.setResultRows(new ArrayList<>(Collections.singletonList(rr)));
+
+ TransformMeta rowsTarget = new TransformMeta("rows_target",
mock(ITransformMeta.class));
+ meta.setOutputRowsSourceTransformMeta(rowsTarget);
+
+ RowMeta row = new RowMeta();
+ row.addValueMeta(new ValueMetaString("x"));
+ IVariables variables = new org.apache.hop.core.variables.Variables();
+ IHopMetadataProvider provider = new MemoryMetadataProvider();
+
+ meta.getFields(row, "pe", new IRowMeta[] {}, rowsTarget, variables,
provider);
+
+ assertEquals(1, row.size());
+ assertNotNull(row.searchValueMeta("out_col"));
+ }
+
+ @Test
+ void getFieldsLeavesRowUnchangedForMainOutput() throws HopTransformException
{
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ RowMeta row = new RowMeta();
+ row.addValueMeta(new ValueMetaString("only"));
+ IVariables variables = new org.apache.hop.core.variables.Variables();
+ IHopMetadataProvider provider = new MemoryMetadataProvider();
+
+ meta.getFields(row, "pe", new IRowMeta[] {}, null, variables, provider);
+
+ assertEquals(1, row.size());
+ assertNotNull(row.searchValueMeta("only"));
+ }
+
+ @Test
+ void prepareExecutionResultsFileFieldsAddsFileNameColumn() throws
HopTransformException {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ TransformMeta parent = new TransformMeta("parent", meta);
+ meta.setParentTransformMeta(parent);
+ TransformMeta fileTarget = new TransformMeta("files",
mock(ITransformMeta.class));
+ meta.setResultFilesTargetTransformMeta(fileTarget);
+
+ RowMeta row = new RowMeta();
+ meta.prepareExecutionResultsFileFields(row, fileTarget);
+ assertEquals(1, row.size());
+ assertEquals(meta.getResultFilesFileNameField(),
row.getValueMeta(0).getName());
+ }
+
+ @Test
+ void prepareResultsRowsFieldsAddsConfiguredColumns() throws
HopTransformException {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ TransformMeta parent = new TransformMeta("parent", meta);
+ meta.setParentTransformMeta(parent);
+ PipelineExecutorResultRows rr = new PipelineExecutorResultRows();
+ rr.setName("metric");
+ rr.setType("Integer");
+ rr.setLength(5);
+ rr.setPrecision(0);
+ meta.setResultRows(new ArrayList<>(Collections.singletonList(rr)));
+
+ RowMeta row = new RowMeta();
+ meta.prepareResultsRowsFields(row);
+ assertEquals(1, row.size());
+ assertEquals("metric", row.getValueMeta(0).getName());
+ }
+}
diff --git
a/plugins/transforms/pipelineexecutor/src/test/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorMetaTest.java
b/plugins/transforms/pipelineexecutor/src/test/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorMetaTest.java
index d3f4515f83..a5baf02252 100644
---
a/plugins/transforms/pipelineexecutor/src/test/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorMetaTest.java
+++
b/plugins/transforms/pipelineexecutor/src/test/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorMetaTest.java
@@ -18,10 +18,17 @@
package org.apache.hop.pipeline.transforms.pipelineexecutor;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+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 org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.exception.HopXmlException;
+import org.apache.hop.core.xml.XmlHandler;
import org.apache.hop.pipeline.transform.TransformSerializationTestUtil;
import org.junit.jupiter.api.Test;
+import org.w3c.dom.Document;
class PipelineExecutorMetaTest {
@@ -86,4 +93,37 @@ class PipelineExecutorMetaTest {
assertEquals(meta.getParameters().size(), clone.getParameters().size());
assertEquals(meta.getResultRows().size(), clone.getResultRows().size());
}
+
+ @Test
+ void setDefaultInitializesExecutorOptions() {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+
+ assertFalse(meta.isFilenameInField());
+ assertEquals("1", meta.getGroupSize());
+ assertEquals("", meta.getGroupField());
+ assertEquals("", meta.getGroupTime());
+ assertEquals("ExecutionTime", meta.getExecutionTimeField());
+ assertEquals("ExecutionResult", meta.getExecutionResultField());
+ assertEquals("ExecutionNrErrors", meta.getExecutionNrErrorsField());
+ assertEquals("FileName", meta.getResultFilesFileNameField());
+ assertNotNull(meta.getParameters());
+ assertTrue(meta.getParameters().isEmpty());
+ assertNotNull(meta.getResultRows());
+ assertTrue(meta.getResultRows().isEmpty());
+ }
+
+ @Test
+ void convertLegacyXmlReadsInheritAllVarsFromOldParametersNode()
+ throws HopException, HopXmlException {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setInheritingAllVariables(false);
+
+ Document doc =
+ XmlHandler.loadXmlString(
+
"<transform><parameters><inherit_all_vars>Y</inherit_all_vars></parameters></transform>");
+ meta.convertLegacyXml(doc.getDocumentElement());
+
+ assertTrue(meta.isInheritingAllVariables());
+ }
}
diff --git
a/plugins/transforms/pipelineexecutor/src/test/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorTest.java
b/plugins/transforms/pipelineexecutor/src/test/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorTest.java
new file mode 100644
index 0000000000..aed02e600b
--- /dev/null
+++
b/plugins/transforms/pipelineexecutor/src/test/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorTest.java
@@ -0,0 +1,355 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.pipeline.transforms.pipelineexecutor;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+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.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.net.URISyntaxException;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.Result;
+import org.apache.hop.core.RowMetaAndData;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.apache.hop.metadata.serializer.memory.MemoryMetadataProvider;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.TransformWithMappingMeta;
+import org.apache.hop.pipeline.engine.IPipelineEngine;
+import org.apache.hop.pipeline.engines.local.LocalPipelineEngine;
+import org.apache.hop.pipeline.transform.TransformMeta;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+/** Unit tests for {@link PipelineExecutor} runtime behaviour (parameter
passing, helpers). */
+class PipelineExecutorTest {
+
+ @RegisterExtension
+ static final RestoreHopEngineEnvironmentExtension env =
+ new RestoreHopEngineEnvironmentExtension();
+
+ @BeforeEach
+ void initHop() throws HopException {
+ HopEnvironment.init();
+ }
+
+ private PipelineExecutor newExecutor(PipelineExecutorMeta meta,
PipelineExecutorData data) {
+ TransformMeta transformMeta = mock(TransformMeta.class);
+ when(transformMeta.getName()).thenReturn("pipeline_executor");
+ when(transformMeta.isPartitioned()).thenReturn(false);
+ PipelineMeta pipelineMeta = mock(PipelineMeta.class);
+ when(pipelineMeta.findTransform(anyString())).thenReturn(transformMeta);
+ LocalPipelineEngine pipeline = spy(new LocalPipelineEngine());
+ PipelineExecutor executor =
+ new PipelineExecutor(transformMeta, meta, data, 0, pipelineMeta,
pipeline);
+ executor.setMetadataProvider(new MemoryMetadataProvider());
+ return executor;
+ }
+
+ @Test
+ void getLastIncomingFieldValuesReturnsNullWhenBufferEmpty() {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ PipelineExecutorData data = new PipelineExecutorData();
+ data.groupBuffer = new ArrayList<>();
+ PipelineExecutor executor = newExecutor(meta, data);
+
+ assertNull(executor.getLastIncomingFieldValues());
+ }
+
+ @Test
+ void getLastIncomingFieldValuesReturnsStringValuesFromLastBufferedRow() {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ PipelineExecutorData data = new PipelineExecutorData();
+ data.groupBuffer = new ArrayList<>();
+ RowMeta rowMeta = new RowMeta();
+ rowMeta.addValueMeta(new ValueMetaString("a"));
+ rowMeta.addValueMeta(new ValueMetaString("b"));
+ data.groupBuffer.add(new RowMetaAndData(rowMeta, new Object[] {"first",
"second"}));
+ data.groupBuffer.add(new RowMetaAndData(rowMeta, new Object[] {"x",
null}));
+
+ PipelineExecutor executor = newExecutor(meta, data);
+ List<String> last = executor.getLastIncomingFieldValues();
+
+ assertNotNull(last);
+ assertEquals(Collections.singletonList("x"), last);
+ }
+
+ @Test
+ void discardLogLinesDoesNothingWhenExecutorPipelineIsNull() {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ PipelineExecutorData data = new PipelineExecutorData();
+ data.setExecutorPipeline(null);
+ PipelineExecutor executor = newExecutor(meta, data);
+
+ executor.discardLogLines(data);
+ }
+
+ @Test
+ void passParametersToPipelineMapsIncomingFieldToChildParameter() throws
HopException {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ meta.setInheritingAllVariables(false);
+ PipelineExecutorParameters param = new PipelineExecutorParameters();
+ param.setVariable("MYPARAM");
+ param.setField("myField");
+ param.setInput("");
+ meta.setParameters(new ArrayList<>(Collections.singletonList(param)));
+
+ RowMeta inputRowMeta = new RowMeta();
+ inputRowMeta.addValueMeta(new ValueMetaString("id"));
+ inputRowMeta.addValueMeta(new ValueMetaString("myField"));
+
+ PipelineExecutorData data = new PipelineExecutorData();
+ data.setInputRowMeta(inputRowMeta);
+ @SuppressWarnings("unchecked")
+ IPipelineEngine<PipelineMeta> child = mock(IPipelineEngine.class);
+ when(child.listParameters()).thenReturn(new String[] {"MYPARAM"});
+ when(child.getPipelineMeta()).thenReturn(mock(PipelineMeta.class));
+ data.setExecutorPipeline(child);
+
+ PipelineExecutor executor = newExecutor(meta, data);
+ executor.passParametersToPipeline(Arrays.asList("1", "from-row"));
+
+ verify(child, atLeastOnce()).setParameterValue(eq("MYPARAM"),
eq("from-row"));
+ }
+
+ @Test
+ void passParametersToPipelineUsesStaticInputWhenFieldNotInRow() throws
HopException {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ meta.setInheritingAllVariables(false);
+ PipelineExecutorParameters param = new PipelineExecutorParameters();
+ param.setVariable("P");
+ param.setField("notInStream");
+ param.setInput("static-value");
+ meta.setParameters(new ArrayList<>(Collections.singletonList(param)));
+
+ RowMeta inputRowMeta = new RowMeta();
+ inputRowMeta.addValueMeta(new ValueMetaString("id"));
+
+ PipelineExecutorData data = new PipelineExecutorData();
+ data.setInputRowMeta(inputRowMeta);
+ @SuppressWarnings("unchecked")
+ IPipelineEngine<PipelineMeta> child = mock(IPipelineEngine.class);
+ when(child.listParameters()).thenReturn(new String[] {"P"});
+ when(child.getPipelineMeta()).thenReturn(mock(PipelineMeta.class));
+ data.setExecutorPipeline(child);
+
+ PipelineExecutor executor = newExecutor(meta, data);
+ executor.passParametersToPipeline(Collections.singletonList("row-id"));
+
+ verify(child, atLeastOnce()).setParameterValue(eq("P"),
eq("static-value"));
+ }
+
+ @Test
+ void passParametersToPipelineTreatsNullIncomingListAsEmpty() throws
HopException {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ meta.setInheritingAllVariables(false);
+ PipelineExecutorParameters param = new PipelineExecutorParameters();
+ param.setVariable("P");
+ param.setField("");
+ param.setInput("fallback");
+ meta.setParameters(new ArrayList<>(Collections.singletonList(param)));
+
+ RowMeta inputRowMeta = new RowMeta();
+ inputRowMeta.addValueMeta(new ValueMetaString("id"));
+
+ PipelineExecutorData data = new PipelineExecutorData();
+ data.setInputRowMeta(inputRowMeta);
+ @SuppressWarnings("unchecked")
+ IPipelineEngine<PipelineMeta> child = mock(IPipelineEngine.class);
+ when(child.listParameters()).thenReturn(new String[] {"P"});
+ when(child.getPipelineMeta()).thenReturn(mock(PipelineMeta.class));
+ data.setExecutorPipeline(child);
+
+ PipelineExecutor executor = newExecutor(meta, data);
+ executor.passParametersToPipeline(null);
+
+ verify(child, atLeastOnce()).setParameterValue(eq("P"), eq("fallback"));
+ }
+
+ @Test
+ void collectPipelineResultsDoesNotForwardRowsWhenNoResultRowsTarget() throws
HopException {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+
+ PipelineExecutorData data = new PipelineExecutorData();
+ data.setResultRowsRowSet(null);
+
+ PipelineExecutor executor = spy(newExecutor(meta, data));
+ Result result = new Result();
+ RowMeta rowMeta = new RowMeta();
+ rowMeta.addValueMeta(new ValueMetaString("x"));
+ result.setRows(Collections.singletonList(new RowMetaAndData(rowMeta, new
Object[] {"v"})));
+
+ executor.collectPipelineResults(result);
+
+ verify(executor, never()).putRowTo(any(), any(), any());
+ }
+
+ @Test
+ void stopRunningDoesNothingWhenNoExecutorPipeline() throws HopException {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ PipelineExecutorData data = new PipelineExecutorData();
+ data.setExecutorPipeline(null);
+ PipelineExecutor executor = newExecutor(meta, data);
+
+ executor.stopRunning();
+ }
+
+ @Test
+ void stopAllDelegatesToExecutorWhenPresent() throws HopException {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ PipelineExecutorData data = new PipelineExecutorData();
+ @SuppressWarnings("unchecked")
+ IPipelineEngine<PipelineMeta> child = mock(IPipelineEngine.class);
+ data.setExecutorPipeline(child);
+
+ PipelineExecutor executor = newExecutor(meta, data);
+ executor.stopAll();
+
+ verify(child).stopAll();
+ }
+
+ @Test
+ void stopRunningStopsChildPipelineWhenPresent() throws HopException {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ PipelineExecutorData data = new PipelineExecutorData();
+ @SuppressWarnings("unchecked")
+ IPipelineEngine<PipelineMeta> child = mock(IPipelineEngine.class);
+ data.setExecutorPipeline(child);
+ PipelineExecutor executor = newExecutor(meta, data);
+
+ executor.stopRunning();
+
+ verify(child).stopAll();
+ }
+
+ @Test
+ void disposeClearsGroupBuffer() throws HopException {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ PipelineExecutorData data = new PipelineExecutorData();
+ data.groupBuffer = new ArrayList<>();
+ data.groupBuffer.add(new RowMetaAndData(new RowMeta(), new Object[0]));
+
+ PipelineExecutor executor = newExecutor(meta, data);
+ executor.dispose();
+
+ assertNull(data.groupBuffer);
+ }
+
+ @Test
+ void initFailsWhenStaticFilenameMissing() throws HopException {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ meta.setFilenameInField(false);
+ meta.setFilename("");
+ meta.setRunConfigurationName("local");
+
+ PipelineExecutor executor = newExecutor(meta, new PipelineExecutorData());
+ assertFalse(executor.init());
+ }
+
+ @Test
+ void initSucceedsWhenFilenameComesFromField() throws HopException {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ meta.setFilenameInField(true);
+ meta.setFilenameField("child_path");
+ meta.setFilename("/tmp/ignored-default.hpl");
+ meta.setRunConfigurationName("local");
+
+ PipelineExecutor executor = newExecutor(meta, new PipelineExecutorData());
+ assertTrue(executor.init());
+ }
+
+ @Test
+ void initLoadsChildPipelineFromFilesystemPath() throws HopException,
URISyntaxException {
+ String path =
+ Paths.get(
+ Objects.requireNonNull(
+ PipelineExecutorTest.class.getResource(
+
"/org/apache/hop/pipeline/transforms/pipelineexecutor/minimal-child.hpl"))
+ .toURI())
+ .toAbsolutePath()
+ .toString();
+
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ meta.setFilenameInField(false);
+ meta.setFilename(path);
+ meta.setRunConfigurationName("local");
+
+ PipelineExecutor executor = newExecutor(meta, new PipelineExecutorData());
+ assertTrue(executor.init());
+ assertNotNull(executor.getData().getExecutorPipelineMeta());
+ }
+
+ @Test
+ void loadMappingMetaHonorsExplicitFilenameOverWrongMetaFilename()
+ throws HopException, URISyntaxException {
+ String goodPath =
+ Paths.get(
+ Objects.requireNonNull(
+ PipelineExecutorTest.class.getResource(
+
"/org/apache/hop/pipeline/transforms/pipelineexecutor/minimal-child.hpl"))
+ .toURI())
+ .toAbsolutePath()
+ .toString();
+
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ meta.setFilename("/this/path/does/not/exist/bad-child.hpl");
+
+ PipelineMeta loaded =
+ TransformWithMappingMeta.loadMappingMeta(
+ meta, goodPath, new MemoryMetadataProvider(), new Variables());
+
+ assertNotNull(loaded);
+ assertFalse(loaded.getTransforms().isEmpty());
+ }
+}
diff --git
a/plugins/transforms/pipelineexecutor/src/test/resources/org/apache/hop/pipeline/transforms/pipelineexecutor/minimal-child.hpl
b/plugins/transforms/pipelineexecutor/src/test/resources/org/apache/hop/pipeline/transforms/pipelineexecutor/minimal-child.hpl
new file mode 100644
index 0000000000..b7835569d7
--- /dev/null
+++
b/plugins/transforms/pipelineexecutor/src/test/resources/org/apache/hop/pipeline/transforms/pipelineexecutor/minimal-child.hpl
@@ -0,0 +1,103 @@
+<?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>minimal-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/01/01 00:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/01/01 00:00:00.000</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>Generate rows</from>
+ <to>Copy rows to result</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <transform>
+ <name>Generate rows</name>
+ <type>RowGenerator</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <fields>
+ <field>
+ <currency/>
+ <decimal/>
+ <format/>
+ <group/>
+ <length>-1</length>
+ <name>col</name>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <type>Integer</type>
+ <nullif>1</nullif>
+ </field>
+ </fields>
+ <interval_in_ms>5000</interval_in_ms>
+ <last_time_field>FiveSecondsAgo</last_time_field>
+ <never_ending>N</never_ending>
+ <limit>1</limit>
+ <row_time_field>now</row_time_field>
+ <attributes/>
+ <GUI>
+ <xloc>480</xloc>
+ <yloc>192</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Copy rows to result</name>
+ <type>RowsToResult</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ <GUI>
+ <xloc>656</xloc>
+ <yloc>192</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>