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 edad1b975e Fix#7298 Dynamic SQL row dialog: restore Template SQL 
editor visibility (#7313)
edad1b975e is described below

commit edad1b975ee25af7667aded21f0ae57b430542b4
Author: Lance <[email protected]>
AuthorDate: Fri Jun 19 17:50:22 2026 +0800

    Fix#7298 Dynamic SQL row dialog: restore Template SQL editor visibility 
(#7313)
    
    * Dynamic SQL row: Template SQL (to retrieve Meta data) editor field is not 
visible
    
    Signed-off-by: lance <[email protected]>
    
    * Fix org.apache.hop.core.database.DatabaseMeta.getReservedWords() 
DatabaseMeta is null
    
    Signed-off-by: lance <[email protected]>
    
    ---------
    
    Signed-off-by: lance <[email protected]>
---
 .../apache/hop/core/row/ValueMetaAndDataTests.java |   3 +
 .../dynamicsqlrow/DynamicSqlRowData.java           |   7 +-
 .../dynamicsqlrow/DynamicSqlRowDialog.java         |  60 ++---
 .../dynamicsqlrow/DynamicSqlRowMeta.java           | 124 +---------
 .../dynamicsqlrow/DynamicSqlRowDataTest.java}      |  57 ++---
 .../dynamicsqlrow/DynamicSqlRowMetaTest.java       | 250 ++++++++++++++++++++-
 .../dynamicsqlrow/DynamicSqlRowTest.java           | 211 +++++++++++++++++
 7 files changed, 531 insertions(+), 181 deletions(-)

diff --git 
a/core/src/test/java/org/apache/hop/core/row/ValueMetaAndDataTests.java 
b/core/src/test/java/org/apache/hop/core/row/ValueMetaAndDataTests.java
index 9e1d0e4a36..446c8fc970 100644
--- a/core/src/test/java/org/apache/hop/core/row/ValueMetaAndDataTests.java
+++ b/core/src/test/java/org/apache/hop/core/row/ValueMetaAndDataTests.java
@@ -34,11 +34,14 @@ import org.apache.hop.core.row.value.ValueMetaNumber;
 import org.apache.hop.core.row.value.ValueMetaSerializable;
 import org.apache.hop.core.row.value.ValueMetaString;
 import org.apache.hop.core.xml.XmlHandler;
+import org.apache.hop.junit.rules.RestoreHopEnvironmentExtension;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
 import org.w3c.dom.Node;
 
 /** Unit test for {@link ValueMetaAndData} */
+@ExtendWith(RestoreHopEnvironmentExtension.class)
 class ValueMetaAndDataTests {
 
   @BeforeAll
diff --git 
a/plugins/transforms/dynamicsqlrow/src/main/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowData.java
 
b/plugins/transforms/dynamicsqlrow/src/main/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowData.java
index 679651b0a7..3669630bed 100644
--- 
a/plugins/transforms/dynamicsqlrow/src/main/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowData.java
+++ 
b/plugins/transforms/dynamicsqlrow/src/main/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowData.java
@@ -18,6 +18,7 @@
 package org.apache.hop.pipeline.transforms.dynamicsqlrow;
 
 import java.util.ArrayList;
+import java.util.List;
 import org.apache.hop.core.database.Database;
 import org.apache.hop.core.row.IRowMeta;
 import org.apache.hop.pipeline.transform.BaseTransformData;
@@ -29,8 +30,8 @@ public class DynamicSqlRowData extends BaseTransformData 
implements ITransformDa
   IRowMeta lookupRowMeta;
 
   public Database db;
-
-  public Object[] notfound; // Values in case nothing is found...
+  // Values in case nothing is found...
+  public Object[] notfound;
 
   public int indexOfSqlField;
 
@@ -38,7 +39,7 @@ public class DynamicSqlRowData extends BaseTransformData 
implements ITransformDa
 
   public String previousSql;
 
-  public ArrayList<Object[]> previousrowbuffer;
+  public List<Object[]> previousrowbuffer;
 
   public boolean isCanceled;
 
diff --git 
a/plugins/transforms/dynamicsqlrow/src/main/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowDialog.java
 
b/plugins/transforms/dynamicsqlrow/src/main/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowDialog.java
index 9993a904b8..a0c76d2d8e 100644
--- 
a/plugins/transforms/dynamicsqlrow/src/main/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowDialog.java
+++ 
b/plugins/transforms/dynamicsqlrow/src/main/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowDialog.java
@@ -46,12 +46,12 @@ import org.eclipse.swt.events.FocusEvent;
 import org.eclipse.swt.events.FocusListener;
 import org.eclipse.swt.events.KeyAdapter;
 import org.eclipse.swt.events.KeyEvent;
-import org.eclipse.swt.events.ModifyListener;
 import org.eclipse.swt.events.MouseAdapter;
 import org.eclipse.swt.events.MouseEvent;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.graphics.Cursor;
+import org.eclipse.swt.graphics.Rectangle;
 import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.swt.layout.FormAttachment;
 import org.eclipse.swt.layout.FormData;
@@ -69,7 +69,7 @@ public class DynamicSqlRowDialog extends BaseTransformDialog {
 
   private MetaSelectionLine<DatabaseMeta> wConnection;
 
-  private TextComposite wSql;
+  private TextComposite wSqlComposite;
 
   private Text wLimit;
 
@@ -100,10 +100,8 @@ public class DynamicSqlRowDialog extends 
BaseTransformDialog {
 
     buildButtonBar().ok(e -> ok()).cancel(e -> cancel()).build();
 
-    ModifyListener lsMod = e -> input.setChanged();
-    backupChanged = input.hasChanged();
-
     ScrolledComposite sc = new ScrolledComposite(shell, SWT.V_SCROLL | 
SWT.H_SCROLL);
+    PropsUi.setLook(sc);
     sc.setLayout(new FillLayout());
     FormData fdSc = new FormData();
     fdSc.left = new FormAttachment(0, margin);
@@ -113,6 +111,7 @@ public class DynamicSqlRowDialog extends 
BaseTransformDialog {
     sc.setLayoutData(fdSc);
 
     Composite wContent = new Composite(sc, SWT.NONE);
+    PropsUi.setLook(wContent);
     FormLayout formLayout = new FormLayout();
     formLayout.marginWidth = PropsUi.getFormMargin();
     formLayout.marginHeight = PropsUi.getFormMargin();
@@ -123,7 +122,6 @@ public class DynamicSqlRowDialog extends 
BaseTransformDialog {
     if (input.getDatabaseMeta() == null && pipelineMeta.nrDatabases() == 1) {
       wConnection.select(0);
     }
-    wConnection.addModifyListener(lsMod);
     wConnection.addListener(SWT.Selection, e -> getSqlReservedWords());
 
     // SQLFieldName field
@@ -266,7 +264,7 @@ public class DynamicSqlRowDialog extends 
BaseTransformDialog {
     fdlSql.top = new FormAttachment(wqueryOnlyOnChange, margin);
     wlSql.setLayoutData(fdlSql);
 
-    wSql =
+    wSqlComposite =
         EnvironmentUtils.getInstance().isWeb()
             ? new StyledTextComp(
                 variables,
@@ -276,32 +274,36 @@ public class DynamicSqlRowDialog extends 
BaseTransformDialog {
                 variables,
                 wContent,
                 SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | 
SWT.V_SCROLL);
-    wSql.addLineStyleListener(getSqlReservedWords());
-    PropsUi.setLook(wSql, Props.WIDGET_STYLE_FIXED);
+    wSqlComposite.addLineStyleListener(getSqlReservedWords());
+    PropsUi.setLook(wSqlComposite, Props.WIDGET_STYLE_FIXED);
     FormData fdSql = new FormData();
     fdSql.left = new FormAttachment(0, 0);
     fdSql.top = new FormAttachment(wlSql, margin);
     fdSql.right = new FormAttachment(100, 0);
-    wSql.setLayoutData(fdSql);
 
     wlPosition = new Label(wContent, SWT.NONE);
     PropsUi.setLook(wlPosition);
     FormData fdlPosition = new FormData();
     fdlPosition.left = new FormAttachment(0, 0);
     fdlPosition.right = new FormAttachment(100, 0);
-    fdlPosition.top = new FormAttachment(wSql, margin);
+    fdlPosition.bottom = new FormAttachment(100, 0);
     wlPosition.setLayoutData(fdlPosition);
 
     fdSql.bottom = new FormAttachment(wlPosition, -margin);
-    wSql.setLayoutData(fdSql);
+    fdSql.height = 200;
+    wSqlComposite.setLayoutData(fdSql);
 
-    sc.setContent(wContent);
     wContent.pack();
-    sc.setMinSize(wContent.computeSize(SWT.DEFAULT, SWT.DEFAULT));
+    Rectangle bounds = wContent.getBounds();
+    sc.setContent(wContent);
+    sc.setExpandHorizontal(true);
+    sc.setExpandVertical(true);
+    sc.setMinWidth(bounds.width);
+    sc.setMinHeight(bounds.height);
 
-    wSql.addModifyListener(arg0 -> setPosition());
+    wSqlComposite.addModifyListener(arg0 -> setPosition());
 
-    wSql.addKeyListener(
+    wSqlComposite.addKeyListener(
         new KeyAdapter() {
           @Override
           public void keyPressed(KeyEvent e) {
@@ -313,7 +315,7 @@ public class DynamicSqlRowDialog extends 
BaseTransformDialog {
             setPosition();
           }
         });
-    wSql.addFocusListener(
+    wSqlComposite.addFocusListener(
         new FocusAdapter() {
           @Override
           public void focusGained(FocusEvent e) {
@@ -325,7 +327,7 @@ public class DynamicSqlRowDialog extends 
BaseTransformDialog {
             setPosition();
           }
         });
-    wSql.addMouseListener(
+    wSqlComposite.addMouseListener(
         new MouseAdapter() {
           @Override
           public void mouseDoubleClick(MouseEvent e) {
@@ -343,9 +345,10 @@ public class DynamicSqlRowDialog extends 
BaseTransformDialog {
           }
         });
 
-    wSql.addModifyListener(lsMod);
+    wSqlComposite.addModifyListener(lsMod);
 
     getData();
+    input.setChanged(backupChanged);
     focusTransformName();
     BaseDialog.defaultShellHandling(shell, c -> ok(), c -> cancel());
 
@@ -353,23 +356,28 @@ public class DynamicSqlRowDialog extends 
BaseTransformDialog {
   }
 
   private List<String> getSqlReservedWords() {
+    String connection = wConnection != null ? wConnection.getText() : 
input.getConnection();
+
     // Do not search keywords when connection is empty
-    if (Utils.isEmpty(input.getConnection())) {
+    if (Utils.isEmpty(connection)) {
       return List.of();
     }
 
     // If connection is a variable that can't be resolved
-    if (variables.resolve(input.getConnection()).startsWith("${")) {
+    if (variables.resolve(connection).startsWith("${")) {
       return List.of();
     }
 
-    DatabaseMeta databaseMeta = 
pipelineMeta.findDatabase(input.getConnection(), variables);
+    DatabaseMeta databaseMeta = pipelineMeta.findDatabase(connection, 
variables);
+    if (databaseMeta == null) {
+      return List.of();
+    }
     return Arrays.stream(databaseMeta.getReservedWords()).toList();
   }
 
   public void setPosition() {
-    int lineNumber = wSql.getLineNumber();
-    int columnNumber = wSql.getColumnNumber();
+    int lineNumber = wSqlComposite.getLineNumber();
+    int columnNumber = wSqlComposite.getColumnNumber();
     wlPosition.setText(
         BaseMessages.getString(
             PKG, "DynamicSQLRowDialog.Position.Label", "" + lineNumber, "" + 
columnNumber));
@@ -381,7 +389,7 @@ public class DynamicSqlRowDialog extends 
BaseTransformDialog {
       logDebug(BaseMessages.getString(PKG, 
"DynamicSQLRowDialog.Log.GettingKeyInfo"));
     }
 
-    wSql.setText(Const.NVL(input.getSql(), ""));
+    wSqlComposite.setText(Const.NVL(input.getSql(), ""));
     wLimit.setText("" + input.getRowLimit());
     wOuter.setSelection(input.isOuterJoin());
     wuseVars.setSelection(input.isReplaceVariables());
@@ -409,7 +417,7 @@ public class DynamicSqlRowDialog extends 
BaseTransformDialog {
 
     input.setConnection(wConnection.getText());
     input.setRowLimit(Const.toInt(wLimit.getText(), 0));
-    input.setSql(wSql.getText());
+    input.setSql(wSqlComposite.getText());
     input.setSqlFieldName(wSqlFieldName.getText());
     input.setOuterJoin(wOuter.getSelection());
     input.setReplaceVariables(wuseVars.getSelection());
diff --git 
a/plugins/transforms/dynamicsqlrow/src/main/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowMeta.java
 
b/plugins/transforms/dynamicsqlrow/src/main/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowMeta.java
index bc864971c7..5ba75d185c 100644
--- 
a/plugins/transforms/dynamicsqlrow/src/main/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowMeta.java
+++ 
b/plugins/transforms/dynamicsqlrow/src/main/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowMeta.java
@@ -18,6 +18,8 @@
 package org.apache.hop.pipeline.transforms.dynamicsqlrow;
 
 import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
 import org.apache.hop.core.CheckResult;
 import org.apache.hop.core.Const;
 import org.apache.hop.core.ICheckResult;
@@ -41,6 +43,8 @@ import org.apache.hop.pipeline.PipelineMeta;
 import org.apache.hop.pipeline.transform.BaseTransformMeta;
 import org.apache.hop.pipeline.transform.TransformMeta;
 
+@Getter
+@Setter
 @Transform(
     id = "DynamicSqlRow",
     image = "dynamicsqlrow.svg",
@@ -111,114 +115,10 @@ public class DynamicSqlRowMeta extends 
BaseTransformMeta<DynamicSqlRow, DynamicS
     this.sqlFieldName = meta.sqlFieldName;
     this.replaceVariables = meta.replaceVariables;
     this.rowLimit = meta.rowLimit;
-    this.connection = meta.connection;
     this.outerJoin = meta.outerJoin;
     this.queryOnlyOnChange = meta.queryOnlyOnChange;
   }
 
-  public String getConnection() {
-    return connection;
-  }
-
-  public void setConnection(String connection) {
-    this.connection = connection;
-  }
-
-  /**
-   * @return Returns the database.
-   */
-  public DatabaseMeta getDatabaseMeta() {
-    return databaseMeta;
-  }
-
-  /**
-   * @param database The database to set.
-   */
-  public void setDatabaseMeta(DatabaseMeta database) {
-    this.databaseMeta = database;
-  }
-
-  /**
-   * @return Returns the outerJoin.
-   */
-  public boolean isOuterJoin() {
-    return outerJoin;
-  }
-
-  /**
-   * @param outerJoin The outerJoin to set.
-   */
-  public void setOuterJoin(boolean outerJoin) {
-    this.outerJoin = outerJoin;
-  }
-
-  /**
-   * @return Returns the replacevars.
-   */
-  public boolean isReplaceVariables() {
-    return replaceVariables;
-  }
-
-  public void setReplaceVariables(boolean replaceVariables) {
-    this.replaceVariables = replaceVariables;
-  }
-
-  /**
-   * @return Returns the queryonlyonchange.
-   */
-  public boolean isQueryOnlyOnChange() {
-    return queryOnlyOnChange;
-  }
-
-  /**
-   * @param queryonlyonchange The queryonlyonchange to set.
-   */
-  public void setQueryOnlyOnChange(boolean queryonlyonchange) {
-    this.queryOnlyOnChange = queryonlyonchange;
-  }
-
-  /**
-   * @return Returns the rowLimit.
-   */
-  public int getRowLimit() {
-    return rowLimit;
-  }
-
-  /**
-   * @param rowLimit The rowLimit to set.
-   */
-  public void setRowLimit(int rowLimit) {
-    this.rowLimit = rowLimit;
-  }
-
-  /**
-   * @return Returns the sql.
-   */
-  public String getSql() {
-    return sql;
-  }
-
-  /**
-   * @param sql The sql to set.
-   */
-  public void setSql(String sql) {
-    this.sql = sql;
-  }
-
-  /**
-   * @return Returns the sqlfieldname.
-   */
-  public String getSqlFieldName() {
-    return sqlFieldName;
-  }
-
-  /**
-   * @param sqlfieldname The sqlfieldname to set.
-   */
-  public void setSqlFieldName(String sqlfieldname) {
-    this.sqlFieldName = sqlfieldname;
-  }
-
   @Override
   public Object clone() {
     return new DynamicSqlRowMeta(this);
@@ -251,7 +151,8 @@ public class DynamicSqlRowMeta extends 
BaseTransformMeta<DynamicSqlRow, DynamicS
     }
 
     Database db = new Database(loggingObject, variables, databaseMeta);
-    databases = new Database[] {db}; // Keep track of this one for cancelQuery
+    // Keep track of this one for cancelQuery
+    databases = new Database[] {db};
 
     // First try without connecting to the database... (can be S L O W)
     // See if it's in the cache...
@@ -269,8 +170,8 @@ public class DynamicSqlRowMeta extends 
BaseTransformMeta<DynamicSqlRow, DynamicS
               + sql,
           dbe);
     }
-
-    if (add != null) { // Cache hit, just return it this...
+    // Cache hit, just return it this...
+    if (add != null) {
       for (int i = 0; i < add.size(); i++) {
         IValueMeta v = add.getValueMeta(i);
         v.setOrigin(name);
@@ -357,10 +258,9 @@ public class DynamicSqlRowMeta extends 
BaseTransformMeta<DynamicSqlRow, DynamicS
     }
 
     if (databaseMeta != null) {
-      Database db = new Database(loggingObject, variables, databaseMeta);
-      databases = new Database[] {db}; // Keep track of this one for 
cancelQuery
-
-      try {
+      try (Database db = new Database(loggingObject, variables, databaseMeta)) 
{
+        // Keep track of this one for cancelQuery
+        databases = new Database[] {db};
         db.connect();
         if (!Utils.isEmpty(sql)) {
 
@@ -387,8 +287,6 @@ public class DynamicSqlRowMeta extends 
BaseTransformMeta<DynamicSqlRow, DynamicS
                 + e.getMessage();
         cr = new CheckResult(ICheckResult.TYPE_RESULT_ERROR, errorMessage, 
transformMeta);
         remarks.add(cr);
-      } finally {
-        db.disconnect();
       }
     } else {
       errorMessage = BaseMessages.getString(PKG, 
"DynamicSQLRowMeta.CheckResult.InvalidConnection");
diff --git 
a/plugins/transforms/dynamicsqlrow/src/main/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowData.java
 
b/plugins/transforms/dynamicsqlrow/src/test/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowDataTest.java
similarity index 50%
copy from 
plugins/transforms/dynamicsqlrow/src/main/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowData.java
copy to 
plugins/transforms/dynamicsqlrow/src/test/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowDataTest.java
index 679651b0a7..d09295a87d 100644
--- 
a/plugins/transforms/dynamicsqlrow/src/main/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowData.java
+++ 
b/plugins/transforms/dynamicsqlrow/src/test/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowDataTest.java
@@ -17,39 +17,28 @@
 
 package org.apache.hop.pipeline.transforms.dynamicsqlrow;
 
-import java.util.ArrayList;
-import org.apache.hop.core.database.Database;
-import org.apache.hop.core.row.IRowMeta;
-import org.apache.hop.pipeline.transform.BaseTransformData;
-import org.apache.hop.pipeline.transform.ITransformData;
-
-@SuppressWarnings("java:S1104")
-public class DynamicSqlRowData extends BaseTransformData implements 
ITransformData {
-  IRowMeta outputRowMeta;
-  IRowMeta lookupRowMeta;
-
-  public Database db;
-
-  public Object[] notfound; // Values in case nothing is found...
-
-  public int indexOfSqlField;
-
-  public boolean skipPreviousRow;
-
-  public String previousSql;
-
-  public ArrayList<Object[]> previousrowbuffer;
-
-  public boolean isCanceled;
-
-  public DynamicSqlRowData() {
-    super();
-
-    db = null;
-    notfound = null;
-    indexOfSqlField = -1;
-    skipPreviousRow = false;
-    previousSql = null;
-    previousrowbuffer = new ArrayList<>();
+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.junit.jupiter.api.Test;
+
+/** Unit test for {@link DynamicSqlRowData} */
+class DynamicSqlRowDataTest {
+
+  @Test
+  void constructorInitializesDefaults() {
+    DynamicSqlRowData data = new DynamicSqlRowData();
+
+    assertNull(data.db);
+    assertNull(data.notfound);
+    assertEquals(-1, data.indexOfSqlField);
+    assertFalse(data.skipPreviousRow);
+    assertNull(data.previousSql);
+    assertNotNull(data.previousrowbuffer);
+    assertTrue(data.previousrowbuffer.isEmpty());
+    assertFalse(data.isCanceled);
   }
 }
diff --git 
a/plugins/transforms/dynamicsqlrow/src/test/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowMetaTest.java
 
b/plugins/transforms/dynamicsqlrow/src/test/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowMetaTest.java
index b20d9a74d9..589a599a5f 100644
--- 
a/plugins/transforms/dynamicsqlrow/src/test/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowMetaTest.java
+++ 
b/plugins/transforms/dynamicsqlrow/src/test/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowMetaTest.java
@@ -16,34 +16,63 @@
  */
 package org.apache.hop.pipeline.transforms.dynamicsqlrow;
 
+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.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.ICheckResult;
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.plugins.PluginRegistry;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaString;
 import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.apache.hop.metadata.api.IHopMetadataProvider;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.transform.TransformMeta;
 import org.apache.hop.pipeline.transforms.loadsave.LoadSaveTester;
 import 
org.apache.hop.pipeline.transforms.loadsave.validator.IFieldLoadSaveValidator;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
+/** Unit test for {@link DynamicSqlRowMeta} */
 class DynamicSqlRowMetaTest {
-  LoadSaveTester loadSaveTester;
+
+  LoadSaveTester<DynamicSqlRowMeta> loadSaveTester;
   Class<DynamicSqlRowMeta> testMetaClass = DynamicSqlRowMeta.class;
 
   @RegisterExtension
   static RestoreHopEngineEnvironmentExtension env = new 
RestoreHopEngineEnvironmentExtension();
 
-  @BeforeEach
-  void setUpLoadSave() throws Exception {
+  @BeforeAll
+  static void setUpBeforeClass() throws HopException {
     HopEnvironment.init();
     PluginRegistry.init();
+  }
+
+  @BeforeEach
+  void setUpLoadSave() throws HopException {
     List<String> attributes =
         Arrays.asList(
-            "sql", "sqlFieldName", "rowLimit", "outerJoin", 
"replaceVariables", "connection");
+            "sql",
+            "sqlFieldName",
+            "rowLimit",
+            "outerJoin",
+            "replaceVariables",
+            "queryOnlyOnChange",
+            "connection");
 
     Map<String, String> getterMap = new HashMap<>();
     Map<String, String> setterMap = new HashMap<>();
@@ -51,7 +80,7 @@ class DynamicSqlRowMetaTest {
     Map<String, IFieldLoadSaveValidator<?>> typeValidatorMap = new HashMap<>();
 
     loadSaveTester =
-        new LoadSaveTester(
+        new LoadSaveTester<>(
             testMetaClass, attributes, getterMap, setterMap, attrValidatorMap, 
typeValidatorMap);
   }
 
@@ -59,4 +88,215 @@ class DynamicSqlRowMetaTest {
   void testSerialization() throws HopException {
     loadSaveTester.testSerialization();
   }
+
+  @Test
+  void testSetDefault() {
+    // valid default value
+    DynamicSqlRowMeta meta = new DynamicSqlRowMeta();
+    meta.setDefault();
+
+    assertNull(meta.getConnection());
+    assertNull(meta.getDatabaseMeta());
+    assertEquals(0, meta.getRowLimit());
+    assertEquals("", meta.getSql());
+    assertFalse(meta.isOuterJoin());
+    assertFalse(meta.isReplaceVariables());
+    assertNull(meta.getSqlFieldName());
+    assertFalse(meta.isQueryOnlyOnChange());
+  }
+
+  @Test
+  void testGettersAndSetters() {
+    // valid property
+    DynamicSqlRowMeta meta = new DynamicSqlRowMeta();
+
+    meta.setConnection("conn");
+    assertEquals("conn", meta.getConnection());
+
+    meta.setSql("SELECT 1");
+    assertEquals("SELECT 1", meta.getSql());
+
+    meta.setSqlFieldName("sql_field");
+    assertEquals("sql_field", meta.getSqlFieldName());
+
+    meta.setRowLimit(10);
+    assertEquals(10, meta.getRowLimit());
+
+    meta.setOuterJoin(true);
+    assertTrue(meta.isOuterJoin());
+
+    meta.setReplaceVariables(true);
+    assertTrue(meta.isReplaceVariables());
+
+    meta.setQueryOnlyOnChange(true);
+    assertTrue(meta.isQueryOnlyOnChange());
+  }
+
+  @Test
+  void testClone() {
+    // valid clone
+    DynamicSqlRowMeta meta = new DynamicSqlRowMeta();
+    meta.setConnection("conn");
+    meta.setSql("SELECT id FROM t");
+    meta.setSqlFieldName("sql_field");
+    meta.setRowLimit(5);
+    meta.setOuterJoin(true);
+    meta.setReplaceVariables(true);
+    meta.setQueryOnlyOnChange(true);
+
+    DynamicSqlRowMeta cloned = (DynamicSqlRowMeta) meta.clone();
+
+    assertNotNull(cloned);
+    assertEquals(meta.getConnection(), cloned.getConnection());
+    assertEquals(meta.getSql(), cloned.getSql());
+    assertEquals(meta.getSqlFieldName(), cloned.getSqlFieldName());
+    assertEquals(meta.getRowLimit(), cloned.getRowLimit());
+    assertEquals(meta.isOuterJoin(), cloned.isOuterJoin());
+    assertEquals(meta.isReplaceVariables(), cloned.isReplaceVariables());
+    assertEquals(meta.isQueryOnlyOnChange(), cloned.isQueryOnlyOnChange());
+  }
+
+  @Test
+  void testCopyConstructor() {
+    DynamicSqlRowMeta meta = new DynamicSqlRowMeta();
+    meta.setConnection("conn");
+    meta.setSql("SELECT 1");
+    meta.setSqlFieldName("sql_field");
+    meta.setRowLimit(3);
+    meta.setOuterJoin(true);
+    meta.setReplaceVariables(false);
+    meta.setQueryOnlyOnChange(true);
+
+    DynamicSqlRowMeta copy = new DynamicSqlRowMeta(meta);
+
+    assertEquals(meta.getConnection(), copy.getConnection());
+    assertEquals(meta.getSql(), copy.getSql());
+    assertEquals(meta.getSqlFieldName(), copy.getSqlFieldName());
+    assertEquals(meta.getRowLimit(), copy.getRowLimit());
+    assertEquals(meta.isOuterJoin(), copy.isOuterJoin());
+    assertEquals(meta.isReplaceVariables(), copy.isReplaceVariables());
+    assertEquals(meta.isQueryOnlyOnChange(), copy.isQueryOnlyOnChange());
+  }
+
+  @Test
+  void supportsErrorHandlingReturnsTrue() {
+    assertTrue(new DynamicSqlRowMeta().supportsErrorHandling());
+  }
+
+  @Test
+  void checkWithoutInputReportsError() {
+    // valid check
+    DynamicSqlRowMeta meta = new DynamicSqlRowMeta();
+    List<ICheckResult> remarks = new ArrayList<>();
+
+    meta.check(
+        remarks,
+        mock(PipelineMeta.class),
+        mock(TransformMeta.class),
+        new RowMeta(),
+        new String[0],
+        new String[0],
+        mock(IRowMeta.class),
+        null,
+        mock(IHopMetadataProvider.class));
+
+    assertTrue(
+        remarks.stream().anyMatch(r -> r.getType() == 
ICheckResult.TYPE_RESULT_ERROR),
+        "Expected error when no input transforms are connected");
+  }
+
+  @Test
+  void checkWithInputAndMissingSqlFieldNameReportsError() {
+    DynamicSqlRowMeta meta = new DynamicSqlRowMeta();
+    List<ICheckResult> remarks = new ArrayList<>();
+
+    meta.check(
+        remarks,
+        mock(PipelineMeta.class),
+        mock(TransformMeta.class),
+        new RowMeta(),
+        new String[] {"in"},
+        new String[0],
+        mock(IRowMeta.class),
+        null,
+        mock(IHopMetadataProvider.class));
+
+    assertTrue(
+        remarks.stream().anyMatch(r -> r.getType() == 
ICheckResult.TYPE_RESULT_ERROR),
+        "Expected error when SQL field name is missing");
+  }
+
+  @Test
+  void checkWithUnknownSqlFieldReportsError() {
+    DynamicSqlRowMeta meta = new DynamicSqlRowMeta();
+    meta.setSqlFieldName("sql");
+    List<ICheckResult> remarks = new ArrayList<>();
+
+    meta.check(
+        remarks,
+        mock(PipelineMeta.class),
+        mock(TransformMeta.class),
+        new RowMeta(),
+        new String[] {"in"},
+        new String[0],
+        mock(IRowMeta.class),
+        null,
+        mock(IHopMetadataProvider.class));
+
+    assertTrue(
+        remarks.stream().anyMatch(r -> r.getType() == 
ICheckResult.TYPE_RESULT_ERROR),
+        "Expected error when SQL field is not present in previous transform 
output");
+  }
+
+  @Test
+  void checkWithValidSqlFieldReportsOk() {
+    DynamicSqlRowMeta meta = new DynamicSqlRowMeta();
+    meta.setSqlFieldName("sql");
+
+    RowMeta prev = new RowMeta();
+    IValueMeta sqlField = new ValueMetaString("sql");
+    sqlField.setOrigin("input");
+    prev.addValueMeta(sqlField);
+
+    List<ICheckResult> remarks = new ArrayList<>();
+    meta.check(
+        remarks,
+        mock(PipelineMeta.class),
+        mock(TransformMeta.class),
+        prev,
+        new String[] {"in"},
+        new String[0],
+        mock(IRowMeta.class),
+        null,
+        mock(IHopMetadataProvider.class));
+
+    assertTrue(
+        remarks.stream().anyMatch(r -> r.getType() == 
ICheckResult.TYPE_RESULT_OK),
+        "Expected OK result when SQL field exists in previous transform 
output");
+  }
+
+  @Test
+  void checkWithoutDatabaseMetaReportsInvalidConnection() {
+    DynamicSqlRowMeta meta = new DynamicSqlRowMeta();
+    meta.setSqlFieldName("sql_field");
+
+    RowMeta prev = new RowMeta();
+    prev.addValueMeta(new ValueMetaString("sql_field"));
+
+    List<ICheckResult> remarks = new ArrayList<>();
+    meta.check(
+        remarks,
+        mock(PipelineMeta.class),
+        mock(TransformMeta.class),
+        prev,
+        new String[] {"in"},
+        new String[0],
+        mock(IRowMeta.class),
+        null,
+        mock(IHopMetadataProvider.class));
+
+    assertTrue(
+        remarks.stream().anyMatch(r -> r.getType() == 
ICheckResult.TYPE_RESULT_ERROR),
+        "Expected error when database connection metadata is missing");
+  }
 }
diff --git 
a/plugins/transforms/dynamicsqlrow/src/test/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowTest.java
 
b/plugins/transforms/dynamicsqlrow/src/test/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowTest.java
new file mode 100644
index 0000000000..3ea28423d5
--- /dev/null
+++ 
b/plugins/transforms/dynamicsqlrow/src/test/java/org/apache/hop/pipeline/transforms/dynamicsqlrow/DynamicSqlRowTest.java
@@ -0,0 +1,211 @@
+/*
+ * 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.dynamicsqlrow;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
+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 org.apache.hop.core.IRowSet;
+import org.apache.hop.core.QueueRowSet;
+import org.apache.hop.core.database.Database;
+import org.apache.hop.core.exception.HopDatabaseException;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.ILoggingObject;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.apache.hop.pipeline.transforms.mock.TransformMockHelper;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/** Unit test for {@link DynamicSqlRow}. */
+@ExtendWith(RestoreHopEngineEnvironmentExtension.class)
+class DynamicSqlRowTest {
+
+  private TransformMockHelper<DynamicSqlRowMeta, DynamicSqlRowData> 
transformMockHelper;
+  private DynamicSqlRowMeta meta;
+  private DynamicSqlRowData data;
+  private DynamicSqlRow transform;
+
+  @BeforeEach
+  void setUp() {
+    transformMockHelper =
+        new TransformMockHelper<>(
+            "DYNAMIC_SQL_ROW", DynamicSqlRowMeta.class, 
DynamicSqlRowData.class);
+    when(transformMockHelper.logChannelFactory.create(any(), 
any(ILoggingObject.class)))
+        .thenReturn(transformMockHelper.iLogChannel);
+    when(transformMockHelper.pipeline.isRunning()).thenReturn(true);
+
+    meta = new DynamicSqlRowMeta();
+    data = new DynamicSqlRowData();
+    transform =
+        new DynamicSqlRow(
+            transformMockHelper.transformMeta,
+            meta,
+            data,
+            0,
+            transformMockHelper.pipelineMeta,
+            transformMockHelper.pipeline);
+  }
+
+  @AfterEach
+  void tearDown() {
+    transformMockHelper.cleanUp();
+  }
+
+  private static IRowSet inputRowSet(Object[] row, IRowMeta rowMeta) {
+    QueueRowSet rowSet = new QueueRowSet();
+    rowSet.putRow(rowMeta, row);
+    rowSet.setDone();
+    return rowSet;
+  }
+
+  private static RowMeta rowMetaWithField(String fieldName) {
+    RowMeta rowMeta = new RowMeta();
+    rowMeta.addValueMeta(new ValueMetaString(fieldName));
+    return rowMeta;
+  }
+
+  @Test
+  void initReturnsFalseWhenDatabaseMetaIsMissing() {
+    meta.setConnection("missing-connection");
+    when(transformMockHelper.pipelineMeta.findDatabase(anyString(), 
any())).thenReturn(null);
+
+    assertFalse(transform.init());
+  }
+
+  @Test
+  void initReturnsFalseWhenConnectionIsEmpty() {
+    meta.setConnection(null);
+
+    assertFalse(transform.init());
+  }
+
+  @Test
+  void disposeDisconnectsDatabase() {
+    data.db = mock(Database.class);
+    transform.dispose();
+
+    verify(data.db).disconnect();
+  }
+
+  @Test
+  void stopRunningDoesNothingWhenAlreadyCanceled() throws HopException {
+    data.db = mock(Database.class);
+    data.isCanceled = true;
+
+    transform.stopRunning();
+
+    verify(data.db, never()).cancelQuery();
+  }
+
+  @Test
+  void stopRunningCancelsQueryWhenDatabaseIsActive() throws HopException {
+    data.db = mock(Database.class);
+    data.isCanceled = false;
+
+    transform.stopRunning();
+
+    verify(data.db).cancelQuery();
+    assertTrue(data.isCanceled);
+    assertTrue(transform.isStopped());
+  }
+
+  @Test
+  void stopRunningDoesNothingWhenDatabaseIsNull() throws HopException {
+    data.db = null;
+    transform.stopRunning();
+
+    assertFalse(data.isCanceled);
+  }
+
+  @Test
+  void processRowReturnsFalseWhenNoMoreInput() throws HopException {
+    QueueRowSet rowSet = new QueueRowSet();
+    rowSet.setDone();
+    transform.addRowSetToInputRowSets(rowSet);
+
+    assertFalse(transform.processRow());
+  }
+
+  @Test
+  void processRowFailsWhenSqlFieldNameIsEmpty() {
+    meta.setSql("SELECT 1");
+    meta.setSqlFieldName(null);
+    transform.addRowSetToInputRowSets(
+        inputRowSet(new Object[] {"SELECT 1"}, rowMetaWithField("sql_field")));
+
+    assertThrows(HopException.class, () -> transform.processRow());
+  }
+
+  @Test
+  void processRowFailsWhenTemplateSqlIsEmpty() {
+    meta.setSql("");
+    meta.setSqlFieldName("sql_field");
+    transform.addRowSetToInputRowSets(
+        inputRowSet(new Object[] {"SELECT 1"}, rowMetaWithField("sql_field")));
+
+    assertThrows(HopException.class, () -> transform.processRow());
+  }
+
+  @Test
+  void processRowFailsWhenSqlFieldIsNotFound() {
+    meta.setSql("SELECT 1");
+    meta.setSqlFieldName("sql_field");
+    // Row meta does not contain "sql_field", so indexOfValue returns -1.
+    transform.addRowSetToInputRowSets(
+        inputRowSet(new Object[] {"SELECT 1"}, 
rowMetaWithField("other_field")));
+
+    HopException exception = assertThrows(HopException.class, () -> 
transform.processRow());
+    assertTrue(exception.getMessage().contains("sql_field"));
+  }
+
+  @Test
+  void processRowUsesErrorHandlingWhenConfigured() throws HopException {
+    meta.setSql("SELECT 1");
+    meta.setSqlFieldName("sql_field");
+    
when(transformMockHelper.transformMeta.isDoingErrorHandling()).thenReturn(true);
+
+    data.db = mock(Database.class);
+    when(data.db.openQuery(anyString())).thenThrow(new 
HopDatabaseException("Query failed"));
+
+    DynamicSqlRow spyTransform = spy(transform);
+    doNothing()
+        .when(spyTransform)
+        .putError(any(), any(), anyLong(), anyString(), any(), anyString());
+
+    spyTransform.addRowSetToInputRowSets(
+        inputRowSet(new Object[] {"SELECT 1"}, rowMetaWithField("sql_field")));
+
+    assertTrue(spyTransform.processRow());
+    assertFalse(spyTransform.isStopped());
+  }
+}


Reply via email to