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 968c30c038 XML cleanup TeraFast transform, fixes #2901 (#6769)
968c30c038 is described below

commit 968c30c0385c3e2b19c6bab9f0f507eeeecc2c12
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Thu Mar 12 08:22:08 2026 +0100

    XML cleanup TeraFast transform, fixes #2901 (#6769)
---
 .../apache/hop/core/util/AbstractTransform.java    |  68 -----
 .../hop/core/util/AbstractTransformMeta.java       | 140 ----------
 .../hop/pipeline/transform/TransformMetaTest.java  |   3 +-
 .../terafast/FastloadControlBuilder.java           |  15 +-
 .../hop/pipeline/transforms/terafast/TeraFast.java |  64 +++--
 .../pipeline/transforms/terafast/TeraFastData.java |  29 ++
 .../transforms/terafast/TeraFastDialog.java        |  89 +++----
 .../pipeline/transforms/terafast/TeraFastMeta.java | 291 ++++-----------------
 .../terafast/FastloadControlBuilderTest.java       | 287 ++++++++++++++++++++
 .../transforms/terafast/TeraFastMetaTest.java      |  93 +++++++
 .../pipeline/transforms/terafast/TeraFastTest.java | 107 ++++++++
 .../src/test/resources/terafast-transform.xml      |  49 ++++
 12 files changed, 692 insertions(+), 543 deletions(-)

diff --git 
a/engine/src/main/java/org/apache/hop/core/util/AbstractTransform.java 
b/engine/src/main/java/org/apache/hop/core/util/AbstractTransform.java
deleted file mode 100644
index a421155dd9..0000000000
--- a/engine/src/main/java/org/apache/hop/core/util/AbstractTransform.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.core.util;
-
-import org.apache.hop.pipeline.Pipeline;
-import org.apache.hop.pipeline.PipelineMeta;
-import org.apache.hop.pipeline.transform.BaseTransform;
-import org.apache.hop.pipeline.transform.ITransformData;
-import org.apache.hop.pipeline.transform.ITransformMeta;
-import org.apache.hop.pipeline.transform.TransformMeta;
-
-public abstract class AbstractTransform<Meta extends ITransformMeta, Data 
extends ITransformData>
-    extends BaseTransform<Meta, Data> {
-
-  /** Constant for unexpected error. */
-  public static final String UNEXPECTED_ERROR = "Unexpected error";
-
-  public static final long DEFAULT_ERROR_CODE = 1L;
-
-  /**
-   * Constructor.
-   *
-   * @param transformMeta the transformMeta.
-   * @param meta the transform specific metadata
-   * @param data the transform data.
-   * @param copyNr the copyNr.
-   * @param pipelineMeta the pipelineMeta.
-   * @param pipeline the transaction.
-   */
-  public AbstractTransform(
-      final TransformMeta transformMeta,
-      final Meta meta,
-      final Data data,
-      final int copyNr,
-      final PipelineMeta pipelineMeta,
-      final Pipeline pipeline) {
-    super(transformMeta, meta, data, copyNr, pipelineMeta, pipeline);
-  }
-
-  /**
-   * Log exception.
-   *
-   * @param exception exception to log.
-   */
-  public void logUnexpectedError(final Throwable exception) {
-    this.logError(UNEXPECTED_ERROR, exception);
-  }
-
-  /** Set default error code. */
-  public void setDefaultError() {
-    this.setErrors(DEFAULT_ERROR_CODE);
-  }
-}
diff --git 
a/engine/src/main/java/org/apache/hop/core/util/AbstractTransformMeta.java 
b/engine/src/main/java/org/apache/hop/core/util/AbstractTransformMeta.java
deleted file mode 100644
index 8bb2665fb7..0000000000
--- a/engine/src/main/java/org/apache/hop/core/util/AbstractTransformMeta.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * 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.core.util;
-
-import java.util.List;
-import java.util.prefs.BackingStoreException;
-import java.util.prefs.Preferences;
-import org.apache.commons.lang.StringUtils;
-import org.apache.hop.core.database.DatabaseMeta;
-import org.apache.hop.core.exception.HopException;
-import org.apache.hop.core.exception.HopXmlException;
-import org.apache.hop.core.util.PluginPropertyHandler.LoadXml;
-import org.apache.hop.core.util.PluginPropertyHandler.ReadFromPreferences;
-import org.apache.hop.core.util.PluginPropertyHandler.SaveToPreferences;
-import org.apache.hop.metadata.api.IHopMetadataProvider;
-import org.apache.hop.pipeline.transform.BaseTransformMeta;
-import org.apache.hop.pipeline.transform.ITransform;
-import org.apache.hop.pipeline.transform.ITransformData;
-import org.w3c.dom.Node;
-
-public abstract class AbstractTransformMeta<Main extends ITransform, Data 
extends ITransformData>
-    extends BaseTransformMeta<Main, Data> {
-
-  private static final String CONNECTION_NAME = "connection";
-
-  private final PluginPropertyFactory propertyFactory =
-      new PluginPropertyFactory(new KeyValueSet());
-
-  private DatabaseMeta dbMeta;
-
-  private StringPluginProperty connectionName;
-
-  /** Default constructor. */
-  public AbstractTransformMeta() {
-    super();
-    this.connectionName = this.propertyFactory.createString(CONNECTION_NAME);
-  }
-
-  /**
-   * @return the propertyFactory
-   */
-  public PluginPropertyFactory getPropertyFactory() {
-    return this.propertyFactory;
-  }
-
-  /**
-   * @return the properties
-   */
-  public KeyValueSet getProperties() {
-    return this.propertyFactory.getProperties();
-  }
-
-  /**
-   * Saves properties to preferences.
-   *
-   * @throws BackingStoreException ...
-   */
-  public void saveAsPreferences() throws BackingStoreException {
-    final Preferences node = Preferences.userNodeForPackage(this.getClass());
-    this.getProperties().walk(new SaveToPreferences(node));
-    node.flush();
-  }
-
-  /** Read properties from preferences. */
-  public void readFromPreferences() {
-    final Preferences node = Preferences.userNodeForPackage(this.getClass());
-    this.getProperties().walk(new ReadFromPreferences(node));
-  }
-
-  public void loadXml(
-      final Node node,
-      final List<DatabaseMeta> databaseMeta,
-      final IHopMetadataProvider metadataProvider)
-      throws HopXmlException {
-    this.getProperties().walk(new LoadXml(node));
-    initDbMeta(databaseMeta);
-  }
-
-  /**
-   * @param databaseList A list of available DatabaseMeta in this pipeline.
-   */
-  private void initDbMeta(final List<DatabaseMeta> databaseList) {
-    if (!StringUtils.isEmpty(this.connectionName.getValue())) {
-      this.dbMeta = DatabaseMeta.findDatabase(databaseList, 
this.connectionName.getValue());
-    }
-  }
-
-  /**
-   * {@inheritDoc}
-   *
-   * @see BaseTransformMeta#getXml()
-   */
-  @Override
-  public String getXml() throws HopException {
-    return PluginPropertyHandler.toXml(this.getProperties());
-  }
-
-  /**
-   * @return the connectionName
-   */
-  public StringPluginProperty getConnectionName() {
-    return this.connectionName;
-  }
-
-  /**
-   * @param connectionName the connectionName to set
-   */
-  public void setConnectionName(final StringPluginProperty connectionName) {
-    this.connectionName = connectionName;
-  }
-
-  /**
-   * @return the dbMeta
-   */
-  public DatabaseMeta getDbMeta() {
-    return this.dbMeta;
-  }
-
-  /**
-   * @param dbMeta the dbMeta to set
-   */
-  public void setDbMeta(final DatabaseMeta dbMeta) {
-    this.dbMeta = dbMeta;
-  }
-}
diff --git 
a/engine/src/test/java/org/apache/hop/pipeline/transform/TransformMetaTest.java 
b/engine/src/test/java/org/apache/hop/pipeline/transform/TransformMetaTest.java
index 1de7116987..89c49e100a 100644
--- 
a/engine/src/test/java/org/apache/hop/pipeline/transform/TransformMetaTest.java
+++ 
b/engine/src/test/java/org/apache/hop/pipeline/transform/TransformMetaTest.java
@@ -26,7 +26,6 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Random;
 import org.apache.commons.lang.builder.EqualsBuilder;
