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 67b7994987 Fix Wait for SQL verification reading the missing property 
WaitForSQL (#9013)
67b7994987 is described below

commit 67b799498732dcf35792cf245ee20a416c1b258d
Author: Lance <[email protected]>
AuthorDate: Mon Oct 19 16:50:19 2026 +0800

    Fix Wait for SQL verification reading the missing property WaitForSQL 
(#9013)
    
    Signed-off-by: leehaut <[email protected]>
---
 .../actions/waitforsql/ActionWaitForSql.java       | 249 ++-------------------
 .../waitforsql/ActionWaitForSqlCheckTest.java      | 170 ++++++++++++++
 .../actions/waitforsql/ActionWaitForSqlTest.java   |  78 +++++++
 .../resources/wf-wait-for-sql-ghost-property.hwf   |  73 ++++++
 4 files changed, 342 insertions(+), 228 deletions(-)

diff --git 
a/plugins/actions/waitforsql/src/main/java/org/apache/hop/workflow/actions/waitforsql/ActionWaitForSql.java
 
b/plugins/actions/waitforsql/src/main/java/org/apache/hop/workflow/actions/waitforsql/ActionWaitForSql.java
index 147c22f935..ce3ce30192 100644
--- 
a/plugins/actions/waitforsql/src/main/java/org/apache/hop/workflow/actions/waitforsql/ActionWaitForSql.java
+++ 
b/plugins/actions/waitforsql/src/main/java/org/apache/hop/workflow/actions/waitforsql/ActionWaitForSql.java
@@ -19,6 +19,8 @@ package org.apache.hop.workflow.actions.waitforsql;
 
 import java.util.ArrayList;
 import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
 import org.apache.hop.core.Const;
 import org.apache.hop.core.ICheckResult;
 import org.apache.hop.core.Result;
@@ -47,6 +49,8 @@ import 
org.apache.hop.workflow.action.validator.ActionValidatorUtils;
 import org.apache.hop.workflow.action.validator.AndValidator;
 
 /** This defines a Wait for SQL data action */
+@Getter
+@Setter
 @Action(
     id = "WAIT_FOR_SQL",
     name = "i18n::ActionWaitForSQL.Name",
@@ -114,6 +118,7 @@ public class ActionWaitForSql extends ActionBase implements 
Cloneable, IAction {
   public static final int SUCCESS_CONDITION_ROWS_COUNT_GREATER = 4;
   public static final int SUCCESS_CONDITION_ROWS_COUNT_GREATER_EQUAL = 5;
 
+  @Getter
   public enum SuccessCondition implements IEnumHasCodeAndDescription {
     ROWS_COUNT_EQUAL(
         "rows_count_equal",
@@ -155,26 +160,6 @@ public class ActionWaitForSql extends ActionBase 
implements Cloneable, IAction {
     public static SuccessCondition lookupCode(String code) {
       return IEnumHasCode.lookupCode(SuccessCondition.class, code, 
ROWS_COUNT_EQUAL);
     }
-
-    /**
-     * Gets code
-     *
-     * @return value of code
-     */
-    @Override
-    public String getCode() {
-      return code;
-    }
-
-    /**
-     * Gets description
-     *
-     * @return value of description
-     */
-    @Override
-    public String getDescription() {
-      return description;
-    }
   }
 
   private static final String DEFAULT_MAXIMUM_TIMEOUT = "0"; // infinite 
timeout
@@ -201,10 +186,6 @@ public class ActionWaitForSql extends ActionBase 
implements Cloneable, IAction {
     this("");
   }
 
-  public SuccessCondition getSuccessCondition() {
-    return successCondition;
-  }
-
   @Override
   public boolean isEvaluation() {
     return true;
@@ -524,216 +505,28 @@ public class ActionWaitForSql extends ActionBase 
implements Cloneable, IAction {
     ActionValidatorUtils.andValidator()
         .validate(
             this,
-            "WaitForSQL",
+            "connection",
             remarks,
             
AndValidator.putValidators(ActionValidatorUtils.notBlankValidator()));
-  }
-
-  /**
-   * Gets isClearResultList
-   *
-   * @return value of isClearResultList
-   */
-  public boolean isClearResultList() {
-    return clearResultList;
-  }
-
-  /**
-   * @param isClearResultList The isClearResultList to set
-   */
-  public void setClearResultList(boolean isClearResultList) {
-    this.clearResultList = isClearResultList;
-  }
-
-  /**
-   * Gets isAddRowsResult
-   *
-   * @return value of isAddRowsResult
-   */
-  public boolean isAddRowsResult() {
-    return addRowsResult;
-  }
-
-  /**
-   * @param isAddRowsResult The isAddRowsResult to set
-   */
-  public void setAddRowsResult(boolean isAddRowsResult) {
-    this.addRowsResult = isAddRowsResult;
-  }
-
-  /**
-   * Gets isUseVars
-   *
-   * @return value of isUseVars
-   */
-  public boolean isUseVars() {
-    return useVars;
-  }
-
-  /**
-   * @param isUseVars The isUseVars to set
-   */
-  public void setUseVars(boolean isUseVars) {
-    this.useVars = isUseVars;
-  }
-
-  /**
-   * Gets isCustomSql
-   *
-   * @return value of isCustomSql
-   */
-  public boolean isCustomSqlEnabled() {
-    return customSqlEnabled;
-  }
-
-  /**
-   * @param isCustomSql The isCustomSql to set
-   */
-  public void setCustomSqlEnabled(boolean isCustomSql) {
-    this.customSqlEnabled = isCustomSql;
-  }
-
-  /**
-   * Gets customSql
-   *
-   * @return value of customSql
-   */
-  public String getCustomSql() {
-    return customSql;
-  }
-
-  /**
-   * @param customSql The customSql to set
-   */
-  public void setCustomSql(String customSql) {
-    this.customSql = customSql;
-  }
-
-  /**
-   * Gets tableName
-   *
-   * @return value of tableName
-   */
-  public String getTableName() {
-    return tableName;
-  }
 
-  /**
-   * @param tableName The tableName to set
-   */
-  public void setTableName(String tableName) {
-    this.tableName = tableName;
-  }
-
-  /**
-   * Gets schemaName
-   *
-   * @return value of schemaName
-   */
-  public String getSchemaName() {
-    return schemaName;
-  }
-
-  /**
-   * @param schemaName The schemaName to set
-   */
-  public void setSchemaName(String schemaName) {
-    this.schemaName = schemaName;
-  }
-
-  /**
-   * Gets maximumTimeout
-   *
-   * @return value of maximumTimeout
-   */
-  public String getMaximumTimeout() {
-    return maximumTimeout;
-  }
-
-  /**
-   * @param maximumTimeout The maximumTimeout to set
-   */
-  public void setMaximumTimeout(String maximumTimeout) {
-    this.maximumTimeout = maximumTimeout;
-  }
-
-  /**
-   * Gets checkCycleTime
-   *
-   * @return value of checkCycleTime
-   */
-  public String getCheckCycleTime() {
-    return checkCycleTime;
-  }
-
-  /**
-   * @param checkCycleTime The checkCycleTime to set
-   */
-  public void setCheckCycleTime(String checkCycleTime) {
-    this.checkCycleTime = checkCycleTime;
-  }
-
-  /**
-   * Gets successOnTimeout
-   *
-   * @return value of successOnTimeout
-   */
-  public boolean isSuccessOnTimeout() {
-    return successOnTimeout;
-  }
-
-  /**
-   * @param successOnTimeout The successOnTimeout to set
-   */
-  public void setSuccessOnTimeout(boolean successOnTimeout) {
-    this.successOnTimeout = successOnTimeout;
+    if (customSqlEnabled) {
+      ActionValidatorUtils.andValidator()
+          .validate(
+              this,
+              "customSql",
+              remarks,
+              
AndValidator.putValidators(ActionValidatorUtils.notBlankValidator()));
+    } else {
+      ActionValidatorUtils.andValidator()
+          .validate(
+              this,
+              "tableName",
+              remarks,
+              
AndValidator.putValidators(ActionValidatorUtils.notBlankValidator()));
+    }
   }
 
-  /**
-   * Gets selectCount
-   *
-   * @return value of selectCount
-   */
   public static String getSelectCount() {
     return SELECT_COUNT;
   }
-
-  /**
-   * Gets rowsCountValue
-   *
-   * @return value of rowsCountValue
-   */
-  public String getRowsCountValue() {
-    return rowsCountValue;
-  }
-
-  /**
-   * @param rowsCountValue The rowsCountValue to set
-   */
-  public void setRowsCountValue(String rowsCountValue) {
-    this.rowsCountValue = rowsCountValue;
-  }
-
-  /**
-   * @param successCondition The successCondition to set
-   */
-  public void setSuccessCondition(SuccessCondition successCondition) {
-    this.successCondition = successCondition;
-  }
-
-  /**
-   * Gets connection
-   *
-   * @return value of connection
-   */
-  public String getConnection() {
-    return connection;
-  }
-
-  /**
-   * @param connection The connection to set
-   */
-  public void setConnection(String connection) {
-    this.connection = connection;
-  }
 }
diff --git 
a/plugins/actions/waitforsql/src/test/java/org/apache/hop/workflow/actions/waitforsql/ActionWaitForSqlCheckTest.java
 
b/plugins/actions/waitforsql/src/test/java/org/apache/hop/workflow/actions/waitforsql/ActionWaitForSqlCheckTest.java
new file mode 100644
index 0000000000..83d29235f5
--- /dev/null
+++ 
b/plugins/actions/waitforsql/src/test/java/org/apache/hop/workflow/actions/waitforsql/ActionWaitForSqlCheckTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.workflow.actions.waitforsql;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.hop.core.HopClientEnvironment;
+import org.apache.hop.core.ICheckResult;
+import org.apache.hop.core.annotations.Action;
+import org.apache.hop.core.plugins.ActionPluginType;
+import org.apache.hop.core.plugins.PluginRegistry;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.metadata.serializer.memory.MemoryMetadataProvider;
+import org.apache.hop.workflow.WorkflowMeta;
+import org.apache.hop.workflow.actions.start.ActionStart;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+/** Check validates connection plus tableName or customSql, never the ghost 
property WaitForSQL. */
+class ActionWaitForSqlCheckTest {
+
+  @BeforeAll
+  static void initPlugins() throws Exception {
+    HopClientEnvironment.init();
+    PluginRegistry registry = PluginRegistry.getInstance();
+    registry.registerPluginType(ActionPluginType.class);
+    registry.registerPluginClass(ActionStart.class.getName(), 
ActionPluginType.class, Action.class);
+    registry.registerPluginClass(
+        ActionWaitForSql.class.getName(), ActionPluginType.class, 
Action.class);
+  }
+
+  @Test
+  void blankConnectionAndTableAreReported() {
+    ActionWaitForSql action = new ActionWaitForSql("Wait for SQL");
+
+    assertEquals(
+        List.of("connection cannot be null or blank.", "tableName cannot be 
null or blank."),
+        errors(action));
+  }
+
+  @Test
+  void whitespaceConnectionAndTableAreReported() {
+    ActionWaitForSql action = new ActionWaitForSql("Wait for SQL");
+    action.setConnection("  ");
+    action.setTableName("\t");
+    action.setCustomSql("select 1");
+
+    assertEquals(
+        List.of("connection cannot be null or blank.", "tableName cannot be 
null or blank."),
+        errors(action));
+  }
+
+  @Test
+  void connectionAndTablePassWithoutReadingCustomSql() {
+    ActionWaitForSql action = readyTableAction();
+    action.setCustomSql("   ");
+
+    assertEquals(List.of(), errors(action));
+    assertTrue(texts(action).contains("connection is OK. Value: dummy-db"));
+    assertTrue(texts(action).contains("tableName is OK. Value: dummy_table"));
+    assertFalse(texts(action).stream().anyMatch(text -> 
text.contains("customSql")));
+  }
+
+  @Test
+  void customSqlBlankIsReportedAndTableNameIsIgnored() {
+    ActionWaitForSql action = new ActionWaitForSql("Wait for SQL");
+    action.setConnection("dummy-db");
+    action.setCustomSqlEnabled(true);
+    action.setCustomSql("  ");
+
+    assertEquals(List.of("customSql cannot be null or blank."), 
errors(action));
+  }
+
+  @Test
+  void customSqlAndConnectionPassWithoutATable() {
+    ActionWaitForSql action = new ActionWaitForSql("Wait for SQL");
+    action.setConnection("dummy-db");
+    action.setCustomSqlEnabled(true);
+    action.setCustomSql("SELECT count(*) FROM dummy_table");
+
+    assertEquals(List.of(), errors(action));
+    assertTrue(texts(action).contains("customSql is OK. Value: SELECT count(*) 
FROM..."));
+  }
+
+  @Test
+  void customSqlStillRequiresAConnection() {
+    ActionWaitForSql action = new ActionWaitForSql("Wait for SQL");
+    action.setCustomSqlEnabled(true);
+    action.setCustomSql("SELECT count(*) FROM dummy_table");
+    action.setTableName("dummy_table");
+
+    assertEquals(List.of("connection cannot be null or blank."), 
errors(action));
+  }
+
+  @Test
+  void loadedWorkflowDoesNotReportGhostProperty() throws Exception {
+    MemoryMetadataProvider provider = new MemoryMetadataProvider();
+    Variables variables = new Variables();
+    WorkflowMeta workflowMeta;
+    try (InputStream in =
+        ActionWaitForSqlCheckTest.class.getResourceAsStream(
+            "/wf-wait-for-sql-ghost-property.hwf")) {
+      workflowMeta = new WorkflowMeta(in, provider, variables);
+    }
+
+    ActionWaitForSql action =
+        (ActionWaitForSql) workflowMeta.findAction("Wait for SQL").getAction();
+    assertEquals("dummy-db", action.getConnection());
+    assertEquals("dummy_table", action.getTableName());
+    assertFalse(action.isCustomSqlEnabled());
+
+    List<ICheckResult> remarks = new ArrayList<>();
+    workflowMeta.checkActions(remarks, false, null, variables, provider);
+    List<String> texts = remarks.stream().map(ICheckResult::getText).toList();
+
+    assertEquals(List.of(), errorTexts(remarks));
+    assertFalse(texts.stream().anyMatch(text -> text.contains("WaitForSQL")));
+    assertTrue(texts.contains("connection is OK. Value: dummy-db"));
+    assertTrue(texts.contains("tableName is OK. Value: dummy_table"));
+  }
+
+  private static ActionWaitForSql readyTableAction() {
+    ActionWaitForSql action = new ActionWaitForSql("Wait for SQL");
+    action.setConnection("dummy-db");
+    action.setTableName("dummy_table");
+    action.setCustomSqlEnabled(false);
+    return action;
+  }
+
+  private static List<String> errors(ActionWaitForSql action) {
+    return errorTexts(remarks(action));
+  }
+
+  private static List<String> texts(ActionWaitForSql action) {
+    return remarks(action).stream().map(ICheckResult::getText).toList();
+  }
+
+  private static List<ICheckResult> remarks(ActionWaitForSql action) {
+    List<ICheckResult> remarks = new ArrayList<>();
+    action.check(remarks, new WorkflowMeta(), new Variables(), null);
+    return remarks;
+  }
+
+  private static List<String> errorTexts(List<ICheckResult> remarks) {
+    return remarks.stream()
+        .filter(remark -> remark.getType() == ICheckResult.TYPE_RESULT_ERROR)
+        .map(ICheckResult::getText)
+        .toList();
+  }
+}
diff --git 
a/plugins/actions/waitforsql/src/test/java/org/apache/hop/workflow/actions/waitforsql/ActionWaitForSqlTest.java
 
b/plugins/actions/waitforsql/src/test/java/org/apache/hop/workflow/actions/waitforsql/ActionWaitForSqlTest.java
index 73f8d6611a..7042de812b 100644
--- 
a/plugins/actions/waitforsql/src/test/java/org/apache/hop/workflow/actions/waitforsql/ActionWaitForSqlTest.java
+++ 
b/plugins/actions/waitforsql/src/test/java/org/apache/hop/workflow/actions/waitforsql/ActionWaitForSqlTest.java
@@ -19,13 +19,17 @@ package org.apache.hop.workflow.actions.waitforsql;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.util.List;
 import org.apache.hop.core.HopClientEnvironment;
 import org.apache.hop.core.database.DatabaseMeta;
+import org.apache.hop.i18n.BaseMessages;
 import org.apache.hop.metadata.serializer.memory.MemoryMetadataProvider;
 import org.apache.hop.workflow.action.ActionSerializationTestUtil;
 import 
org.apache.hop.workflow.actions.waitforsql.ActionWaitForSql.SuccessCondition;
+import org.jetbrains.annotations.NotNull;
 import org.junit.jupiter.api.Test;
 
 /** Unit tests for wait for sql action. */
@@ -58,4 +62,78 @@ class ActionWaitForSqlTest {
     assertFalse(action.isSuccessOnTimeout());
     assertTrue(action.isClearResultList());
   }
+
+  @Test
+  void constructorUsesDocumentedDefaults() {
+    ActionWaitForSql action = new ActionWaitForSql("Wait for SQL");
+
+    assertEquals(
+        List.of(true, false, false, false, false),
+        List.of(
+            action.isClearResultList(),
+            action.isAddRowsResult(),
+            action.isUseVars(),
+            action.isCustomSqlEnabled(),
+            action.isSuccessOnTimeout()));
+    assertNull(action.getConnection());
+    assertNull(action.getSchemaName());
+    assertNull(action.getTableName());
+    assertNull(action.getCustomSql());
+    assertEquals(
+        List.of("0", "0", "60"),
+        List.of(
+            action.getRowsCountValue(), action.getMaximumTimeout(), 
action.getCheckCycleTime()));
+    assertEquals(SuccessCondition.ROWS_COUNT_GREATER, 
action.getSuccessCondition());
+    assertEquals("SELECT count(*) FROM ", ActionWaitForSql.getSelectCount());
+  }
+
+  @Test
+  void accessorsRoundTrip() {
+    ActionWaitForSql action = getActionWaitForSql();
+
+    assertEquals(
+        List.of(false, true, true, true, true),
+        List.of(
+            action.isClearResultList(),
+            action.isAddRowsResult(),
+            action.isUseVars(),
+            action.isCustomSqlEnabled(),
+            action.isSuccessOnTimeout()));
+    assertEquals(
+        List.of("dummy-db", "public", "dummy_table", "select 1", "3", "30", 
"5"),
+        List.of(
+            action.getConnection(),
+            action.getSchemaName(),
+            action.getTableName(),
+            action.getCustomSql(),
+            action.getRowsCountValue(),
+            action.getMaximumTimeout(),
+            action.getCheckCycleTime()));
+    assertEquals(SuccessCondition.ROWS_COUNT_EQUAL, 
action.getSuccessCondition());
+    assertEquals("rows_count_equal", action.getSuccessCondition().getCode());
+    assertEquals(
+        BaseMessages.getString(
+            ActionWaitForSql.class, 
"ActionWaitForSQL.SuccessWhenRowCountEqual.Label"),
+        action.getSuccessCondition().getDescription());
+    assertEquals(
+        SuccessCondition.ROWS_COUNT_EQUAL, 
SuccessCondition.lookupCode("rows_count_equal"));
+  }
+
+  private static @NotNull ActionWaitForSql getActionWaitForSql() {
+    ActionWaitForSql action = new ActionWaitForSql("Action wait for SQL");
+    action.setClearResultList(false);
+    action.setAddRowsResult(true);
+    action.setUseVars(true);
+    action.setCustomSqlEnabled(true);
+    action.setSuccessOnTimeout(true);
+    action.setConnection("dummy-db");
+    action.setSchemaName("public");
+    action.setTableName("dummy_table");
+    action.setCustomSql("select 1");
+    action.setRowsCountValue("3");
+    action.setMaximumTimeout("30");
+    action.setCheckCycleTime("5");
+    action.setSuccessCondition(SuccessCondition.ROWS_COUNT_EQUAL);
+    return action;
+  }
 }
diff --git 
a/plugins/actions/waitforsql/src/test/resources/wf-wait-for-sql-ghost-property.hwf
 
b/plugins/actions/waitforsql/src/test/resources/wf-wait-for-sql-ghost-property.hwf
new file mode 100644
index 0000000000..55195cdbf6
--- /dev/null
+++ 
b/plugins/actions/waitforsql/src/test/resources/wf-wait-for-sql-ghost-property.hwf
@@ -0,0 +1,73 @@
+<?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>wf-wait-for-sql-ghost-property</name>
+  <name_sync_with_filename>Y</name_sync_with_filename>
+  <description/>
+  <extended_description/>
+  <created_user>-</created_user>
+  <modified_user>-</modified_user>
+  <created_date>2026/10/19 09:00:00.000</created_date>
+  <modified_date>2026/10/19 09:00:00.000</modified_date>
+  <parameters/>
+  <actions>
+    <action>
+      <name>Start</name>
+      <description/>
+      <type>SPECIAL</type>
+      <attributes/>
+      <xloc>80</xloc>
+      <yloc>80</yloc>
+      <parallel>N</parallel>
+    </action>
+    <action>
+      <clear_result_rows>N</clear_result_rows>
+      <add_rows_result>N</add_rows_result>
+      <is_usevars>N</is_usevars>
+      <is_custom_sql>N</is_custom_sql>
+      <custom_sql/>
+      <connection>dummy-db</connection>
+      <tablename>dummy_table</tablename>
+      <schemaname/>
+      <maximum_timeout>0</maximum_timeout>
+      <check_cycle_time>5</check_cycle_time>
+      <success_on_timeout>N</success_on_timeout>
+      <rows_count_value>0</rows_count_value>
+      <success_condition>rows_count_greater</success_condition>
+      <name>Wait for SQL</name>
+      <description/>
+      <type>WAIT_FOR_SQL</type>
+      <attributes/>
+      <xloc>320</xloc>
+      <yloc>80</yloc>
+      <parallel>N</parallel>
+    </action>
+  </actions>
+  <hops>
+    <hop>
+      <from>Start</from>
+      <to>Wait for SQL</to>
+      <evaluation>Y</evaluation>
+      <unconditional>Y</unconditional>
+      <enabled>Y</enabled>
+    </hop>
+  </hops>
+  <notepads/>
+</workflow>

Reply via email to