This is an automated email from the ASF dual-hosted git repository.
mattcasters 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 32c2ce6400 Fix:stale parameter handling in the Pipeline Executor when
retries are enabled and variables are inherited from parent scopes (#9059)
32c2ce6400 is described below
commit 32c2ce6400844ce99cfb060b0c7e9cd24884463e
Author: Happy <[email protected]>
AuthorDate: Sun Oct 25 03:56:45 2026 +0800
Fix:stale parameter handling in the Pipeline Executor when retries are
enabled and variables are inherited from parent scopes (#9059)
* Enhance DatabaseJoin to support named and positional SQL parameters
* Enhance DatabaseJoin to support runtime output metadata initialization
and improve parameter handling
* Add tests for mixed named and positional SQL parameters in
DatabaseJoinMeta
* Added a new core SQL parser: parseSqlParameterSpec(String) that returns
* Refactor DatabaseJoin to simplify output field name handling and ensure
parameter specification is initialized
* Refactor DatabaseJoinMeta to streamline SQL parameter handling and
enhance metadata discovery
* Add tests for SQL parameter parsing and metadata handling in
DatabaseJoinMeta
* Fix SQL parameter parsing for JSONB operators and ARRAY expressions
- Skip only explicit JSONB operator tokens: `?|`, `?&`, `??`, `??|`,
and `??&`.
- Consume complete operator tokens so `col ?? ?` still counts the trailing
`?` as a bind parameter.
- Stop treating all `[...]` expressions as bracket-quoted identifiers.
- Add dialect-aware bracket handling for SQL Server.
- Preserve PostgreSQL parameter markers in `ARRAY[?]` and `ARRAY[?, ?]`.
- Apply the same parsing behavior to the Database Join path.
- Expand regression coverage for JSONB operators, `LIKE`, `NOT LIKE`,
`BETWEEN`, `SELECT ? FROM dual`, and ARRAY expressions.
* Enhance parseSqlParameterSpec() call in DatabaseJoin to support
bracket-quoted identifiers
* Refactor parseSqlParameterSpec() in DatabaseJoinMeta to support
bracket-quoted identifiers
* Add tests for mixed named and positional placeholders in SQL parameter
parsing
* Refactor SQL parameter tests to improve clarity and coverage for JSONB
operators and bracket-quoted identifiers
* Add resolved parameters panel to DatabaseJoinDialog for improved SQL
parameter handling
* Refactor PipelineExecutor to improve parameter handling and add retry
logic for pipeline execution
* Add retry attempts and delay configuration to PipelineExecutorDialog for
improved error handling
* Add unit tests for retry logic in PipelineExecutor to validate execution
attempts
* Add retry configuration panel to PipelineExecutorDialog for enhanced
error handling
* Add support for retry options in PipelineExecutorMeta with XML handling
* Add timeout handling to executePipelineAttempt for retry logic
* Add support for additional parameters in executePipelineAttempt and
enhance retry logic tests
* Add log message for interrupted retries and update tooltip for wait
timeout
* Refactor retry logic in executeWithRetries and add cleanup for previous
attempts
* Add tests for cleanup and retry logic in PipelineExecutor
* Update documentation for pipeline execution parameters to clarify retry
behavior and timing
* Refactor cleanup logic in executePipelineAttempt to use
stopPreviousAttemptPipeline
* Add tests for cleanup behavior in executeWithRetries method
* Enhance retry logic in PipelineExecutor to refresh parameter variables
from external scopes
* Add tests for refreshing parameter variables from external scopes in
PipelineExecutor
* Refactor PipelineExecutor to streamline parameter variable refresh logic
and improve retry handling
* Add tests for refreshing parameter variables from external scopes in
PipelineExecutor
* Add integration tests for Pipeline Executor retries
Run a child that fails once, then succeeds. Check that an inherited
variable and a static ${TOKEN} cell pick up the value published on the
root scope, and that a null, a boolean and a date stay the same on both
attempts.
---------
Co-authored-by: happy.he <[email protected]>
Co-authored-by: mattcasters <[email protected]>
---
.../transforms/0125-retry-fields-child.hpl | 245 +++++++++++++
.../transforms/0125-retry-fields-parent.hpl | 175 +++++++++
.../transforms/0125-retry-inherited-child.hpl | 224 ++++++++++++
.../transforms/0125-retry-inherited-parent.hpl | 112 ++++++
.../transforms/0125-retry-static-child.hpl | 224 ++++++++++++
.../transforms/0125-retry-static-parent.hpl | 117 ++++++
.../files/expected/0125-retry-fields.txt | 2 +
.../files/expected/0125-retry-inherited.txt | 2 +
.../files/expected/0125-retry-static.txt | 2 +
.../main-0125-pipeline-executor-retry.hwf | 394 +++++++++++++++++++++
.../pipelineexecutor/PipelineExecutor.java | 80 ++++-
.../pipelineexecutor/PipelineExecutorTest.java | 267 +++++++++++++-
12 files changed, 1830 insertions(+), 14 deletions(-)
diff --git a/integration-tests/transforms/0125-retry-fields-child.hpl
b/integration-tests/transforms/0125-retry-fields-child.hpl
new file mode 100644
index 0000000000..8694f385a7
--- /dev/null
+++ b/integration-tests/transforms/0125-retry-fields-child.hpl
@@ -0,0 +1,245 @@
+<?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>0125-retry-fields-child</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Records field-mapped parameters. The first attempt fails so
the executor retries the same row.</description>
+ <extended_description/>
+ <pipeline_version/>
+ <pipeline_type>Normal</pipeline_type>
+ <parameters>
+ <parameter>
+ <name>LABEL</name>
+ <default_value/>
+ <description/>
+ </parameter>
+ <parameter>
+ <name>NOTE</name>
+ <default_value/>
+ <description/>
+ </parameter>
+ <parameter>
+ <name>FLAG</name>
+ <default_value/>
+ <description/>
+ </parameter>
+ <parameter>
+ <name>WHEN</name>
+ <default_value/>
+ <description/>
+ </parameter>
+ </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/10/24 00:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/10/24 00:00:00.000</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>Generate rows</from>
+ <to>Record fields</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Record fields</from>
+ <to>Needs retry</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Needs retry</from>
+ <to>Abort</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Needs retry</from>
+ <to>Done</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>
+ <name>row</name>
+ <type>Integer</type>
+ <format>0</format>
+ <currency/>
+ <decimal/>
+ <group/>
+ <nullif>1</nullif>
+ <length>-1</length>
+ <precision>0</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ </fields>
+ <interval_in_ms>5000</interval_in_ms>
+ <last_time_field/>
+ <limit>1</limit>
+ <never_ending>N</never_ending>
+ <row_time_field/>
+ <attributes/>
+ <GUI>
+ <xloc>80</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Record fields</name>
+ <type>ScriptValueMod</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <jsScripts>
+ <jsScript>
+ <jsScript_type>0</jsScript_type>
+ <jsScript_name>Script 1</jsScript_name>
+ <jsScript_script>var label = getVariable("LABEL", "");
+var note = getVariable("NOTE", "");
+var flag = getVariable("FLAG", "");
+var when = getVariable("WHEN", "");
+var path = java.lang.System.getProperty("java.io.tmpdir") +
"/hop-it-0125-fields.txt";
+var out = new java.io.FileWriter(path, true);
+out.write(label + "|" + note + "|" + flag + "|" + when + "\n");
+out.close();
+var marker = new java.io.File(java.lang.System.getProperty("java.io.tmpdir") +
"/hop-it-0125-fields.marker");
+var first = !marker.exists();
+if (first) {
+ var touch = new java.io.FileWriter(marker);
+ touch.close();
+}
+var fail = first ? "Y" : "N";</jsScript_script>
+ </jsScript>
+ </jsScripts>
+ <fields>
+ <field>
+ <name>fail</name>
+ <rename>fail</rename>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <replace>N</replace>
+ </field>
+ </fields>
+ <optimizationLevel>9</optimizationLevel>
+ <languageVersion>ES6</languageVersion>
+ <attributes/>
+ <GUI>
+ <xloc>256</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Needs retry</name>
+ <type>FilterRows</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <send_true_to>Abort</send_true_to>
+ <send_false_to>Done</send_false_to>
+ <compare>
+ <condition>
+ <negated>N</negated>
+ <leftvalue>fail</leftvalue>
+ <function>=</function>
+ <rightvalue/>
+ <value>
+ <name>constant</name>
+ <type>String</type>
+ <text>Y</text>
+ <length>-1</length>
+ <precision>-1</precision>
+ <isnull>N</isnull>
+ <mask/>
+ </value>
+ </condition>
+ </compare>
+ <attributes/>
+ <GUI>
+ <xloc>432</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Abort</name>
+ <type>Abort</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <row_threshold>0</row_threshold>
+ <message>field parameters recorded, retry the same row</message>
+ <always_log_rows>Y</always_log_rows>
+ <abort_option>ABORT_WITH_ERROR</abort_option>
+ <attributes/>
+ <GUI>
+ <xloc>608</xloc>
+ <yloc>32</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Done</name>
+ <type>Dummy</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ <GUI>
+ <xloc>608</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/transforms/0125-retry-fields-parent.hpl
b/integration-tests/transforms/0125-retry-fields-parent.hpl
new file mode 100644
index 0000000000..afc7b9ecb8
--- /dev/null
+++ b/integration-tests/transforms/0125-retry-fields-parent.hpl
@@ -0,0 +1,175 @@
+<?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>0125-retry-fields-parent</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Field mappings include a null column, a boolean and a date.
Both attempts must receive the same converted values.</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/10/24 00:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/10/24 00:00:00.000</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>Generate rows</from>
+ <to>Execute child</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>
+ <name>label</name>
+ <type>String</type>
+ <format/>
+ <currency/>
+ <decimal/>
+ <group/>
+ <nullif>alpha</nullif>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <name>note</name>
+ <type>String</type>
+ <format/>
+ <currency/>
+ <decimal/>
+ <group/>
+ <nullif/>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <name>flag</name>
+ <type>Boolean</type>
+ <format>Y/N</format>
+ <currency/>
+ <decimal/>
+ <group/>
+ <nullif>Y</nullif>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ <field>
+ <name>when</name>
+ <type>Date</type>
+ <format>yyyy-MM-dd</format>
+ <currency/>
+ <decimal/>
+ <group/>
+ <nullif>2026-10-21</nullif>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ </fields>
+ <interval_in_ms>5000</interval_in_ms>
+ <last_time_field/>
+ <limit>1</limit>
+ <never_ending>N</never_ending>
+ <row_time_field/>
+ <attributes/>
+ <GUI>
+ <xloc>80</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Execute child</name>
+ <type>PipelineExecutor</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <run_configuration>local</run_configuration>
+ <filename>${PROJECT_HOME}/0125-retry-fields-child.hpl</filename>
+ <filenameInField>N</filenameInField>
+ <filenameField/>
+ <group_size>1</group_size>
+ <group_field/>
+ <group_time/>
+ <parameters>
+ <variable_mapping>
+ <variable>LABEL</variable>
+ <field>label</field>
+ <input/>
+ </variable_mapping>
+ <variable_mapping>
+ <variable>NOTE</variable>
+ <field>note</field>
+ <input/>
+ </variable_mapping>
+ <variable_mapping>
+ <variable>FLAG</variable>
+ <field>flag</field>
+ <input/>
+ </variable_mapping>
+ <variable_mapping>
+ <variable>WHEN</variable>
+ <field>when</field>
+ <input/>
+ </variable_mapping>
+ </parameters>
+ <inherit_all_vars>N</inherit_all_vars>
+ <retry>
+ <retry_attempts>1</retry_attempts>
+ <retry_delay>0</retry_delay>
+ </retry>
+ <execution_result_target_transform/>
+ <result_files_file_name_field>FileName</result_files_file_name_field>
+ <attributes/>
+ <GUI>
+ <xloc>288</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/transforms/0125-retry-inherited-child.hpl
b/integration-tests/transforms/0125-retry-inherited-child.hpl
new file mode 100644
index 0000000000..277a5f8ae3
--- /dev/null
+++ b/integration-tests/transforms/0125-retry-inherited-child.hpl
@@ -0,0 +1,224 @@
+<?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>0125-retry-inherited-child</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Records inherited TOKEN, then publishes a new root value and
fails until that value arrives.</description>
+ <extended_description/>
+ <pipeline_version/>
+ <pipeline_type>Normal</pipeline_type>
+ <parameters>
+ <parameter>
+ <name>TOKEN</name>
+ <default_value/>
+ <description>Inherited from the parent Pipeline Executor. Empty until
the parent passes a value.</description>
+ </parameter>
+ </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/10/24 00:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/10/24 00:00:00.000</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>Generate rows</from>
+ <to>Record token</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Record token</from>
+ <to>Needs retry</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Needs retry</from>
+ <to>Abort</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Needs retry</from>
+ <to>Done</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>
+ <name>row</name>
+ <type>Integer</type>
+ <format>0</format>
+ <currency/>
+ <decimal/>
+ <group/>
+ <nullif>1</nullif>
+ <length>-1</length>
+ <precision>0</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ </fields>
+ <interval_in_ms>5000</interval_in_ms>
+ <last_time_field/>
+ <limit>1</limit>
+ <never_ending>N</never_ending>
+ <row_time_field/>
+ <attributes/>
+ <GUI>
+ <xloc>80</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Record token</name>
+ <type>ScriptValueMod</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <jsScripts>
+ <jsScript>
+ <jsScript_type>0</jsScript_type>
+ <jsScript_name>Script 1</jsScript_name>
+ <jsScript_script>var token = getVariable("TOKEN", "");
+var path = java.lang.System.getProperty("java.io.tmpdir") +
"/hop-it-0125-inherited.txt";
+var out = new java.io.FileWriter(path, true);
+out.write(token + "\n");
+out.close();
+if (token != "fresh") {
+ setVariable("TOKEN", "fresh", "r");
+}
+var fail = token == "fresh" ? "N" : "Y";</jsScript_script>
+ </jsScript>
+ </jsScripts>
+ <fields>
+ <field>
+ <name>fail</name>
+ <rename>fail</rename>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <replace>N</replace>
+ </field>
+ </fields>
+ <optimizationLevel>9</optimizationLevel>
+ <languageVersion>ES6</languageVersion>
+ <attributes/>
+ <GUI>
+ <xloc>256</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Needs retry</name>
+ <type>FilterRows</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <send_true_to>Abort</send_true_to>
+ <send_false_to>Done</send_false_to>
+ <compare>
+ <condition>
+ <negated>N</negated>
+ <leftvalue>fail</leftvalue>
+ <function>=</function>
+ <rightvalue/>
+ <value>
+ <name>constant</name>
+ <type>String</type>
+ <text>Y</text>
+ <length>-1</length>
+ <precision>-1</precision>
+ <isnull>N</isnull>
+ <mask/>
+ </value>
+ </condition>
+ </compare>
+ <attributes/>
+ <GUI>
+ <xloc>432</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Abort</name>
+ <type>Abort</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <row_threshold>0</row_threshold>
+ <message>inherited token is not fresh yet</message>
+ <always_log_rows>Y</always_log_rows>
+ <abort_option>ABORT_WITH_ERROR</abort_option>
+ <attributes/>
+ <GUI>
+ <xloc>608</xloc>
+ <yloc>32</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Done</name>
+ <type>Dummy</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ <GUI>
+ <xloc>608</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/transforms/0125-retry-inherited-parent.hpl
b/integration-tests/transforms/0125-retry-inherited-parent.hpl
new file mode 100644
index 0000000000..7273c7e470
--- /dev/null
+++ b/integration-tests/transforms/0125-retry-inherited-parent.hpl
@@ -0,0 +1,112 @@
+<?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>0125-retry-inherited-parent</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Pipeline Executor retries a child that is not given TOKEN on
the parameters grid. Inherit-all must pick up the value the child publishes on
the root scope.</description>
+ <extended_description/>
+ <pipeline_version/>
+ <pipeline_type>Normal</pipeline_type>
+ <parameters>
+ <parameter>
+ <name>TOKEN</name>
+ <default_value>stale</default_value>
+ <description>Starting value. The child replaces it on the root scope
during the failed attempt.</description>
+ </parameter>
+ </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/10/24 00:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/10/24 00:00:00.000</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>Generate rows</from>
+ <to>Execute child</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>
+ </fields>
+ <interval_in_ms>5000</interval_in_ms>
+ <last_time_field/>
+ <limit>1</limit>
+ <never_ending>N</never_ending>
+ <row_time_field/>
+ <attributes/>
+ <GUI>
+ <xloc>80</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Execute child</name>
+ <type>PipelineExecutor</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <run_configuration>local</run_configuration>
+ <filename>${PROJECT_HOME}/0125-retry-inherited-child.hpl</filename>
+ <filenameInField>N</filenameInField>
+ <filenameField/>
+ <group_size>1</group_size>
+ <group_field/>
+ <group_time/>
+ <parameters>
+ </parameters>
+ <inherit_all_vars>Y</inherit_all_vars>
+ <retry>
+ <retry_attempts>1</retry_attempts>
+ <retry_delay>0</retry_delay>
+ </retry>
+ <execution_result_target_transform/>
+ <result_files_file_name_field>FileName</result_files_file_name_field>
+ <attributes/>
+ <GUI>
+ <xloc>288</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/transforms/0125-retry-static-child.hpl
b/integration-tests/transforms/0125-retry-static-child.hpl
new file mode 100644
index 0000000000..86644cbf81
--- /dev/null
+++ b/integration-tests/transforms/0125-retry-static-child.hpl
@@ -0,0 +1,224 @@
+<?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>0125-retry-static-child</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Records parameter P. P is a static ${TOKEN} cell on the
parent executor.</description>
+ <extended_description/>
+ <pipeline_version/>
+ <pipeline_type>Normal</pipeline_type>
+ <parameters>
+ <parameter>
+ <name>P</name>
+ <default_value/>
+ <description>Value of the static ${TOKEN} cell.</description>
+ </parameter>
+ </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/10/24 00:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/10/24 00:00:00.000</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>Generate rows</from>
+ <to>Record token</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Record token</from>
+ <to>Needs retry</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Needs retry</from>
+ <to>Abort</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>Needs retry</from>
+ <to>Done</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>
+ <name>row</name>
+ <type>Integer</type>
+ <format>0</format>
+ <currency/>
+ <decimal/>
+ <group/>
+ <nullif>1</nullif>
+ <length>-1</length>
+ <precision>0</precision>
+ <set_empty_string>N</set_empty_string>
+ </field>
+ </fields>
+ <interval_in_ms>5000</interval_in_ms>
+ <last_time_field/>
+ <limit>1</limit>
+ <never_ending>N</never_ending>
+ <row_time_field/>
+ <attributes/>
+ <GUI>
+ <xloc>80</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Record token</name>
+ <type>ScriptValueMod</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <jsScripts>
+ <jsScript>
+ <jsScript_type>0</jsScript_type>
+ <jsScript_name>Script 1</jsScript_name>
+ <jsScript_script>var token = getVariable("P", "");
+var path = java.lang.System.getProperty("java.io.tmpdir") +
"/hop-it-0125-static.txt";
+var out = new java.io.FileWriter(path, true);
+out.write(token + "\n");
+out.close();
+if (token != "fresh") {
+ setVariable("TOKEN", "fresh", "r");
+}
+var fail = token == "fresh" ? "N" : "Y";</jsScript_script>
+ </jsScript>
+ </jsScripts>
+ <fields>
+ <field>
+ <name>fail</name>
+ <rename>fail</rename>
+ <type>String</type>
+ <length>-1</length>
+ <precision>-1</precision>
+ <replace>N</replace>
+ </field>
+ </fields>
+ <optimizationLevel>9</optimizationLevel>
+ <languageVersion>ES6</languageVersion>
+ <attributes/>
+ <GUI>
+ <xloc>256</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Needs retry</name>
+ <type>FilterRows</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <send_true_to>Abort</send_true_to>
+ <send_false_to>Done</send_false_to>
+ <compare>
+ <condition>
+ <negated>N</negated>
+ <leftvalue>fail</leftvalue>
+ <function>=</function>
+ <rightvalue/>
+ <value>
+ <name>constant</name>
+ <type>String</type>
+ <text>Y</text>
+ <length>-1</length>
+ <precision>-1</precision>
+ <isnull>N</isnull>
+ <mask/>
+ </value>
+ </condition>
+ </compare>
+ <attributes/>
+ <GUI>
+ <xloc>432</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Abort</name>
+ <type>Abort</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <row_threshold>0</row_threshold>
+ <message>static token is not fresh yet</message>
+ <always_log_rows>Y</always_log_rows>
+ <abort_option>ABORT_WITH_ERROR</abort_option>
+ <attributes/>
+ <GUI>
+ <xloc>608</xloc>
+ <yloc>32</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Done</name>
+ <type>Dummy</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ <GUI>
+ <xloc>608</xloc>
+ <yloc>160</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/transforms/0125-retry-static-parent.hpl
b/integration-tests/transforms/0125-retry-static-parent.hpl
new file mode 100644
index 0000000000..c7e2976b83
--- /dev/null
+++ b/integration-tests/transforms/0125-retry-static-parent.hpl
@@ -0,0 +1,117 @@
+<?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>0125-retry-static-parent</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>The executor passes P as the static cell ${TOKEN}. A retry
must resolve that cell again after the child updates TOKEN.</description>
+ <extended_description/>
+ <pipeline_version/>
+ <pipeline_type>Normal</pipeline_type>
+ <parameters>
+ <parameter>
+ <name>TOKEN</name>
+ <default_value>stale</default_value>
+ <description>Resolved by the static parameter cell. The child replaces
it on the root scope.</description>
+ </parameter>
+ </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/10/24 00:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/10/24 00:00:00.000</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>Generate rows</from>
+ <to>Execute child</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>
+ </fields>
+ <interval_in_ms>5000</interval_in_ms>
+ <last_time_field/>
+ <limit>1</limit>
+ <never_ending>N</never_ending>
+ <row_time_field/>
+ <attributes/>
+ <GUI>
+ <xloc>80</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>Execute child</name>
+ <type>PipelineExecutor</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <run_configuration>local</run_configuration>
+ <filename>${PROJECT_HOME}/0125-retry-static-child.hpl</filename>
+ <filenameInField>N</filenameInField>
+ <filenameField/>
+ <group_size>1</group_size>
+ <group_field/>
+ <group_time/>
+ <parameters>
+ <variable_mapping>
+ <variable>P</variable>
+ <field/>
+ <input>${TOKEN}</input>
+ </variable_mapping>
+ </parameters>
+ <inherit_all_vars>Y</inherit_all_vars>
+ <retry>
+ <retry_attempts>1</retry_attempts>
+ <retry_delay>0</retry_delay>
+ </retry>
+ <execution_result_target_transform/>
+ <result_files_file_name_field>FileName</result_files_file_name_field>
+ <attributes/>
+ <GUI>
+ <xloc>288</xloc>
+ <yloc>96</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git a/integration-tests/transforms/files/expected/0125-retry-fields.txt
b/integration-tests/transforms/files/expected/0125-retry-fields.txt
new file mode 100644
index 0000000000..a53e7f526f
--- /dev/null
+++ b/integration-tests/transforms/files/expected/0125-retry-fields.txt
@@ -0,0 +1,2 @@
+alpha||Y|2026-10-21
+alpha||Y|2026-10-21
diff --git
a/integration-tests/transforms/files/expected/0125-retry-inherited.txt
b/integration-tests/transforms/files/expected/0125-retry-inherited.txt
new file mode 100644
index 0000000000..1f8665bf5d
--- /dev/null
+++ b/integration-tests/transforms/files/expected/0125-retry-inherited.txt
@@ -0,0 +1,2 @@
+stale
+fresh
diff --git a/integration-tests/transforms/files/expected/0125-retry-static.txt
b/integration-tests/transforms/files/expected/0125-retry-static.txt
new file mode 100644
index 0000000000..1f8665bf5d
--- /dev/null
+++ b/integration-tests/transforms/files/expected/0125-retry-static.txt
@@ -0,0 +1,2 @@
+stale
+fresh
diff --git a/integration-tests/transforms/main-0125-pipeline-executor-retry.hwf
b/integration-tests/transforms/main-0125-pipeline-executor-retry.hwf
new file mode 100644
index 0000000000..542625c6fd
--- /dev/null
+++ b/integration-tests/transforms/main-0125-pipeline-executor-retry.hwf
@@ -0,0 +1,394 @@
+<?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-0125-pipeline-executor-retry</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>Pipeline Executor retries: an inherited variable, a static
${TOKEN} cell, and field values that include a null, a boolean and a
date.</description>
+ <extended_description/>
+ <workflow_version/>
+ <created_user>-</created_user>
+ <created_date>2026/10/24 00:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/10/24 00:00:00.000</modified_date>
+ <parameters>
+ </parameters>
+ <actions>
+ <action>
+ <name>Start</name>
+ <description/>
+ <type>SPECIAL</type>
+ <attributes/>
+ <DayOfMonth>1</DayOfMonth>
+ <hour>12</hour>
+ <intervalMinutes>60</intervalMinutes>
+ <intervalSeconds>0</intervalSeconds>
+ <minutes>0</minutes>
+ <repeat>N</repeat>
+ <schedulerType>0</schedulerType>
+ <weekDay>1</weekDay>
+ <parallel>N</parallel>
+ <xloc>64</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>delete inherited result</name>
+ <description/>
+ <type>DELETE_FILE</type>
+ <attributes/>
+ <fail_if_file_not_exists>N</fail_if_file_not_exists>
+ <filename>${java.io.tmpdir}/hop-it-0125-inherited.txt</filename>
+ <parallel>N</parallel>
+ <xloc>224</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>inherited variable</name>
+ <description/>
+ <type>PIPELINE</type>
+ <attributes/>
+ <filename>${PROJECT_HOME}/0125-retry-inherited-parent.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>
+ <set_logfile>N</set_logfile>
+ <loglevel>Basic</loglevel>
+ <set_append_logfile>N</set_append_logfile>
+ <wait_until_finished>Y</wait_until_finished>
+ <create_parent_folder>N</create_parent_folder>
+ <run_configuration>local</run_configuration>
+ <parameters>
+ <pass_all_parameters>N</pass_all_parameters>
+ <parameter>
+ <name>TOKEN</name>
+ <stream_name/>
+ <value>stale</value>
+ </parameter>
+ </parameters>
+ <parallel>N</parallel>
+ <xloc>416</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>check inherited variable</name>
+ <description/>
+ <type>FILE_COMPARE</type>
+ <attributes/>
+ <filename1>${java.io.tmpdir}/hop-it-0125-inherited.txt</filename1>
+
<filename2>${PROJECT_HOME}/files/expected/0125-retry-inherited.txt</filename2>
+ <add_filename_result>N</add_filename_result>
+ <parallel>N</parallel>
+ <xloc>608</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>delete static result</name>
+ <description/>
+ <type>DELETE_FILE</type>
+ <attributes/>
+ <fail_if_file_not_exists>N</fail_if_file_not_exists>
+ <filename>${java.io.tmpdir}/hop-it-0125-static.txt</filename>
+ <parallel>N</parallel>
+ <xloc>800</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>static variable cell</name>
+ <description/>
+ <type>PIPELINE</type>
+ <attributes/>
+ <filename>${PROJECT_HOME}/0125-retry-static-parent.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>
+ <set_logfile>N</set_logfile>
+ <loglevel>Basic</loglevel>
+ <set_append_logfile>N</set_append_logfile>
+ <wait_until_finished>Y</wait_until_finished>
+ <create_parent_folder>N</create_parent_folder>
+ <run_configuration>local</run_configuration>
+ <parameters>
+ <pass_all_parameters>N</pass_all_parameters>
+ <parameter>
+ <name>TOKEN</name>
+ <stream_name/>
+ <value>stale</value>
+ </parameter>
+ </parameters>
+ <parallel>N</parallel>
+ <xloc>992</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>check static variable cell</name>
+ <description/>
+ <type>FILE_COMPARE</type>
+ <attributes/>
+ <filename1>${java.io.tmpdir}/hop-it-0125-static.txt</filename1>
+
<filename2>${PROJECT_HOME}/files/expected/0125-retry-static.txt</filename2>
+ <add_filename_result>N</add_filename_result>
+ <parallel>N</parallel>
+ <xloc>1184</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>delete field result</name>
+ <description/>
+ <type>DELETE_FILE</type>
+ <attributes/>
+ <fail_if_file_not_exists>N</fail_if_file_not_exists>
+ <filename>${java.io.tmpdir}/hop-it-0125-fields.txt</filename>
+ <parallel>N</parallel>
+ <xloc>1376</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>delete field marker</name>
+ <description/>
+ <type>DELETE_FILE</type>
+ <attributes/>
+ <fail_if_file_not_exists>N</fail_if_file_not_exists>
+ <filename>${java.io.tmpdir}/hop-it-0125-fields.marker</filename>
+ <parallel>N</parallel>
+ <xloc>1472</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>field values</name>
+ <description/>
+ <type>PIPELINE</type>
+ <attributes/>
+ <filename>${PROJECT_HOME}/0125-retry-fields-parent.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>
+ <set_logfile>N</set_logfile>
+ <loglevel>Basic</loglevel>
+ <set_append_logfile>N</set_append_logfile>
+ <wait_until_finished>Y</wait_until_finished>
+ <create_parent_folder>N</create_parent_folder>
+ <run_configuration>local</run_configuration>
+ <parameters>
+ <pass_all_parameters>Y</pass_all_parameters>
+ </parameters>
+ <parallel>N</parallel>
+ <xloc>1568</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>check field values</name>
+ <description/>
+ <type>FILE_COMPARE</type>
+ <attributes/>
+ <filename1>${java.io.tmpdir}/hop-it-0125-fields.txt</filename1>
+
<filename2>${PROJECT_HOME}/files/expected/0125-retry-fields.txt</filename2>
+ <add_filename_result>N</add_filename_result>
+ <parallel>N</parallel>
+ <xloc>1760</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>Success</name>
+ <description/>
+ <type>SUCCESS</type>
+ <attributes/>
+ <parallel>N</parallel>
+ <xloc>1952</xloc>
+ <yloc>64</yloc>
+ <attributes_hac/>
+ </action>
+ <action>
+ <name>Abort</name>
+ <description/>
+ <type>ABORT</type>
+ <attributes/>
+ <message>Pipeline Executor retry integration test failed</message>
+ <parallel>N</parallel>
+ <xloc>992</xloc>
+ <yloc>240</yloc>
+ <attributes_hac/>
+ </action>
+ </actions>
+ <hops>
+ <hop>
+ <from>Start</from>
+ <to>delete inherited result</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>Y</unconditional>
+ </hop>
+ <hop>
+ <from>delete inherited result</from>
+ <to>inherited variable</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>delete inherited result</from>
+ <to>Abort</to>
+ <enabled>Y</enabled>
+ <evaluation>N</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>inherited variable</from>
+ <to>check inherited variable</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>inherited variable</from>
+ <to>Abort</to>
+ <enabled>Y</enabled>
+ <evaluation>N</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>check inherited variable</from>
+ <to>delete static result</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>check inherited variable</from>
+ <to>Abort</to>
+ <enabled>Y</enabled>
+ <evaluation>N</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>delete static result</from>
+ <to>static variable cell</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>delete static result</from>
+ <to>Abort</to>
+ <enabled>Y</enabled>
+ <evaluation>N</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>static variable cell</from>
+ <to>check static variable cell</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>static variable cell</from>
+ <to>Abort</to>
+ <enabled>Y</enabled>
+ <evaluation>N</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>check static variable cell</from>
+ <to>delete field result</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>check static variable cell</from>
+ <to>Abort</to>
+ <enabled>Y</enabled>
+ <evaluation>N</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>delete field result</from>
+ <to>delete field marker</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>delete field result</from>
+ <to>Abort</to>
+ <enabled>Y</enabled>
+ <evaluation>N</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>delete field marker</from>
+ <to>field values</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>delete field marker</from>
+ <to>Abort</to>
+ <enabled>Y</enabled>
+ <evaluation>N</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>field values</from>
+ <to>check field values</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>field values</from>
+ <to>Abort</to>
+ <enabled>Y</enabled>
+ <evaluation>N</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>check field values</from>
+ <to>Success</to>
+ <enabled>Y</enabled>
+ <evaluation>Y</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ <hop>
+ <from>check field values</from>
+ <to>Abort</to>
+ <enabled>Y</enabled>
+ <evaluation>N</evaluation>
+ <unconditional>N</unconditional>
+ </hop>
+ </hops>
+ <notepads>
+ </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 eb2fa79b5a..7f68413bf7 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
@@ -20,16 +20,18 @@ package org.apache.hop.pipeline.transforms.pipelineexecutor;
import com.google.common.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.apache.hop.core.Const;
import org.apache.hop.core.IRowSet;
import org.apache.hop.core.Result;
import org.apache.hop.core.ResultFile;
import org.apache.hop.core.RowMetaAndData;
import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.exception.HopValueException;
import org.apache.hop.core.logging.HopLogStore;
import org.apache.hop.core.logging.LoggingRegistry;
import org.apache.hop.core.parameters.SubExecutionParameters;
@@ -37,6 +39,7 @@ import org.apache.hop.core.row.IRowMeta;
import org.apache.hop.core.row.RowDataUtil;
import org.apache.hop.core.row.RowMeta;
import org.apache.hop.core.util.Utils;
+import org.apache.hop.core.variables.IVariables;
import org.apache.hop.execution.ExecutionWait;
import org.apache.hop.i18n.BaseMessages;
import org.apache.hop.pipeline.Pipeline;
@@ -318,6 +321,10 @@ public class PipelineExecutor extends
BaseTransform<PipelineExecutorMeta, Pipeli
break;
}
+ if (attempt > 0) {
+ refreshRetriedParameterVariablesFromExternalScopes();
+ }
+
result = executePipelineAttempt(parameterValues, configuredTimeoutMs);
if (!isFailedResult(result) || attempt == retryAttempts) {
break;
@@ -330,6 +337,49 @@ public class PipelineExecutor extends
BaseTransform<PipelineExecutorMeta, Pipeli
return result;
}
+ @VisibleForTesting
+ void refreshRetriedParameterVariablesFromExternalScopes() {
+ Set<String> variableNamesToRefresh = new
LinkedHashSet<>(Arrays.asList(getVariableNames()));
+ List<PipelineExecutorParameters> parameters = meta.getParameters();
+ if (parameters != null) {
+ for (PipelineExecutorParameters parameter : parameters) {
+ if (parameter == null) {
+ continue;
+ }
+ String parameterVariable = Const.trim(parameter.getVariable());
+ if (!Utils.isEmpty(parameterVariable)) {
+ variableNamesToRefresh.add(parameterVariable);
+ }
+ }
+ }
+
+ for (String variableName : variableNamesToRefresh) {
+ String latestValue =
resolveRetriedVariableValueFromExternalScopes(variableName);
+ if (latestValue != null) {
+ setVariable(variableName, latestValue);
+ }
+ }
+ }
+
+ private String resolveRetriedVariableValueFromExternalScopes(String
variableName) {
+ if (Utils.isEmpty(variableName)) {
+ return null;
+ }
+
+ IPipelineEngine<PipelineMeta> pipeline = getPipeline();
+ IVariables parent = pipeline != null ? pipeline.getParentVariables() :
null;
+ int guard = 0;
+ while (parent != null && guard++ < 100) {
+ String value = parent.getVariable(variableName);
+ if (value != null) {
+ return value;
+ }
+ parent = parent.getParentVariables();
+ }
+
+ return System.getProperty(variableName);
+ }
+
private boolean waitRetryDelay(long retryDelayMs) {
long remainingDelayMs = retryDelayMs;
while (remainingDelayMs > 0) {
@@ -692,21 +742,29 @@ public class PipelineExecutor extends
BaseTransform<PipelineExecutorMeta, Pipeli
return getData().getExecutorPipeline();
}
- protected List<String> getLastIncomingFieldValues() {
+ protected List<String> getLastIncomingFieldValues() throws HopException {
PipelineExecutorData pipelineExecutorData = getData();
- List<String> lastIncomingFieldValues = new ArrayList<>();
- if (pipelineExecutorData == null ||
pipelineExecutorData.groupBuffer.isEmpty()) {
+ if (pipelineExecutorData == null
+ || pipelineExecutorData.groupBuffer == null
+ || pipelineExecutorData.groupBuffer.isEmpty()) {
return null;
}
- int lastIncomingFieldIndex = pipelineExecutorData.groupBuffer.size() - 1;
- ArrayList lastGroupBufferData =
- new ArrayList(
-
Arrays.asList(pipelineExecutorData.groupBuffer.get(lastIncomingFieldIndex).getData()));
- lastGroupBufferData.removeAll(Collections.singleton(null));
+ RowMetaAndData lastBufferedRow =
+
pipelineExecutorData.groupBuffer.get(pipelineExecutorData.groupBuffer.size() -
1);
+ IRowMeta rowMeta = lastBufferedRow.getRowMeta();
+ Object[] rowData = lastBufferedRow.getData();
+ if (rowMeta == null || rowData == null) {
+ return null;
+ }
- for (Object lastGroupBufferDatum : lastGroupBufferData) {
- lastIncomingFieldValues.add(lastGroupBufferDatum.toString());
+ List<String> lastIncomingFieldValues = new ArrayList<>(rowMeta.size());
+ for (int i = 0; i < rowMeta.size(); i++) {
+ try {
+ lastIncomingFieldValues.add(rowMeta.getString(rowData, i));
+ } catch (HopValueException e) {
+ throw new HopException("Error converting buffered row values", e);
+ }
}
return lastIncomingFieldValues;
}
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
index 388faf62c6..e0f88c1c14 100644
---
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
@@ -27,9 +27,11 @@ import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -42,13 +44,17 @@ import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Date;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
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.ValueMetaBoolean;
+import org.apache.hop.core.row.value.ValueMetaDate;
import org.apache.hop.core.row.value.ValueMetaString;
import org.apache.hop.core.variables.Variables;
import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
@@ -58,9 +64,11 @@ 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.apache.hop.workflow.engines.local.LocalWorkflowEngine;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
+import org.mockito.InOrder;
/** Unit tests for {@link PipelineExecutor} runtime behaviour (parameter
passing, helpers). */
class PipelineExecutorTest {
@@ -88,7 +96,7 @@ class PipelineExecutorTest {
}
@Test
- void getLastIncomingFieldValuesReturnsNullWhenBufferEmpty() {
+ void getLastIncomingFieldValuesReturnsNullWhenBufferEmpty() throws
HopException {
PipelineExecutorMeta meta = new PipelineExecutorMeta();
meta.setDefault();
PipelineExecutorData data = new PipelineExecutorData();
@@ -99,7 +107,7 @@ class PipelineExecutorTest {
}
@Test
- void getLastIncomingFieldValuesReturnsStringValuesFromLastBufferedRow() {
+ void getLastIncomingFieldValuesPreservesNullsFromLastBufferedRow() throws
HopException {
PipelineExecutorMeta meta = new PipelineExecutorMeta();
meta.setDefault();
PipelineExecutorData data = new PipelineExecutorData();
@@ -114,7 +122,29 @@ class PipelineExecutorTest {
List<String> last = executor.getLastIncomingFieldValues();
assertNotNull(last);
- assertEquals(Collections.singletonList("x"), last);
+ assertEquals(Arrays.asList("x", null), last);
+ }
+
+ @Test
+ void getLastIncomingFieldValuesUsesRowMetaStringConversion() throws
HopException {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ PipelineExecutorData data = new PipelineExecutorData();
+ data.groupBuffer = new ArrayList<>();
+ RowMeta rowMeta = new RowMeta();
+ rowMeta.addValueMeta(new ValueMetaString("s1"));
+ rowMeta.addValueMeta(new ValueMetaDate("d1"));
+ rowMeta.addValueMeta(new ValueMetaBoolean("b1"));
+ Date date = new Date(1761051000000L);
+ rowMeta.getValueMeta(1).setConversionMask("yyyy-MM-dd");
+ data.groupBuffer.add(new RowMetaAndData(rowMeta, new Object[] {"x", date,
true}));
+
+ PipelineExecutor executor = newExecutor(meta, data);
+ List<String> last = executor.getLastIncomingFieldValues();
+
+ assertNotNull(last);
+ assertEquals(
+ Arrays.asList("x",
rowMeta.getString(data.groupBuffer.get(0).getData(), 1), "Y"), last);
}
@Test
@@ -308,6 +338,140 @@ class PipelineExecutorTest {
assertEquals("B2", child.getVariable("TEST3_PARAMETER2"));
}
+ @Test
+ void
refreshRetriedParameterVariablesFromExternalScopesUsesLatestJvmValueForUnmappedParameter()
{
+ final String variableName = "ACCESS_TOKEN";
+ final String originalProperty = System.getProperty(variableName);
+ try {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ meta.setInheritingAllVariables(true);
+ PipelineExecutorParameters param = new PipelineExecutorParameters();
+ param.setVariable(variableName);
+ param.setField("");
+ param.setInput("");
+ meta.setParameters(new ArrayList<>(Collections.singletonList(param)));
+
+ PipelineExecutor executor = newExecutor(meta, new
PipelineExecutorData());
+ executor.setVariable(variableName, "stale-token");
+ Variables parent = new Variables();
+ parent.initializeFrom(null);
+ parent.setVariable(variableName, "stale-parent-token");
+ executor.getPipeline().initializeFrom(parent);
+ parent.setVariable(variableName, null);
+ System.setProperty(variableName, "mynewtoken");
+
+ executor.refreshRetriedParameterVariablesFromExternalScopes();
+
+ assertEquals("mynewtoken", executor.getVariable(variableName));
+ } finally {
+ if (originalProperty == null) {
+ System.clearProperty(variableName);
+ } else {
+ System.setProperty(variableName, originalProperty);
+ }
+ }
+ }
+
+ @Test
+ void
refreshRetriedParameterVariablesFromExternalScopesPrefersParentPipelineValueOverJvm()
{
+ final String variableName = "ACCESS_TOKEN";
+ final String originalProperty = System.getProperty(variableName);
+ try {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ meta.setInheritingAllVariables(true);
+ PipelineExecutorParameters param = new PipelineExecutorParameters();
+ param.setVariable(variableName);
+ param.setField("");
+ param.setInput("");
+ meta.setParameters(new ArrayList<>(Collections.singletonList(param)));
+
+ PipelineExecutor executor = newExecutor(meta, new
PipelineExecutorData());
+ executor.setVariable(variableName, "stale-token");
+ LocalWorkflowEngine parentWorkflow = new LocalWorkflowEngine();
+ parentWorkflow.initializeFrom(null);
+ parentWorkflow.setVariable(variableName, "parent-token");
+ executor.getPipeline().initializeFrom(parentWorkflow);
+ System.setProperty(variableName, "jvm-token");
+
+ executor.refreshRetriedParameterVariablesFromExternalScopes();
+
+ assertEquals("parent-token", executor.getVariable(variableName));
+ } finally {
+ if (originalProperty == null) {
+ System.clearProperty(variableName);
+ } else {
+ System.setProperty(variableName, originalProperty);
+ }
+ }
+ }
+
+ @Test
+ void
refreshRetriedParameterVariablesFromExternalScopesRefreshesInheritedParameterNotOnGrid()
+ throws HopException {
+ final String variableName = "ACCESS_TOKEN";
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ meta.setInheritingAllVariables(true);
+ meta.setParameters(new ArrayList<>());
+
+ PipelineMeta childMeta = new PipelineMeta();
+ childMeta.setName("child");
+ childMeta.addParameterDefinition(variableName, "", "token");
+ LocalPipelineEngine child = new LocalPipelineEngine(childMeta);
+ child.copyParametersFromDefinitions(childMeta);
+
+ PipelineExecutorData data = new PipelineExecutorData();
+ data.setExecutorPipeline(child);
+
+ PipelineExecutor executor = newExecutor(meta, data);
+ executor.setVariable(variableName, "stale-token");
+ executor.getPipeline().setVariable(variableName, "stale-copied-token");
+ Variables parent = new Variables();
+ parent.setVariable(variableName, "fresh-token");
+ executor.getPipeline().setParentVariables(parent);
+
+ executor.refreshRetriedParameterVariablesFromExternalScopes();
+ executor.passParametersToPipeline(Collections.emptyList());
+
+ assertEquals("fresh-token", child.getVariable(variableName));
+ }
+
+ @Test
+ void
refreshRetriedParameterVariablesFromExternalScopesRefreshesVariableUsedInStaticInput()
+ throws HopException {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ meta.setInheritingAllVariables(true);
+ PipelineExecutorParameters param = new PipelineExecutorParameters();
+ param.setVariable("P");
+ param.setField("");
+ param.setInput("${ACCESS_TOKEN}");
+ meta.setParameters(new ArrayList<>(Collections.singletonList(param)));
+
+ PipelineMeta childMeta = new PipelineMeta();
+ childMeta.setName("child");
+ childMeta.addParameterDefinition("P", "", "mapped from static");
+ LocalPipelineEngine child = new LocalPipelineEngine(childMeta);
+ child.copyParametersFromDefinitions(childMeta);
+
+ PipelineExecutorData data = new PipelineExecutorData();
+ data.setExecutorPipeline(child);
+
+ PipelineExecutor executor = newExecutor(meta, data);
+ executor.setVariable("ACCESS_TOKEN", "stale-token");
+ executor.getPipeline().setVariable("ACCESS_TOKEN", "stale-copied-token");
+ Variables parent = new Variables();
+ parent.setVariable("ACCESS_TOKEN", "fresh-token");
+ executor.getPipeline().setParentVariables(parent);
+
+ executor.refreshRetriedParameterVariablesFromExternalScopes();
+ executor.passParametersToPipeline(Collections.emptyList());
+
+ assertEquals("fresh-token", child.getVariable("P"));
+ }
+
@Test
void collectPipelineResultsDoesNotForwardRowsWhenNoResultRowsTarget() throws
HopException {
PipelineExecutorMeta meta = new PipelineExecutorMeta();
@@ -478,6 +642,103 @@ class PipelineExecutorTest {
verify(executor, times(2)).executePipelineAttempt(any(), anyLong());
}
+ @Test
+ void executeWithRetriesKeepsAttemptZeroParameterValuesOnRetryAttempt()
throws HopException {
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ meta.setRetryAttempts("1");
+ meta.setRetryDelay("0");
+
+ PipelineExecutorData data = new PipelineExecutorData();
+ data.groupBuffer = new ArrayList<>();
+ RowMeta bufferedRowMeta = new RowMeta();
+ bufferedRowMeta.addValueMeta(new ValueMetaString("col1"));
+ bufferedRowMeta.addValueMeta(new ValueMetaDate("col2"));
+ bufferedRowMeta.addValueMeta(new ValueMetaBoolean("col3"));
+ bufferedRowMeta.getValueMeta(1).setConversionMask("yyyy-MM-dd");
+ data.groupBuffer.add(
+ new RowMetaAndData(bufferedRowMeta, new Object[] {null, new
Date(1761051000000L), true}));
+
+ PipelineExecutor executor = spy(newExecutor(meta, data));
+
+ Result failed = new Result();
+ failed.setResult(false);
+ failed.setNrErrors(1);
+ Result success = new Result();
+ success.setResult(true);
+ success.setNrErrors(0);
+
+ doReturn(failed, success).when(executor).executePipelineAttempt(any(),
anyLong());
+ Result result = executor.executeWithRetries(Arrays.asList("from-row",
null, "N"));
+
+ assertTrue(result.isResult());
+ InOrder attemptOrder = inOrder(executor);
+ attemptOrder
+ .verify(executor)
+ .executePipelineAttempt(eq(Arrays.asList("from-row", null, "N")),
anyLong());
+ attemptOrder
+ .verify(executor)
+ .executePipelineAttempt(eq(Arrays.asList("from-row", null, "N")),
anyLong());
+ }
+
+ @Test
+ void executeWithRetriesRefreshesFromInitializedParentBeforeRetryAttempt()
throws HopException {
+ final String variableName = "ACCESS_TOKEN";
+ PipelineExecutorMeta meta = new PipelineExecutorMeta();
+ meta.setDefault();
+ meta.setInheritingAllVariables(true);
+ meta.setRetryAttempts("1");
+ meta.setRetryDelay("0");
+ PipelineExecutorParameters param = new PipelineExecutorParameters();
+ param.setVariable(variableName);
+ param.setField("");
+ param.setInput("");
+ meta.setParameters(new ArrayList<>(Collections.singletonList(param)));
+
+ PipelineMeta childMeta = new PipelineMeta();
+ childMeta.setName("child");
+ childMeta.addParameterDefinition(variableName, "", "token");
+ LocalPipelineEngine child = new LocalPipelineEngine(childMeta);
+ child.copyParametersFromDefinitions(childMeta);
+
+ PipelineExecutorData data = new PipelineExecutorData();
+ data.setExecutorPipeline(child);
+ PipelineExecutor executor = spy(newExecutor(meta, data));
+
+ LocalWorkflowEngine parentWorkflow = new LocalWorkflowEngine();
+ parentWorkflow.initializeFrom(null);
+ parentWorkflow.setVariable(variableName, "old-token");
+ executor.getPipeline().initializeFrom(parentWorkflow);
+ executor.setVariable(variableName, "old-token");
+
+ List<String> childParameterByAttempt = new ArrayList<>();
+ AtomicInteger attemptCounter = new AtomicInteger();
+ doAnswer(
+ invocation -> {
+ int attempt = attemptCounter.getAndIncrement();
+ executor.passParametersToPipeline(Collections.emptyList());
+ childParameterByAttempt.add(child.getVariable(variableName));
+
+ Result result = new Result();
+ if (attempt == 0) {
+ parentWorkflow.setVariable(variableName, "new-token");
+ result.setResult(false);
+ result.setNrErrors(1);
+ } else {
+ result.setResult(true);
+ result.setNrErrors(0);
+ }
+ return result;
+ })
+ .when(executor)
+ .executePipelineAttempt(any(), anyLong());
+
+ Result result = executor.executeWithRetries(Collections.emptyList());
+
+ assertTrue(result.isResult());
+ assertEquals(Arrays.asList("old-token", "new-token"),
childParameterByAttempt);
+ }
+
@Test
void executeWithRetriesCleansUpPreviousAttemptOnlyWhenRetrying() throws
HopException {
PipelineExecutorMeta meta = new PipelineExecutorMeta();