-import org.apache.hop.core.util.AbstractTransformMeta;
 import org.apache.hop.core.variables.IVariables;
 import org.apache.hop.core.variables.Variables;
 import org.apache.hop.partition.PartitionSchema;
@@ -79,7 +78,7 @@ class TransformMetaTest {
   }
 
   private static TransformMeta createTestMeta() throws Exception {
-    ITransformMeta transformMetaInterface = mock(AbstractTransformMeta.class);
+    ITransformMeta transformMetaInterface = mock(ITransformMeta.class);
     when(transformMetaInterface.clone()).thenReturn(transformMetaInterface);
 
     TransformMeta meta = new TransformMeta(TRANSFORM_ID, "transformName", 
transformMetaInterface);
diff --git 
a/plugins/transforms/terafast/src/main/java/org/apache/hop/pipeline/transforms/terafast/FastloadControlBuilder.java
 
b/plugins/transforms/terafast/src/main/java/org/apache/hop/pipeline/transforms/terafast/FastloadControlBuilder.java
index b7039e394e..c7f736126b 100644
--- 
a/plugins/transforms/terafast/src/main/java/org/apache/hop/pipeline/transforms/terafast/FastloadControlBuilder.java
+++ 
b/plugins/transforms/terafast/src/main/java/org/apache/hop/pipeline/transforms/terafast/FastloadControlBuilder.java
@@ -17,12 +17,12 @@
 
 package org.apache.hop.pipeline.transforms.terafast;
 
+import java.util.List;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang.SystemUtils;
 import org.apache.hop.core.row.IRowMeta;
 import org.apache.hop.core.row.IValueMeta;
 import org.apache.hop.core.util.Assert;
-import org.apache.hop.core.util.StringListPluginProperty;
 import org.apache.hop.core.util.Utils;
 
 public class FastloadControlBuilder {
@@ -131,16 +131,14 @@ public class FastloadControlBuilder {
    * @return this
    */
   public FastloadControlBuilder define(
-      final IRowMeta targetTableFields,
-      StringListPluginProperty tableFieldList,
-      final String dataFile) {
+      final IRowMeta targetTableFields, List<String> tableFieldList, final 
String dataFile) {
     Assert.assertNotNull(targetTableFields, "fields cannot be null");
     Assert.assertNotNull(dataFile, "dataFile cannot be null");
 
     this.builder.append("DEFINE ");
     for (int i = 0; i < targetTableFields.size(); i++) {
       IValueMeta value = targetTableFields.getValueMeta(i);
-      int tableIndex = tableFieldList.getValue().indexOf(value.getName());
+      int tableIndex = tableFieldList.indexOf(value.getName());
       if (tableIndex >= 0) {
         this.builder.append(value.getName());
         // all fields of type VARCHAR. converted by fastload if necessary
@@ -172,16 +170,13 @@ public class FastloadControlBuilder {
    * @return ...
    */
   public FastloadControlBuilder insert(
-      final IRowMeta targetTableFields,
-      StringListPluginProperty tableFieldList,
-      final String tableName) {
+      final IRowMeta targetTableFields, List<String> tableFieldList, final 
String tableName) {
     Assert.assertNotNull(targetTableFields, "targetTableFields cannot be 
null.");
     Assert.assertNotNull(tableName, "TableName cannot be null.");
 
     this.builder.append("INSERT INTO " + tableName + "(");
     for (int i = 0; i < targetTableFields.size(); i++) {
-      int tableIndex =
-          
tableFieldList.getValue().indexOf(targetTableFields.getValueMeta(i).getName());
+      int tableIndex = 
tableFieldList.indexOf(targetTableFields.getValueMeta(i).getName());
       if (tableIndex >= 0) {
         this.builder.append(":" + targetTableFields.getValueMeta(i).getName());
         if (targetTableFields.getValueMeta(i).getType() == 
IValueMeta.TYPE_DATE) {
diff --git 
a/plugins/transforms/terafast/src/main/java/org/apache/hop/pipeline/transforms/terafast/TeraFast.java
 
b/plugins/transforms/terafast/src/main/java/org/apache/hop/pipeline/transforms/terafast/TeraFast.java
index 808e9f4f1f..e2bda8ca42 100644
--- 
a/plugins/transforms/terafast/src/main/java/org/apache/hop/pipeline/transforms/terafast/TeraFast.java
+++ 
b/plugins/transforms/terafast/src/main/java/org/apache/hop/pipeline/transforms/terafast/TeraFast.java
@@ -33,20 +33,20 @@ import org.apache.commons.lang.StringUtils;
 import org.apache.commons.vfs2.FileObject;
 import org.apache.hop.core.Const;
 import org.apache.hop.core.database.Database;
+import org.apache.hop.core.database.DatabaseMeta;
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.logging.LogLevel;
 import org.apache.hop.core.row.IRowMeta;
 import org.apache.hop.core.row.IValueMeta;
-import org.apache.hop.core.util.AbstractTransform;
 import org.apache.hop.core.util.ConfigurableStreamLogger;
-import org.apache.hop.core.util.GenericTransformData;
 import org.apache.hop.core.vfs.HopVfs;
 import org.apache.hop.i18n.BaseMessages;
 import org.apache.hop.pipeline.Pipeline;
 import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.transform.BaseTransform;
 import org.apache.hop.pipeline.transform.TransformMeta;
 
-public class TeraFast extends AbstractTransform<TeraFastMeta, 
GenericTransformData> {
+public class TeraFast extends BaseTransform<TeraFastMeta, TeraFastData> {
 
   private static final Class<?> PKG = TeraFastMeta.class;
 
@@ -63,7 +63,7 @@ public class TeraFast extends AbstractTransform<TeraFastMeta, 
GenericTransformDa
   public TeraFast(
       final TransformMeta transformMeta,
       final TeraFastMeta meta,
-      final GenericTransformData data,
+      final TeraFastData data,
       final int copyNr,
       final PipelineMeta pipelineMeta,
       final Pipeline pipeline) {
@@ -77,23 +77,22 @@ public class TeraFast extends 
AbstractTransform<TeraFastMeta, GenericTransformDa
    * @throws HopException Upon any exception
    */
   public String createCommandLine() throws HopException {
-    if (StringUtils.isBlank(this.meta.getFastloadPath().getValue())) {
+    if (StringUtils.isBlank(this.meta.getFastloadPath())) {
       throw new HopException("Fastload path not set");
     }
     final StringBuilder builder = new StringBuilder();
     try {
       final FileObject fileObject =
-          
HopVfs.getFileObject(resolve(this.meta.getFastloadPath().getValue()), 
variables);
+          HopVfs.getFileObject(resolve(this.meta.getFastloadPath()), 
variables);
       final String fastloadExec = HopVfs.getFilename(fileObject);
       builder.append(fastloadExec);
     } catch (Exception e) {
       throw new HopException("Error retrieving fastload application string", 
e);
     }
     // Add log error log, if set.
-    if (StringUtils.isNotBlank(this.meta.getLogFile().getValue())) {
+    if (StringUtils.isNotBlank(this.meta.getLogFile())) {
       try {
-        FileObject fileObject =
-            HopVfs.getFileObject(resolve(this.meta.getLogFile().getValue()), 
variables);
+        FileObject fileObject = 
HopVfs.getFileObject(resolve(this.meta.getLogFile()), variables);
         builder.append(" -e ");
         builder.append("\"" + HopVfs.getFilename(fileObject) + "\"");
       } catch (Exception e) {
@@ -105,7 +104,8 @@ public class TeraFast extends 
AbstractTransform<TeraFastMeta, GenericTransformDa
 
   protected void verifyDatabaseConnection() throws HopException {
     // Confirming Database Connection is defined.
-    if (this.meta.getDbMeta() == null) {
+    DatabaseMeta databaseMeta = 
getPipelineMeta().findDatabase(meta.getConnectionName(), variables);
+    if (databaseMeta == null) {
       throw new HopException(
           BaseMessages.getString(PKG, 
"TeraFastDialog.GetSQL.NoConnectionDefined"));
     }
@@ -148,13 +148,13 @@ public class TeraFast extends 
AbstractTransform<TeraFastMeta, GenericTransformDa
         if (this.process != null) {
           final int exitVal = this.process.waitFor();
           if (exitVal != 0) {
-            setErrors(DEFAULT_ERROR_CODE);
+            setErrors(1);
           }
           logBasic(BaseMessages.getString(PKG, 
"TeraFast.Log.ExitValueFastloadPath", "" + exitVal));
         }
       } catch (Exception e) {
         logError(BaseMessages.getString(PKG, "TeraFast.Log.ErrorInTransform"), 
e);
-        this.setDefaultError();
+        this.setErrors(1);
         stopAll();
       }
 
@@ -164,7 +164,7 @@ public class TeraFast extends 
AbstractTransform<TeraFastMeta, GenericTransformDa
     if (this.first) {
       this.first = false;
       try {
-        final File tempDataFile = new 
File(resolveFileName(this.meta.getDataFile().getValue()));
+        final File tempDataFile = new 
File(resolveFileName(this.meta.getDataFile()));
         this.dataFile = FileUtils.openOutputStream(tempDataFile);
         this.dataFilePrintStream = new PrintStream(dataFile);
       } catch (IOException e) {
@@ -181,9 +181,9 @@ public class TeraFast extends 
AbstractTransform<TeraFastMeta, GenericTransformDa
       List<Integer> columnSortOrder = new ArrayList<>(tableRowMeta.size());
       for (int i = 0; i < tableRowMeta.size(); i++) {
         IValueMeta column = tableRowMeta.getValueMeta(i);
-        int tableIndex = 
this.meta.getTableFieldList().getValue().indexOf(column.getName());
+        int tableIndex = 
this.meta.getTableFieldList().indexOf(column.getName());
         if (tableIndex >= 0) {
-          String streamField = 
this.meta.getStreamFieldList().getValue().get(tableIndex);
+          String streamField = this.meta.getStreamFieldList().get(tableIndex);
           columnSortOrder.add(streamRowMeta.indexOfValue(streamField));
         }
       }
@@ -272,16 +272,18 @@ public class TeraFast extends 
AbstractTransform<TeraFastMeta, GenericTransformDa
    * @throws HopException ...
    */
   public void execute() throws HopException {
-    if (this.meta.getTruncateTable().getValue()) {
-      Database db = new Database(this, this, this.meta.getDbMeta());
+    if (this.meta.isTruncateTable()) {
+      DatabaseMeta databaseMeta =
+          getPipelineMeta().findDatabase(meta.getConnectionName(), variables);
+      Database db = new Database(this, this, databaseMeta);
       db.connect();
-      db.truncateTable(this.meta.getTargetTable().getValue());
+      db.truncateTable(this.meta.getTargetTable());
       db.commit();
       db.disconnect();
     }
     startFastLoad();
 
-    if (this.meta.getUseControlFile().getValue()) {
+    if (this.meta.isUseControlFile()) {
       this.invokeLoadingControlFile();
     } else {
       this.invokeLoadingCommand();
@@ -322,7 +324,7 @@ public class TeraFast extends 
AbstractTransform<TeraFastMeta, GenericTransformDa
     final InputStream control;
     final String controlContent;
     try {
-      controlFile = new 
File(resolveFileName(this.meta.getControlFile().getValue()));
+      controlFile = new File(resolveFileName(this.meta.getControlFile()));
       control = FileUtils.openInputStream(controlFile);
       controlContent = resolve(FileUtils.readFileToString(controlFile));
     } catch (IOException e) {
@@ -347,29 +349,25 @@ public class TeraFast extends 
AbstractTransform<TeraFastMeta, GenericTransformDa
    */
   private void invokeLoadingCommand() throws HopException {
     final FastloadControlBuilder builder = new FastloadControlBuilder();
-    builder.setSessions(this.meta.getSessions().getValue());
-    builder.setErrorLimit(this.meta.getErrorLimit().getValue());
+    DatabaseMeta databaseMeta = 
getPipelineMeta().findDatabase(meta.getConnectionName(), variables);
+    builder.setSessions(this.meta.getSessions());
+    builder.setErrorLimit(this.meta.getErrorLimit());
     builder.logon(
-        this.meta.getDbMeta().getHostname(),
-        this.meta.getDbMeta().getUsername(),
-        this.meta.getDbMeta().getPassword());
+        databaseMeta.getHostname(), databaseMeta.getUsername(), 
databaseMeta.getPassword());
     builder.setRecordFormat(FastloadControlBuilder.RECORD_VARTEXT);
     try {
       builder.define(
           this.meta.getRequiredFields(this),
           meta.getTableFieldList(),
-          resolveFileName(this.meta.getDataFile().getValue()));
+          resolveFileName(this.meta.getDataFile()));
     } catch (Exception ex) {
       throw new HopException("Error defining data file!", ex);
     }
     builder.show();
-    builder.beginLoading(
-        this.meta.getDbMeta().getPreferredSchemaName(), 
this.meta.getTargetTable().getValue());
+    builder.beginLoading(databaseMeta.getPreferredSchemaName(), 
this.meta.getTargetTable());
 
     builder.insert(
-        this.meta.getRequiredFields(this),
-        meta.getTableFieldList(),
-        this.meta.getTargetTable().getValue());
+        this.meta.getRequiredFields(this), meta.getTableFieldList(), 
this.meta.getTargetTable());
     builder.endLoading();
     builder.logoff();
     final String control = builder.toString();
@@ -402,11 +400,11 @@ public class TeraFast extends 
AbstractTransform<TeraFastMeta, GenericTransformDa
         logDetailed("Exit value for the fastload process was : " + exitValue);
         if (exitValue != 0) {
           logError("Exit value for the fastload process was : " + exitValue);
-          setErrors(DEFAULT_ERROR_CODE);
+          setErrors(1);
         }
       }
     } catch (InterruptedException e) {
-      setErrors(DEFAULT_ERROR_CODE);
+      setErrors(1);
       logError("Unexpected error encountered while finishing the fastload 
process", e);
     }
 
diff --git 
a/plugins/transforms/terafast/src/main/java/org/apache/hop/pipeline/transforms/terafast/TeraFastData.java
 
b/plugins/transforms/terafast/src/main/java/org/apache/hop/pipeline/transforms/terafast/TeraFastData.java
new file mode 100644
index 0000000000..8e54ea75ef
--- /dev/null
+++ 
b/plugins/transforms/terafast/src/main/java/org/apache/hop/pipeline/transforms/terafast/TeraFastData.java
@@ -0,0 +1,29 @@
+/*
+ * 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.terafast;
+
+import org.apache.hop.pipeline.transform.BaseTransformData;
+import org.apache.hop.pipeline.transform.ITransformData;
+
+@SuppressWarnings("java:S1104")
+public class TeraFastData extends BaseTransformData implements ITransformData {
+
+  public TeraFastData() {
+    super();
+  }
+}
diff --git 
a/plugins/transforms/terafast/src/main/java/org/apache/hop/pipeline/transforms/terafast/TeraFastDialog.java
 
b/plugins/transforms/terafast/src/main/java/org/apache/hop/pipeline/transforms/terafast/TeraFastDialog.java
index b18645c34a..afb8f3d673 100644
--- 
a/plugins/transforms/terafast/src/main/java/org/apache/hop/pipeline/transforms/terafast/TeraFastDialog.java
+++ 
b/plugins/transforms/terafast/src/main/java/org/apache/hop/pipeline/transforms/terafast/TeraFastDialog.java
@@ -215,54 +215,43 @@ public class TeraFastDialog extends BaseTransformDialog {
 
   /** Set data values in dialog. */
   public void getData() {
-    setTextIfPropertyValue(this.meta.getFastloadPath(), this.wFastLoadPath);
+    this.wFastLoadPath.setText(this.meta.getFastloadPath());
     if (this.wFastLoadPath.getText().isEmpty()) {
       this.wFastLoadPath.setText(TeraFastMeta.DEFAULT_FASTLOAD_PATH);
     }
-    setTextIfPropertyValue(this.meta.getControlFile(), this.wControlFile);
-    setTextIfPropertyValue(this.meta.getDataFile(), this.wDataFile);
+    this.wControlFile.setText(this.meta.getControlFile());
+    this.wDataFile.setText(this.meta.getDataFile());
     if (this.wDataFile.getText().isEmpty()) {
       this.wDataFile.setText(TeraFastMeta.DEFAULT_DATA_FILE);
     }
-    setTextIfPropertyValue(this.meta.getLogFile(), this.wLogFile);
-    setTextIfPropertyValue(this.meta.getTargetTable(), this.wTable);
+    this.wLogFile.setText(this.meta.getLogFile());
+    this.wTable.setText(this.meta.getTargetTable());
     if (this.wTable.getText().isEmpty()) {
       this.wTable.setText(TeraFastMeta.DEFAULT_TARGET_TABLE);
     }
     // Integer fields: always set from meta or default (evaluate() is false 
when value is 0)
-    Integer errLimit = this.meta.getErrorLimit().getValue();
+    Integer errLimit = this.meta.getErrorLimit();
     this.wErrLimit.setText(
         errLimit != null
             ? String.valueOf(errLimit)
             : String.valueOf(TeraFastMeta.DEFAULT_ERROR_LIMIT));
-    Integer sessions = this.meta.getSessions().getValue();
+    Integer sessions = this.meta.getSessions();
     this.wSessions.setText(
         sessions != null
             ? String.valueOf(sessions)
             : String.valueOf(TeraFastMeta.DEFAULT_SESSIONS));
-    setTextIfPropertyValue(this.meta.getConnectionName(), 
this.wConnection.getComboWidget());
-    this.wbTruncateTable.setSelection(
-        Boolean.TRUE.equals(this.meta.getTruncateTable().getValue())
-            || (this.meta.getTruncateTable().getValue() == null
-                && TeraFastMeta.DEFAULT_TRUNCATETABLE));
-    this.wUseControlFile.setSelection(
-        Boolean.TRUE.equals(this.meta.getUseControlFile().getValue())
-            || (this.meta.getUseControlFile().getValue() == null));
-    this.wVariableSubstitution.setSelection(
-        Boolean.TRUE.equals(this.meta.getVariableSubstitution().getValue())
-            || (this.meta.getVariableSubstitution().getValue() == null
-                && TeraFastMeta.DEFAULT_VARIABLE_SUBSTITUTION));
-
-    if (this.meta.getTableFieldList().getValue().size()
-        == this.meta.getStreamFieldList().getValue().size()) {
-      for (int i = 0; i < this.meta.getTableFieldList().getValue().size(); 
i++) {
+    this.wbTruncateTable.setSelection(this.meta.isTruncateTable());
+    this.wUseControlFile.setSelection(this.meta.isUseControlFile());
+    
this.wVariableSubstitution.setSelection(this.meta.isVariableSubstitution());
+    if (this.meta.getTableFieldList().size() == 
this.meta.getStreamFieldList().size()) {
+      for (int i = 0; i < this.meta.getTableFieldList().size(); i++) {
         TableItem item = this.wReturn.table.getItem(i);
-        item.setText(1, this.meta.getTableFieldList().getValue().get(i));
-        item.setText(2, this.meta.getStreamFieldList().getValue().get(i));
+        item.setText(1, this.meta.getTableFieldList().get(i));
+        item.setText(2, this.meta.getStreamFieldList().get(i));
       }
     }
-    if (this.meta.getDbMeta() != null) {
-      this.wConnection.setText(this.meta.getConnectionName().getValue());
+    if (this.meta.getConnectionName() != null) {
+      this.wConnection.setText(this.meta.getConnectionName());
     }
     setTableFieldCombo();
   }
@@ -308,7 +297,7 @@ public class TeraFastDialog extends BaseTransformDialog {
       return;
     }
     // refresh fields
-    this.meta.getTargetTable().setValue(this.wTable.getText());
+    this.meta.setTargetTable(this.wTable.getText());
     try {
       targetFields = this.meta.getRequiredFields(variables);
     } catch (HopException e) {
@@ -412,32 +401,27 @@ public class TeraFastDialog extends BaseTransformDialog {
   /** Ok clicked. */
   public void ok() {
     this.transformName = this.wTransformName.getText(); // return value
-    
this.meta.getUseControlFile().setValue(this.wUseControlFile.getSelection());
-    
this.meta.getVariableSubstitution().setValue(this.wVariableSubstitution.getSelection());
-    this.meta.getControlFile().setValue(this.wControlFile.getText());
-    this.meta.getFastloadPath().setValue(this.wFastLoadPath.getText());
-    this.meta.getDataFile().setValue(this.wDataFile.getText());
-    this.meta.getLogFile().setValue(this.wLogFile.getText());
-    this.meta
-        .getErrorLimit()
-        .setValue(Const.toInt(this.wErrLimit.getText(), 
TeraFastMeta.DEFAULT_ERROR_LIMIT));
-    this.meta
-        .getSessions()
-        .setValue(Const.toInt(this.wSessions.getText(), 
TeraFastMeta.DEFAULT_SESSIONS));
-    this.meta.getTargetTable().setValue(this.wTable.getText());
-    this.meta.getConnectionName().setValue(this.wConnection.getText());
-    this.meta
-        .getTruncateTable()
-        .setValue(this.wbTruncateTable.getSelection() && 
this.wbTruncateTable.getEnabled());
-    
this.meta.setDbMeta(this.pipelineMeta.findDatabase(this.wConnection.getText(), 
variables));
-
-    this.meta.getTableFieldList().getValue().clear();
-    this.meta.getStreamFieldList().getValue().clear();
+    this.meta.setUseControlFile(this.wUseControlFile.getSelection());
+    
this.meta.setVariableSubstitution(this.wVariableSubstitution.getSelection());
+    this.meta.setControlFile(this.wControlFile.getText());
+    this.meta.setFastloadPath(this.wFastLoadPath.getText());
+    this.meta.setDataFile(this.wDataFile.getText());
+    this.meta.setLogFile(this.wLogFile.getText());
+    this.meta.setErrorLimit(
+        Const.toInt(this.wErrLimit.getText(), 
TeraFastMeta.DEFAULT_ERROR_LIMIT));
+    this.meta.setSessions(Const.toInt(this.wSessions.getText(), 
TeraFastMeta.DEFAULT_SESSIONS));
+    this.meta.setTargetTable(this.wTable.getText());
+    this.meta.setConnectionName(this.wConnection.getText());
+    this.meta.setTruncateTable(
+        this.wbTruncateTable.getSelection() && 
this.wbTruncateTable.getEnabled());
+
+    this.meta.getTableFieldList().clear();
+    this.meta.getStreamFieldList().clear();
     int nrFields = this.wReturn.nrNonEmpty();
     for (int i = 0; i < nrFields; i++) {
       TableItem item = this.wReturn.getNonEmpty(i);
-      this.meta.getTableFieldList().getValue().add(item.getText(1));
-      this.meta.getStreamFieldList().getValue().add(item.getText(2));
+      this.meta.getTableFieldList().add(item.getText(1));
+      this.meta.getStreamFieldList().add(item.getText(2));
     }
 
     dispose();
@@ -475,7 +459,8 @@ public class TeraFastDialog extends BaseTransformDialog {
     this.buildLogFileLine(factory);
 
     // Connection line
-    this.wConnection = addConnectionLine(this.shell, this.wLogFile, 
meta.getDbMeta(), lsMod);
+    DatabaseMeta databaseMeta = 
pipelineMeta.findDatabase(wConnection.getText(), variables);
+    this.wConnection = addConnectionLine(this.shell, this.wLogFile, 
databaseMeta, lsMod);
     this.buildTableLine(factory);
     this.buildTruncateTableLine(factory);
     this.buildDataFileLine(factory);
diff --git 
a/plugins/transforms/terafast/src/main/java/org/apache/hop/pipeline/transforms/terafast/TeraFastMeta.java
 
b/plugins/transforms/terafast/src/main/java/org/apache/hop/pipeline/transforms/terafast/TeraFastMeta.java
index 4d557a2359..10592ee1ce 100644
--- 
a/plugins/transforms/terafast/src/main/java/org/apache/hop/pipeline/transforms/terafast/TeraFastMeta.java
+++ 
b/plugins/transforms/terafast/src/main/java/org/apache/hop/pipeline/transforms/terafast/TeraFastMeta.java
@@ -18,23 +18,23 @@
 package org.apache.hop.pipeline.transforms.terafast;
 
 import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
 import org.apache.commons.lang.StringUtils;
 import org.apache.hop.core.CheckResult;
 import org.apache.hop.core.ICheckResult;
 import org.apache.hop.core.annotations.ActionTransformType;
 import org.apache.hop.core.annotations.Transform;
 import org.apache.hop.core.database.Database;
+import org.apache.hop.core.database.DatabaseMeta;
 import org.apache.hop.core.exception.HopDatabaseException;
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.exception.HopTransformException;
 import org.apache.hop.core.row.IRowMeta;
-import org.apache.hop.core.util.AbstractTransformMeta;
-import org.apache.hop.core.util.BooleanPluginProperty;
-import org.apache.hop.core.util.IntegerPluginProperty;
-import org.apache.hop.core.util.StringListPluginProperty;
-import org.apache.hop.core.util.StringPluginProperty;
 import org.apache.hop.core.variables.IVariables;
 import org.apache.hop.i18n.BaseMessages;
+import org.apache.hop.metadata.api.HopMetadataProperty;
+import org.apache.hop.metadata.api.HopMetadataPropertyType;
 import org.apache.hop.metadata.api.IHopMetadataProvider;
 import org.apache.hop.pipeline.PipelineMeta;
 import org.apache.hop.pipeline.transform.BaseTransformMeta;
@@ -52,7 +52,9 @@ import org.apache.hop.pipeline.transform.TransformMeta;
     keywords = "i18n::TeraFastMeta.keyword",
     documentationUrl = "/pipeline/transforms/terafast.html",
     actionTransformTypes = {ActionTransformType.RDBMS, 
ActionTransformType.OUTPUT})
-public class TeraFastMeta extends AbstractTransformMeta<ITransform, 
ITransformData> {
+@Getter
+@Setter
+public class TeraFastMeta extends BaseTransformMeta<ITransform, 
ITransformData> {
 
   private static final Class<?> PKG = TeraFastMeta.class;
 
@@ -74,71 +76,51 @@ public class TeraFastMeta extends 
AbstractTransformMeta<ITransform, ITransformDa
   /** Default error limit. */
   public static final int DEFAULT_ERROR_LIMIT = 25;
 
-  /* custom xml values */
-  private static final String FASTLOADPATH = "fastload_path";
-
-  private static final String CONTROLFILE = "controlfile_path";
-
-  private static final String DATAFILE = "datafile_path";
-
-  private static final String LOGFILE = "logfile_path";
-
-  private static final String SESSIONS = "sessions";
-
-  private static final String ERRORLIMIT = "error_limit";
-
-  private static final String USECONTROLFILE = "use_control_file";
-
-  private static final String TARGETTABLE = "target_table";
-
-  private static final String TRUNCATETABLE = "truncate_table";
-
-  private static final String TABLE_FIELD_LIST = "table_field_list";
-
-  private static final String STREAM_FIELD_LIST = "stream_field_list";
-
-  private static final String VARIABLE_SUBSTITUTION = "variable_substitution";
-
   /** available options. */
-  private StringPluginProperty fastloadPath;
+  @HopMetadataProperty(key = "fastload_path")
+  private String fastloadPath;
+
+  @HopMetadataProperty(key = "controlfile_path")
+  private String controlFile;
 
-  private StringPluginProperty controlFile;
+  @HopMetadataProperty(key = "datafile_path")
+  private String dataFile;
 
-  private StringPluginProperty dataFile;
+  @HopMetadataProperty(key = "logfile_path")
+  private String logFile;
 
-  private StringPluginProperty logFile;
+  @HopMetadataProperty(key = "sessions")
+  private Integer sessions;
 
-  private IntegerPluginProperty sessions;
+  @HopMetadataProperty(key = "error_limit")
+  private Integer errorLimit;
 
-  private IntegerPluginProperty errorLimit;
+  @HopMetadataProperty(key = "use_control_file")
+  private boolean useControlFile;
 
-  private BooleanPluginProperty useControlFile;
+  @HopMetadataProperty(key = "variable_substitution")
+  private boolean variableSubstitution;
 
-  private BooleanPluginProperty variableSubstitution;
+  @HopMetadataProperty(key = "truncate_table")
+  private boolean truncateTable;
 
-  private BooleanPluginProperty truncateTable;
+  @HopMetadataProperty(key = "target_table")
+  private String targetTable;
 
-  private StringPluginProperty targetTable;
+  @HopMetadataProperty(key = "table_field_list")
+  private List<String> tableFieldList;
 
-  private StringListPluginProperty tableFieldList;
+  @HopMetadataProperty(key = "stream_field_list")
+  private List<String> streamFieldList;
 
-  private StringListPluginProperty streamFieldList;
+  @HopMetadataProperty(
+      key = "connectionName",
+      hopMetadataPropertyType = HopMetadataPropertyType.RDBMS_CONNECTION)
+  private String connectionName;
 
   /** */
   public TeraFastMeta() {
     super();
-    this.fastloadPath = this.getPropertyFactory().createString(FASTLOADPATH);
-    this.controlFile = this.getPropertyFactory().createString(CONTROLFILE);
-    this.dataFile = this.getPropertyFactory().createString(DATAFILE);
-    this.logFile = this.getPropertyFactory().createString(LOGFILE);
-    this.sessions = this.getPropertyFactory().createInteger(SESSIONS);
-    this.errorLimit = this.getPropertyFactory().createInteger(ERRORLIMIT);
-    this.targetTable = this.getPropertyFactory().createString(TARGETTABLE);
-    this.useControlFile = 
this.getPropertyFactory().createBoolean(USECONTROLFILE);
-    this.truncateTable = 
this.getPropertyFactory().createBoolean(TRUNCATETABLE);
-    this.tableFieldList = 
this.getPropertyFactory().createStringList(TABLE_FIELD_LIST);
-    this.streamFieldList = 
this.getPropertyFactory().createStringList(STREAM_FIELD_LIST);
-    this.variableSubstitution = 
this.getPropertyFactory().createBoolean(VARIABLE_SUBSTITUTION);
   }
 
   @Override
@@ -170,7 +152,7 @@ public class TeraFastMeta extends 
AbstractTransformMeta<ITransform, ITransformDa
       remarks.add(checkResult);
 
       boolean error = false;
-      for (String field : this.tableFieldList.getValue()) {
+      for (String field : this.tableFieldList) {
         if (tableFields.searchValueMeta(field) == null) {
           error = true;
           checkResult =
@@ -199,7 +181,7 @@ public class TeraFastMeta extends 
AbstractTransformMeta<ITransform, ITransformDa
         remarks.add(checkResult);
 
         error = false;
-        for (String field : this.streamFieldList.getValue()) {
+        for (String field : this.streamFieldList) {
           if (prev.searchValueMeta(field) == null) {
             error = true;
             checkResult =
@@ -237,8 +219,10 @@ public class TeraFastMeta extends 
AbstractTransformMeta<ITransform, ITransformDa
    * @throws HopException if an error occurs.
    */
   public Database connectToDatabase(IVariables variables) throws HopException {
-    if (this.getDbMeta() != null) {
-      Database db = new Database(loggingObject, variables, this.getDbMeta());
+    DatabaseMeta databaseMeta =
+        
getParentTransformMeta().getParentPipelineMeta().findDatabase(connectionName, 
variables);
+    if (databaseMeta != null) {
+      Database db = new Database(loggingObject, variables, databaseMeta);
       db.connect();
       return db;
     }
@@ -253,14 +237,14 @@ public class TeraFastMeta extends 
AbstractTransformMeta<ITransform, ITransformDa
    */
   @Override
   public void setDefault() {
-    this.fastloadPath.setValue(DEFAULT_FASTLOAD_PATH);
-    this.dataFile.setValue(DEFAULT_DATA_FILE);
-    this.sessions.setValue(DEFAULT_SESSIONS);
-    this.errorLimit.setValue(DEFAULT_ERROR_LIMIT);
-    this.truncateTable.setValue(DEFAULT_TRUNCATETABLE);
-    this.variableSubstitution.setValue(DEFAULT_VARIABLE_SUBSTITUTION);
-    this.targetTable.setValue(DEFAULT_TARGET_TABLE);
-    this.useControlFile.setValue(true);
+    this.fastloadPath = DEFAULT_FASTLOAD_PATH;
+    this.dataFile = DEFAULT_DATA_FILE;
+    this.sessions = DEFAULT_SESSIONS;
+    this.errorLimit = DEFAULT_ERROR_LIMIT;
+    this.truncateTable = DEFAULT_TRUNCATETABLE;
+    this.variableSubstitution = DEFAULT_VARIABLE_SUBSTITUTION;
+    this.targetTable = DEFAULT_TARGET_TABLE;
+    this.useControlFile = true;
   }
 
   @Override
@@ -282,11 +266,10 @@ public class TeraFastMeta extends 
AbstractTransformMeta<ITransform, ITransformDa
    */
   @Override
   public IRowMeta getRequiredFields(final IVariables variables) throws 
HopException {
-    if (!this.useControlFile.getValue()) {
+    if (!this.useControlFile) {
       final Database database = connectToDatabase(variables);
       IRowMeta fields =
-          database.getTableFieldsMeta(
-              StringUtils.EMPTY, 
variables.resolve(this.targetTable.getValue()));
+          database.getTableFieldsMeta(StringUtils.EMPTY, 
variables.resolve(this.targetTable));
       database.disconnect();
       if (fields == null) {
         throw new HopException(BaseMessages.getString(PKG, 
"TeraFastMeta.Exception.TableNotFound"));
@@ -305,172 +288,4 @@ public class TeraFastMeta extends 
AbstractTransformMeta<ITransform, ITransformDa
   public Object clone() {
     return super.clone();
   }
-
-  /**
-   * @return the fastloadPath
-   */
-  public StringPluginProperty getFastloadPath() {
-    return this.fastloadPath;
-  }
-
-  /**
-   * @param fastloadPath the fastloadPath to set
-   */
-  public void setFastloadPath(final StringPluginProperty fastloadPath) {
-    this.fastloadPath = fastloadPath;
-  }
-
-  /**
-   * @return the controlFile
-   */
-  public StringPluginProperty getControlFile() {
-    return this.controlFile;
-  }
-
-  /**
-   * @param controlFile the controlFile to set
-   */
-  public void setControlFile(final StringPluginProperty controlFile) {
-    this.controlFile = controlFile;
-  }
-
-  /**
-   * @return the dataFile
-   */
-  public StringPluginProperty getDataFile() {
-    return this.dataFile;
-  }
-
-  /**
-   * @param dataFile the dataFile to set
-   */
-  public void setDataFile(final StringPluginProperty dataFile) {
-    this.dataFile = dataFile;
-  }
-
-  /**
-   * @return the logFile
-   */
-  public StringPluginProperty getLogFile() {
-    return this.logFile;
-  }
-
-  /**
-   * @param logFile the logFile to set
-   */
-  public void setLogFile(final StringPluginProperty logFile) {
-    this.logFile = logFile;
-  }
-
-  /**
-   * @return the sessions
-   */
-  public IntegerPluginProperty getSessions() {
-    return this.sessions;
-  }
-
-  /**
-   * @param sessions the sessions to set
-   */
-  public void setSessions(final IntegerPluginProperty sessions) {
-    this.sessions = sessions;
-  }
-
-  /**
-   * @return the errorLimit
-   */
-  public IntegerPluginProperty getErrorLimit() {
-    return this.errorLimit;
-  }
-
-  /**
-   * @param errorLimit the errorLimit to set
-   */
-  public void setErrorLimit(final IntegerPluginProperty errorLimit) {
-    this.errorLimit = errorLimit;
-  }
-
-  /**
-   * @return the useControlFile
-   */
-  public BooleanPluginProperty getUseControlFile() {
-    return this.useControlFile;
-  }
-
-  /**
-   * @param useControlFile the useControlFile to set
-   */
-  public void setUseControlFile(final BooleanPluginProperty useControlFile) {
-    this.useControlFile = useControlFile;
-  }
-
-  /**
-   * @return the targetTable
-   */
-  public StringPluginProperty getTargetTable() {
-    return this.targetTable;
-  }
-
-  /**
-   * @param targetTable the targetTable to set
-   */
-  public void setTargetTable(final StringPluginProperty targetTable) {
-    this.targetTable = targetTable;
-  }
-
-  /**
-   * @return the truncateTable
-   */
-  public BooleanPluginProperty getTruncateTable() {
-    return this.truncateTable;
-  }
-
-  /**
-   * @param truncateTable the truncateTable to set
-   */
-  public void setTruncateTable(final BooleanPluginProperty truncateTable) {
-    this.truncateTable = truncateTable;
-  }
-
-  /**
-   * @return the tableFieldList
-   */
-  public StringListPluginProperty getTableFieldList() {
-    return this.tableFieldList;
-  }
-
-  /**
-   * @param tableFieldList the tableFieldList to set
-   */
-  public void setTableFieldList(final StringListPluginProperty tableFieldList) 
{
-    this.tableFieldList = tableFieldList;
-  }
-
-  /**
-   * @return the streamFieldList
-   */
-  public StringListPluginProperty getStreamFieldList() {
-    return this.streamFieldList;
-  }
-
-  /**
-   * @param streamFieldList the streamFieldList to set
-   */
-  public void setStreamFieldList(final StringListPluginProperty 
streamFieldList) {
-    this.streamFieldList = streamFieldList;
-  }
-
-  /**
-   * @return the variableSubstitution
-   */
-  public BooleanPluginProperty getVariableSubstitution() {
-    return this.variableSubstitution;
-  }
-
-  /**
-   * @param variableSubstitution the variableSubstitution to set
-   */
-  public void setVariableSubstitution(BooleanPluginProperty 
variableSubstitution) {
-    this.variableSubstitution = variableSubstitution;
-  }
 }
diff --git 
a/plugins/transforms/terafast/src/test/java/org/apache/hop/pipeline/transforms/terafast/FastloadControlBuilderTest.java
 
b/plugins/transforms/terafast/src/test/java/org/apache/hop/pipeline/transforms/terafast/FastloadControlBuilderTest.java
new file mode 100644
index 0000000000..38ff2c110d
--- /dev/null
+++ 
b/plugins/transforms/terafast/src/test/java/org/apache/hop/pipeline/transforms/terafast/FastloadControlBuilderTest.java
@@ -0,0 +1,287 @@
+/*
+ * 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.terafast;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Arrays;
+import java.util.List;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaDate;
+import org.apache.hop.core.row.value.ValueMetaInteger;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class FastloadControlBuilderTest {
+
+  private FastloadControlBuilder builder;
+
+  @BeforeEach
+  void setUp() {
+    builder = new FastloadControlBuilder();
+  }
+
+  @Test
+  void testNewline() {
+    builder.newline();
+    String out = builder.toString();
+    assertTrue(out.endsWith(";" + System.lineSeparator()));
+  }
+
+  @Test
+  void testLogonWithConnectionString() {
+    builder.logon("localtd/user,pass");
+    String out = builder.toString();
+    assertTrue(out.contains("LOGON localtd/user,pass"));
+  }
+
+  @Test
+  void testLogonWithConnectionStringThrowsWhenBlank() {
+    assertThrows(IllegalArgumentException.class, () -> builder.logon(""));
+    assertThrows(IllegalArgumentException.class, () -> builder.logon("   "));
+    assertThrows(IllegalArgumentException.class, () -> builder.logon(null));
+  }
+
+  @Test
+  void testLogonWithHostUserPassword() {
+    builder.logon("localtd", "user", "pass");
+    String out = builder.toString();
+    assertTrue(out.contains("LOGON localtd/user,pass"));
+  }
+
+  @Test
+  void testLogonWithHostUserPasswordThrowsWhenInvalid() {
+    assertThrows(IllegalArgumentException.class, () -> builder.logon("", "u", 
"p"));
+    assertThrows(IllegalArgumentException.class, () -> builder.logon("h", "", 
"p"));
+    assertThrows(IllegalArgumentException.class, () -> builder.logon("h", "u", 
null));
+  }
+
+  @Test
+  void testSetRecordFormat() {
+    builder.setRecordFormat(FastloadControlBuilder.RECORD_VARTEXT);
+    String out = builder.toString();
+    assertTrue(out.contains("SET RECORD " + 
FastloadControlBuilder.RECORD_VARTEXT));
+  }
+
+  @Test
+  void testSetRecordFormatThrowsWhenBlank() {
+    assertThrows(IllegalArgumentException.class, () -> 
builder.setRecordFormat(""));
+    assertThrows(IllegalArgumentException.class, () -> 
builder.setRecordFormat(null));
+  }
+
+  @Test
+  void testSetSessions() {
+    builder.setSessions(4);
+    String out = builder.toString();
+    assertTrue(out.contains("SESSIONS 4"));
+  }
+
+  @Test
+  void testSetSessionsThrowsWhenZeroOrNegative() {
+    assertThrows(IllegalArgumentException.class, () -> builder.setSessions(0));
+    assertThrows(IllegalArgumentException.class, () -> 
builder.setSessions(-1));
+  }
+
+  @Test
+  void testSetErrorLimit() {
+    builder.setErrorLimit(100);
+    String out = builder.toString();
+    assertTrue(out.contains("ERRLIMIT 100"));
+  }
+
+  @Test
+  void testSetErrorLimitThrowsWhenZeroOrNegative() {
+    assertThrows(IllegalArgumentException.class, () -> 
builder.setErrorLimit(0));
+    assertThrows(IllegalArgumentException.class, () -> 
builder.setErrorLimit(-1));
+  }
+
+  @Test
+  void testDefine() {
+    IRowMeta rowMeta = new RowMeta();
+    rowMeta.addValueMeta(new ValueMetaString("col1", 10, 0));
+    rowMeta.addValueMeta(new ValueMetaInteger("col2", 8, 0));
+    List<String> tableFieldList = Arrays.asList("col1", "col2");
+
+    builder.define(rowMeta, tableFieldList, "/path/data.dat");
+    String out = builder.toString();
+
+    assertTrue(out.contains("DEFINE "));
+    assertTrue(out.contains("col1"));
+    assertTrue(out.contains("col2"));
+    assertTrue(out.contains("FILE=/path/data.dat"));
+    assertTrue(out.contains("VARCHAR("));
+    assertTrue(out.contains("NEWLINECHAR"));
+  }
+
+  @Test
+  void testDefineWithDateField() {
+    IRowMeta rowMeta = new RowMeta();
+    rowMeta.addValueMeta(new ValueMetaDate("dt"));
+    List<String> tableFieldList = Arrays.asList("dt");
+
+    builder.define(rowMeta, tableFieldList, "data.dat");
+    String out = builder.toString();
+
+    assertTrue(out.contains("dt"));
+    assertTrue(out.contains("VARCHAR"));
+    assertTrue(out.contains("?")); // DEFAULT_NULL_VALUE
+  }
+
+  @Test
+  void testDefineThrowsWhenNull() {
+    IRowMeta rowMeta = new RowMeta();
+    rowMeta.addValueMeta(new ValueMetaString("a", 5, 0));
+    assertThrows(
+        IllegalArgumentException.class, () -> builder.define(null, 
Arrays.asList("a"), "f.dat"));
+    assertThrows(
+        IllegalArgumentException.class, () -> builder.define(rowMeta, 
Arrays.asList("a"), null));
+  }
+
+  @Test
+  void testInsert() {
+    IRowMeta rowMeta = new RowMeta();
+    rowMeta.addValueMeta(new ValueMetaString("col1", 10, 0));
+    rowMeta.addValueMeta(new ValueMetaInteger("col2", 8, 0));
+    List<String> tableFieldList = Arrays.asList("col1", "col2");
+
+    builder.insert(rowMeta, tableFieldList, "mytable");
+    String out = builder.toString();
+
+    assertTrue(out.contains("INSERT INTO mytable("));
+    assertTrue(out.contains(":col1"));
+    assertTrue(out.contains(":col2"));
+  }
+
+  @Test
+  void testInsertWithDateField() {
+    IRowMeta rowMeta = new RowMeta();
+    rowMeta.addValueMeta(new ValueMetaDate("dt"));
+    List<String> tableFieldList = Arrays.asList("dt");
+
+    builder.insert(rowMeta, tableFieldList, "t");
+    String out = builder.toString();
+
+    assertTrue(
+        out.contains(":dt(DATE, FORMAT '" + 
FastloadControlBuilder.DEFAULT_DATE_FORMAT + "')"));
+  }
+
+  @Test
+  void testInsertThrowsWhenNull() {
+    IRowMeta rowMeta = new RowMeta();
+    rowMeta.addValueMeta(new ValueMetaString("a", 5, 0));
+    assertThrows(
+        IllegalArgumentException.class, () -> builder.insert(null, 
Arrays.asList("a"), "t"));
+    assertThrows(
+        IllegalArgumentException.class, () -> builder.insert(rowMeta, 
Arrays.asList("a"), null));
+  }
+
+  @Test
+  void testShow() {
+    builder.show();
+    assertTrue(builder.toString().contains("SHOW"));
+  }
+
+  @Test
+  void testEndLoading() {
+    builder.endLoading();
+    assertTrue(builder.toString().contains("END LOADING"));
+  }
+
+  @Test
+  void testBeginLoadingWithTableOnly() {
+    builder.beginLoading("", "mytable");
+    String out = builder.toString();
+    assertTrue(out.contains("BEGIN LOADING mytable"));
+    assertTrue(out.contains("ERRORFILES " + 
FastloadControlBuilder.DEFAULT_ERROR_TABLE1));
+    assertTrue(out.contains(FastloadControlBuilder.DEFAULT_ERROR_TABLE2));
+  }
+
+  @Test
+  void testBeginLoadingWithSchemaAndTable() {
+    builder.beginLoading("myschema", "mytable");
+    String out = builder.toString();
+    assertTrue(out.contains("BEGIN LOADING mytable"));
+    assertTrue(out.contains("myschema." + 
FastloadControlBuilder.DEFAULT_ERROR_TABLE1));
+    assertTrue(out.contains("myschema." + 
FastloadControlBuilder.DEFAULT_ERROR_TABLE2));
+  }
+
+  @Test
+  void testBeginLoadingThrowsWhenTableBlank() {
+    assertThrows(IllegalArgumentException.class, () -> 
builder.beginLoading("", ""));
+    assertThrows(IllegalArgumentException.class, () -> 
builder.beginLoading("", "  "));
+  }
+
+  @Test
+  void testLine() {
+    builder.line("SOME COMMAND");
+    assertTrue(builder.toString().contains("SOME COMMAND"));
+  }
+
+  @Test
+  void testLineIgnoresBlank() {
+    builder.line("x");
+    builder.line("");
+    builder.line("   ");
+    builder.line("y");
+    String out = builder.toString();
+    assertTrue(out.contains("x"));
+    assertTrue(out.contains("y"));
+    // blank lines do not append content
+    assertTrue(out.indexOf("x") < out.indexOf("y"));
+  }
+
+  @Test
+  void testLogoff() {
+    builder.logoff();
+    assertTrue(builder.toString().contains("LOGOFF"));
+  }
+
+  @Test
+  void testFullSequence() {
+    IRowMeta rowMeta = new RowMeta();
+    rowMeta.addValueMeta(new ValueMetaString("a", 5, 0));
+    List<String> tableFieldList = Arrays.asList("a");
+
+    builder
+        .logon("host/user,pass")
+        .setRecordFormat(FastloadControlBuilder.RECORD_VARTEXT)
+        .setSessions(2)
+        .setErrorLimit(25)
+        .define(rowMeta, tableFieldList, "data.dat")
+        .show()
+        .beginLoading("", "target")
+        .insert(rowMeta, tableFieldList, "target")
+        .endLoading()
+        .logoff();
+
+    String out = builder.toString();
+    assertTrue(out.contains("LOGON host/user,pass"));
+    assertTrue(out.contains("SET RECORD"));
+    assertTrue(out.contains("SESSIONS 2"));
+    assertTrue(out.contains("ERRLIMIT 25"));
+    assertTrue(out.contains("DEFINE"));
+    assertTrue(out.contains("SHOW"));
+    assertTrue(out.contains("BEGIN LOADING target"));
+    assertTrue(out.contains("INSERT INTO target"));
+    assertTrue(out.contains("END LOADING"));
+    assertTrue(out.contains("LOGOFF"));
+  }
+}
diff --git 
a/plugins/transforms/terafast/src/test/java/org/apache/hop/pipeline/transforms/terafast/TeraFastMetaTest.java
 
b/plugins/transforms/terafast/src/test/java/org/apache/hop/pipeline/transforms/terafast/TeraFastMetaTest.java
new file mode 100644
index 0000000000..77c45fe6d2
--- /dev/null
+++ 
b/plugins/transforms/terafast/src/test/java/org/apache/hop/pipeline/transforms/terafast/TeraFastMetaTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.terafast;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Arrays;
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.plugins.PluginRegistry;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.metadata.api.IHopMetadataProvider;
+import org.apache.hop.pipeline.transform.TransformSerializationTestUtil;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class TeraFastMetaTest {
+  @BeforeEach
+  void setUpLoadSave() throws Exception {
+    HopEnvironment.init();
+    PluginRegistry.init();
+  }
+
+  @Test
+  void testSerialization() throws Exception {
+    
TransformSerializationTestUtil.testSerialization("/terafast-transform.xml", 
TeraFastMeta.class);
+  }
+
+  @Test
+  void testSetDefault() {
+    TeraFastMeta meta = new TeraFastMeta();
+    meta.setDefault();
+    assertEquals(TeraFastMeta.DEFAULT_FASTLOAD_PATH, meta.getFastloadPath());
+    assertEquals(TeraFastMeta.DEFAULT_DATA_FILE, meta.getDataFile());
+    assertEquals(TeraFastMeta.DEFAULT_SESSIONS, meta.getSessions());
+    assertEquals(TeraFastMeta.DEFAULT_ERROR_LIMIT, meta.getErrorLimit());
+    assertEquals(TeraFastMeta.DEFAULT_TARGET_TABLE, meta.getTargetTable());
+    assertTrue(meta.isTruncateTable());
+    assertTrue(meta.isVariableSubstitution());
+    assertTrue(meta.isUseControlFile());
+  }
+
+  @Test
+  void testClone() {
+    TeraFastMeta meta = new TeraFastMeta();
+    meta.setDefault();
+    meta.setFastloadPath("/custom/fastload");
+    meta.setTableFieldList(Arrays.asList("a", "b"));
+    meta.setStreamFieldList(Arrays.asList("x", "y"));
+    TeraFastMeta clone = (TeraFastMeta) meta.clone();
+    assertNotNull(clone);
+    assertEquals(meta.getFastloadPath(), clone.getFastloadPath());
+    assertEquals(meta.getTableFieldList(), clone.getTableFieldList());
+    assertEquals(meta.getStreamFieldList(), clone.getStreamFieldList());
+    assertEquals(meta.getSessions(), clone.getSessions());
+  }
+
+  @Test
+  void testGetFieldsDoesNotModifyInputRowMeta() throws Exception {
+    TeraFastMeta meta = new TeraFastMeta();
+    IRowMeta inputRowMeta = new RowMeta();
+    int originalSize = inputRowMeta.size();
+    meta.getFields(
+        inputRowMeta, "origin", null, null, new Variables(), 
(IHopMetadataProvider) null);
+    assertEquals(originalSize, inputRowMeta.size());
+  }
+
+  @Test
+  void testGetRequiredFieldsWhenUsingControlFile() throws Exception {
+    TeraFastMeta meta = new TeraFastMeta();
+    meta.setUseControlFile(true);
+    assertNull(meta.getRequiredFields(new Variables()));
+  }
+}
diff --git 
a/plugins/transforms/terafast/src/test/java/org/apache/hop/pipeline/transforms/terafast/TeraFastTest.java
 
b/plugins/transforms/terafast/src/test/java/org/apache/hop/pipeline/transforms/terafast/TeraFastTest.java
new file mode 100644
index 0000000000..6e2cc9e5f0
--- /dev/null
+++ 
b/plugins/transforms/terafast/src/test/java/org/apache/hop/pipeline/transforms/terafast/TeraFastTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.terafast;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.plugins.PluginRegistry;
+import org.apache.hop.pipeline.Pipeline;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.engines.local.LocalPipelineEngine;
+import org.apache.hop.pipeline.transform.TransformMeta;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class TeraFastTest {
+
+  @BeforeEach
+  void setUp() throws Exception {
+    HopEnvironment.init();
+    PluginRegistry.init();
+  }
+
+  @Test
+  void testCreateCommandLineThrowsWhenFastloadPathBlank() {
+    PipelineMeta pipelineMeta = new PipelineMeta();
+    TransformMeta transformMeta = new TransformMeta("test", new 
TeraFastMeta());
+    pipelineMeta.addTransform(transformMeta);
+
+    TeraFastMeta meta = new TeraFastMeta();
+    meta.setFastloadPath("");
+    TeraFastData data = new TeraFastData();
+    Pipeline pipeline = new LocalPipelineEngine(pipelineMeta);
+
+    TeraFast transform = new TeraFast(transformMeta, meta, data, 0, 
pipelineMeta, pipeline);
+
+    HopException e = assertThrows(HopException.class, 
transform::createCommandLine);
+    assertTrue(
+        e.getMessage().contains("Fastload path not set") || 
e.getMessage().contains("fastload"));
+  }
+
+  @Test
+  void testCreateCommandLineWithValidPath() throws Exception {
+    String path = new 
File(System.getProperty("java.io.tmpdir")).getAbsolutePath();
+
+    PipelineMeta pipelineMeta = new PipelineMeta();
+    TransformMeta transformMeta = new TransformMeta("test", new 
TeraFastMeta());
+    pipelineMeta.addTransform(transformMeta);
+
+    TeraFastMeta meta = new TeraFastMeta();
+    meta.setFastloadPath(path);
+    TeraFastData data = new TeraFastData();
+    Pipeline pipeline = new LocalPipelineEngine(pipelineMeta);
+
+    TeraFast transform = new TeraFast(transformMeta, meta, data, 0, 
pipelineMeta, pipeline);
+
+    String cmd = transform.createCommandLine();
+
+    assertNotNull(cmd);
+    assertTrue(cmd.contains(path) || cmd.replace('\\', 
'/').contains(path.replace('\\', '/')));
+  }
+
+  @Test
+  void testCreateCommandLineWithLogFile() throws Exception {
+    String fastloadPath = new 
File(System.getProperty("java.io.tmpdir")).getAbsolutePath();
+    String logPath =
+        new File(System.getProperty("java.io.tmpdir"), 
"terafast.log").getAbsolutePath();
+
+    PipelineMeta pipelineMeta = new PipelineMeta();
+    TransformMeta transformMeta = new TransformMeta("test", new 
TeraFastMeta());
+    pipelineMeta.addTransform(transformMeta);
+
+    TeraFastMeta meta = new TeraFastMeta();
+    meta.setFastloadPath(fastloadPath);
+    meta.setLogFile(logPath);
+    TeraFastData data = new TeraFastData();
+    Pipeline pipeline = new LocalPipelineEngine(pipelineMeta);
+
+    TeraFast transform = new TeraFast(transformMeta, meta, data, 0, 
pipelineMeta, pipeline);
+
+    String cmd = transform.createCommandLine();
+
+    assertNotNull(cmd);
+    assertTrue(cmd.contains("-e"));
+    assertTrue(
+        cmd.contains(logPath) || cmd.replace('\\', 
'/').contains(logPath.replace('\\', '/')));
+  }
+}
diff --git 
a/plugins/transforms/terafast/src/test/resources/terafast-transform.xml 
b/plugins/transforms/terafast/src/test/resources/terafast-transform.xml
new file mode 100644
index 0000000000..18093f5767
--- /dev/null
+++ b/plugins/transforms/terafast/src/test/resources/terafast-transform.xml
@@ -0,0 +1,49 @@
+<?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.
+  ~
+  -->
+
+<transform>
+    <name>Teradata Fastload bulk loader</name>
+    <type>TeraFast</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+        <method>none</method>
+        <schema_name/>
+    </partitioning>
+    <connection>unit-test-db</connection>
+    <fastload_path>/usr/bin/fastload</fastload_path>
+    <controlfile_path>/path/to/control.ctl</controlfile_path>
+    <datafile_path>${Internal.Transform.CopyNr}.dat</datafile_path>
+    <logfile_path>/tmp/terafast.log</logfile_path>
+    <sessions>2</sessions>
+    <error_limit>25</error_limit>
+    <use_control_file>Y</use_control_file>
+    <target_table>${TARGET_TABLE}_${RUN_ID}</target_table>
+    <truncate_table>Y</truncate_table>
+    <table_field_list>col1,col2</table_field_list>
+    <stream_field_list>field1,field2</stream_field_list>
+    <variable_substitution>Y</variable_substitution>
+    <attributes/>
+    <GUI>
+        <xloc>368</xloc>
+        <yloc>96</yloc>
+    </GUI>
+</transform>

Reply via email to