This is an automated email from the ASF dual-hosted git repository.

mattcasters pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 446bb7ea04 Fix AI-reported bugs against current main type rules (#8244)
446bb7ea04 is described below

commit 446bb7ea04b6b40ef09cba21637f37dfaa4a9c1a
Author: Matt Casters <[email protected]>
AuthorDate: Wed Sep 9 21:26:41 2026 +0200

    Fix AI-reported bugs against current main type rules (#8244)
    
    * Fix AI-reported bugs against current main type rules
    
    Verified against 2.20 DatabaseTypeMapper rather than 2.19 
getFieldDefinition.
    
    - #8216: store JDBC precision as Hop Number length; keep numericLength() as
      integer digits for dialect rules; PostgreSQL writes NUMERIC(length, scale)
    - #8218: timestamp parse mask reads HOP_DEFAULT_TIMESTAMP_FORMAT
    - #8219: Stream Lookup handles Timestamp default values
    - #8220/#8221: SQL Server DATE/TIME/DATETIME2 and NVARCHAR/VARCHAR(MAX) via 
type rules
    - #8222: parameter metadata uses StandardJdbcTypeMapper
    - #8223: skip JDBC getColumns() for ordinary databases
    - #8225: do not list empty unknown metadata folders
    - #8226: split catalog.schema even when supportsCatalogs() is false
    - #8227: ProgressMonitorDialog closes when the runnable returns
    - #8230: remove dead terminal menu constants
    
    Fixes #8216, #8218, #8219, #8220, #8221, #8222, #8223, #8225, #8226, #8227, 
#8230
    
    * Address review on Get Fields errors, PostgreSQL NUMERIC, and query 
fallback
    
    - Re-throw progress-dialog exceptions after the shell is disposed, and drop
      the extra monitor.done() so a failed Get Fields still shows the error 
dialog
    - Clamp PostgreSQL NUMERIC precision to at least the scale (PG < 15 rejects
      s > p); document the 2.20 NUMERIC(length, scale) meaning
    - Do not run getQueryFieldsFallback twice when that path already failed
    - Prefer Hop Timestamp over original JDBC DATE when generating SQL Server 
DDL
    - Map unsized NUMERIC/DECIMAL parameters back to Integer
---
 .../org/apache/hop/core/database/Database.java     | 160 +++++++++------------
 .../org/apache/hop/core/database/DatabaseMeta.java |  16 ++-
 .../database/types/StandardJdbcTypeMapper.java     |  25 +++-
 .../apache/hop/core/row/value/ValueMetaBase.java   |   2 +-
 .../apache/hop/core/database/DatabaseMetaTest.java |  14 ++
 .../org/apache/hop/core/database/DatabaseTest.java |  41 ++++++
 .../hop/core/row/value/ValueMetaTimestampTest.java |   7 +
 .../JdbcTypeMappingCharacterizationTest.txt        |  96 ++++++-------
 .../modules/ROOT/pages/database/column-types.adoc  |  15 +-
 .../ROOT/pages/database/databases/postgresql.adoc  |   8 ++
 .../CockroachFieldDefinitionGoldenTest.txt         |  12 +-
 .../GreenplumFieldDefinitionGoldenTest.txt         |  12 +-
 .../databases/mssql/MsSqlServerDatabaseMeta.java   |  87 +++++++++--
 .../mssql/MsSqlServerDatabaseMetaTest.java         |   4 +-
 ...sSqlServerDatabaseMeta_FieldDefinitionTest.java |   3 +-
 .../databases/mssql/MsSqlServerTypeRulesTest.java  | 138 ++++++++++++++++++
 .../mssql/MsSqlServerFieldDefinitionGoldenTest.txt |  88 ++++++------
 .../MsSqlServerNativeDatabaseMetaTest.java         |  24 ++++
 .../MsSqlServerNativeFieldDefinitionGoldenTest.txt |  88 ++++++------
 .../hop/databases/mysql/MySqlTypeRulesTest.java    |  18 +++
 .../postgresql/PostgreSqlDatabaseMeta.java         |   6 +-
 .../postgresql/PostgreSqlDatabaseMetaTest.java     |  10 +-
 .../postgresql/PostgreSqlTypeRulesTest.java        |  18 ++-
 .../PostgreSqlFieldDefinitionGoldenTest.txt        |  12 +-
 .../redshift/RedshiftFieldDefinitionGoldenTest.txt |  12 +-
 .../transforms/streamlookup/StreamLookup.java      |   2 +-
 .../transforms/streamlookup/StreamLookupTest.java  |  81 +++++++++++
 .../main/java/org/apache/hop/ui/core/PropsUi.java  |   1 -
 .../hop/ui/core/dialog/ProgressMonitorDialog.java  |  11 ++
 .../main/java/org/apache/hop/ui/hopgui/HopGui.java |   2 -
 .../perspective/metadata/MetadataPerspective.java  |   6 +-
 31 files changed, 724 insertions(+), 295 deletions(-)

diff --git a/core/src/main/java/org/apache/hop/core/database/Database.java 
b/core/src/main/java/org/apache/hop/core/database/Database.java
index a66a658906..bc44cf0e33 100644
--- a/core/src/main/java/org/apache/hop/core/database/Database.java
+++ b/core/src/main/java/org/apache/hop/core/database/Database.java
@@ -2287,33 +2287,25 @@ public class Database implements IVariables, 
ILoggingObject, AutoCloseable {
     // For now, we just try to get the field layout on the re-bound in the
     // exception block below.
     //
-    boolean maybeScanTable = false;
     try {
       if (databaseMeta.supportsPreparedStatementMetadataRetrieval()) {
-        // On with the regular program.
-        //
         fields = getQueryFieldsFromPreparedStatement(sql);
+      } else if (isDataServiceConnection()) {
+        // Data services return Hop type names on SOURCE_* columns. Standard 
JDBC getColumns()
+        // does not, so ordinary databases go straight to the fallback.
+        fields = getQueryFieldsFromDatabaseMetaData(sql);
       } else {
-        if (isDataServiceConnection()) {
-          fields = getQueryFieldsFromDatabaseMetaData(sql);
-        } else {
-          maybeScanTable = true;
-          fields = getQueryFieldsFromDatabaseMetaData();
-        }
+        fields = getQueryFieldsFallback(sql, param, inform, data);
       }
     } catch (Exception e) {
-      if (maybeScanTable) {
-        String fastFetchSql = sql.replaceAll("\\b((?i)WHERE)\\b(\\s)", "$1 1=2 
AND$2");
-        if (fastFetchSql.length() > sql.length()) {
-          try {
-            fields = getQueryFieldsFallback(sql, param, inform, data);
-          } catch (HopDatabaseException ignore) {
-            // Do nothing
-          }
-        }
-      }
-      if (fields == null) {
+      // Only recover from the prepared-statement / data-service paths. The 
else branch already
+      // ran the fallback; calling it again would execute the user's SQL twice.
+      if (databaseMeta.supportsPreparedStatementMetadataRetrieval() || 
isDataServiceConnection()) {
         fields = getQueryFieldsFallback(sql, param, inform, data);
+      } else if (e instanceof HopDatabaseException hopDatabaseException) {
+        throw hopDatabaseException;
+      } else {
+        throw new HopDatabaseException(e);
       }
     }
 
@@ -2358,54 +2350,55 @@ public class Database implements IVariables, 
ILoggingObject, AutoCloseable {
 
   private IRowMeta getQueryFieldsFromDatabaseMetaData(String sql) throws 
Exception {
 
-    ResultSet columns =
+    try (ResultSet columns =
         connection
             .getMetaData()
-            .getColumns("", "", StringUtils.isNotBlank(sql) ? sql : 
databaseMeta.getName(), "");
-    IRowMeta rowMeta = new RowMeta();
-    while (columns.next()) {
-      IValueMeta valueMeta = null;
-      String name = columns.getString("COLUMN_NAME");
-      String comments = columns.getString("REMARKS");
-      String type = columns.getString("SOURCE_DATA_TYPE");
-      int size = columns.getInt("COLUMN_SIZE");
-      valueMeta =
-          switch (type) {
-            case "Integer", "Long" -> new ValueMetaInteger();
-            case "BigDecimal", "BigNumber" -> new ValueMetaBigNumber();
-            case "Double", "Number" -> new ValueMetaNumber();
-            case "String" -> new ValueMetaString();
-            case "Date" -> new ValueMetaDate();
-            case "Boolean" -> new ValueMetaBoolean();
-            case "Binary" -> new ValueMetaBinary();
-            case "Timestamp" -> new ValueMetaTimestamp();
-            case "Internet Address" -> new ValueMetaInternetAddress();
-            default -> valueMeta;
-          };
-      if (valueMeta != null) {
-        valueMeta.setName(name);
-        valueMeta.setComments(comments);
-        valueMeta.setLength(size);
-        valueMeta.setOriginalColumnTypeName(type);
-
-        valueMeta.setConversionMask(columns.getString("SOURCE_MASK"));
-        valueMeta.setDecimalSymbol(columns.getString("SOURCE_DECIMAL_SYMBOL"));
-        
valueMeta.setGroupingSymbol(columns.getString("SOURCE_GROUPING_SYMBOL"));
-        
valueMeta.setCurrencySymbol(columns.getString("SOURCE_CURRENCY_SYMBOL"));
-
-        rowMeta.addValueMeta(valueMeta);
+            .getColumns("", "", StringUtils.isNotBlank(sql) ? sql : 
databaseMeta.getName(), "")) {
+      IRowMeta rowMeta = new RowMeta();
+      while (columns.next()) {
+        IValueMeta valueMeta = null;
+        String name = columns.getString("COLUMN_NAME");
+        String comments = columns.getString("REMARKS");
+        String type = columns.getString("SOURCE_DATA_TYPE");
+        int size = columns.getInt("COLUMN_SIZE");
+        valueMeta =
+            switch (type) {
+              case "Integer", "Long" -> new ValueMetaInteger();
+              case "BigDecimal", "BigNumber" -> new ValueMetaBigNumber();
+              case "Double", "Number" -> new ValueMetaNumber();
+              case "String" -> new ValueMetaString();
+              case "Date" -> new ValueMetaDate();
+              case "Boolean" -> new ValueMetaBoolean();
+              case "Binary" -> new ValueMetaBinary();
+              case "Timestamp" -> new ValueMetaTimestamp();
+              case "Internet Address" -> new ValueMetaInternetAddress();
+              default -> valueMeta;
+            };
+        if (valueMeta != null) {
+          valueMeta.setName(name);
+          valueMeta.setComments(comments);
+          valueMeta.setLength(size);
+          valueMeta.setOriginalColumnTypeName(type);
+
+          valueMeta.setConversionMask(columns.getString("SOURCE_MASK"));
+          
valueMeta.setDecimalSymbol(columns.getString("SOURCE_DECIMAL_SYMBOL"));
+          
valueMeta.setGroupingSymbol(columns.getString("SOURCE_GROUPING_SYMBOL"));
+          
valueMeta.setCurrencySymbol(columns.getString("SOURCE_CURRENCY_SYMBOL"));
+
+          rowMeta.addValueMeta(valueMeta);
+        } else {
+          log.logBasic(
+              "Database.getQueryFields() IValueMeta mapping not resolved for 
the column " + name);
+          rowMeta = null;
+          break;
+        }
+      }
+      if (rowMeta != null && !rowMeta.isEmpty()) {
+        return rowMeta;
       } else {
-        log.logBasic(
-            "Database.getQueryFields() IValueMeta mapping not resolved for the 
column " + name);
-        rowMeta = null;
-        break;
+        throw new Exception("Error in Database.getQueryFields()");
       }
     }
-    if (rowMeta != null && !rowMeta.isEmpty()) {
-      return rowMeta;
-    } else {
-      throw new Exception("Error in Database.getQueryFields()");
-    }
   }
 
   public IRowMeta getQueryFieldsFallback(String sql, boolean param, IRowMeta 
inform, Object[] data)
@@ -3563,33 +3556,18 @@ public class Database implements IVariables, 
ILoggingObject, AutoCloseable {
       ParameterMetaData pmd = ps.getParameterMetaData();
       for (int i = 1; i <= pmd.getParameterCount(); i++) {
         String name = "par" + i;
-        int sqltype = pmd.getParameterType(i);
-        int length = pmd.getPrecision(i);
-        int precision = pmd.getScale(i);
-        IValueMeta val =
-            switch (sqltype) {
-              case java.sql.Types.CHAR, java.sql.Types.VARCHAR -> new 
ValueMetaString(name);
-              case java.sql.Types.BIGINT,
-                      java.sql.Types.INTEGER,
-                      java.sql.Types.NUMERIC,
-                      java.sql.Types.SMALLINT,
-                      java.sql.Types.TINYINT ->
-                  new ValueMetaInteger(name);
-              case java.sql.Types.DECIMAL,
-                      java.sql.Types.DOUBLE,
-                      java.sql.Types.FLOAT,
-                      java.sql.Types.REAL ->
-                  new ValueMetaNumber(name);
-              case java.sql.Types.DATE, java.sql.Types.TIME, 
java.sql.Types.TIMESTAMP ->
-                  new ValueMetaDate(name);
-              case java.sql.Types.BOOLEAN, java.sql.Types.BIT -> new 
ValueMetaBoolean(name);
-              default -> new ValueMetaNone(name);
-            };
-
-        if (val.isNumeric() && (length > 18 || precision > 18)) {
-          val = new ValueMetaBigNumber(name);
+        int sqlType = pmd.getParameterType(i);
+        int precision = pmd.getPrecision(i);
+        int scale = pmd.getScale(i);
+        DatabaseColumn column = DatabaseColumn.of(name, sqlType, null, 
precision, scale, precision);
+        IValueMeta val = DatabaseTypeMapper.getValueMeta(this, databaseMeta, 
column, false, false);
+        if (val == null) {
+          val = new ValueMetaNone(name);
+        } else if (isUnsizedExactNumeric(sqlType, precision, scale) && 
val.isNumber()) {
+          // Drivers often report NUMERIC/DECIMAL with precision 0 for untyped 
parameters.
+          // The mapper then yields a double-backed Number; the old parameter 
path used Integer.
+          val = new ValueMetaInteger(name);
         }
-
         par.addValueMeta(val);
       }
     } catch (AbstractMethodError | Exception e) {
@@ -3600,6 +3578,10 @@ public class Database implements IVariables, 
ILoggingObject, AutoCloseable {
     return par;
   }
 
+  private static boolean isUnsizedExactNumeric(int sqlType, int precision, int 
scale) {
+    return (sqlType == Types.NUMERIC || sqlType == Types.DECIMAL) && precision 
<= 0 && scale <= 0;
+  }
+
   public int countParameters(String sql) {
     int q = 0;
     boolean quoteOpened = false;
diff --git a/core/src/main/java/org/apache/hop/core/database/DatabaseMeta.java 
b/core/src/main/java/org/apache/hop/core/database/DatabaseMeta.java
index 4fc82f3918..2e7bf5d47a 100644
--- a/core/src/main/java/org/apache/hop/core/database/DatabaseMeta.java
+++ b/core/src/main/java/org/apache/hop/core/database/DatabaseMeta.java
@@ -1097,13 +1097,15 @@ public class DatabaseMeta extends HopMetadataBase 
implements Cloneable, IHopMeta
   }
 
   private String quoteSchema(String schemaName) {
-    if (supportsCatalogs()) {
-      int separatorIndex = schemaName.indexOf('.');
-      if (separatorIndex > 0 && separatorIndex < schemaName.length() - 1) {
-        String catalogName = schemaName.substring(0, separatorIndex);
-        String schemaPart = schemaName.substring(separatorIndex + 1);
-        return quoteField(catalogName) + "." + quoteField(schemaPart);
-      }
+    // A composite "catalog.schema" is split whenever the caller passed one, 
including on dialects
+    // that return supportsCatalogs() == false (jTDS SQL Server, Access, 
Gupta, Iris). That flag is
+    // a browsing hint and must not collapse mydb.dbo into the unresolvable 
identifier [mydb.dbo].
+    // A schema whose name itself contains a literal dot is vanishingly rare 
next to catalog.schema.
+    int separatorIndex = schemaName.indexOf('.');
+    if (separatorIndex > 0 && separatorIndex < schemaName.length() - 1) {
+      String catalogName = schemaName.substring(0, separatorIndex);
+      String schemaPart = schemaName.substring(separatorIndex + 1);
+      return quoteField(catalogName) + "." + quoteField(schemaPart);
     }
     return quoteField(schemaName);
   }
diff --git 
a/core/src/main/java/org/apache/hop/core/database/types/StandardJdbcTypeMapper.java
 
b/core/src/main/java/org/apache/hop/core/database/types/StandardJdbcTypeMapper.java
index 9548e70033..751c809cbc 100644
--- 
a/core/src/main/java/org/apache/hop/core/database/types/StandardJdbcTypeMapper.java
+++ 
b/core/src/main/java/org/apache/hop/core/database/types/StandardJdbcTypeMapper.java
@@ -197,7 +197,9 @@ public final class StandardJdbcTypeMapper {
    */
   private static Numeric mapDecimal(DatabaseMeta databaseMeta, DatabaseColumn 
column, int sqlType) {
     int valtype = IValueMeta.TYPE_NUMBER;
-    int length = numericLength(column);
+    // Hop Number/BigNumber length is the total number of significant digits 
(JDBC precision).
+    // numericLength() remains the digits before the decimal, for dialect rule 
conditions.
+    int length = totalDigits(column);
     int precision = numericScale(column);
 
     if (isApproximateNumeric(sqlType)) {
@@ -271,10 +273,13 @@ public final class StandardJdbcTypeMapper {
   }
 
   /**
-   * The Hop length of a numeric column: the digits before the decimal.
+   * Digits before the decimal: JDBC precision with the scale taken off.
    *
-   * <p>A database reports precision as the total number of significant 
digits, so the scale has to
-   * come off. A value at or beyond 126 means the database did not really say.
+   * <p>Dialect rules use this to test whether a declaration is possible at 
all (an overscaled
+   * DOUBLE, an unspecified NUMERIC). It is not the Hop length stored on the 
value metadata; that is
+   * {@link #totalDigits(DatabaseColumn)}.
+   *
+   * <p>A value at or beyond 126 means the database did not really say.
    */
   public static int numericLength(DatabaseColumn column) {
     int length = column.getPrecision();
@@ -285,6 +290,18 @@ public final class StandardJdbcTypeMapper {
     return length >= 126 ? -1 : length;
   }
 
+  /**
+   * The Hop length of a numeric column: the total number of significant 
digits, the same value the
+   * database reports as its precision. Dialogs and {@code DECIMAL(length, 
precision)} writers use
+   * this meaning.
+   *
+   * <p>A value at or beyond 126 means the database did not really say.
+   */
+  public static int totalDigits(DatabaseColumn column) {
+    int length = column.getPrecision();
+    return length >= 126 ? -1 : length;
+  }
+
   /** The Hop precision of a numeric column: the digits after the decimal. */
   public static int numericScale(DatabaseColumn column) {
     int scale = column.getScale();
diff --git 
a/core/src/main/java/org/apache/hop/core/row/value/ValueMetaBase.java 
b/core/src/main/java/org/apache/hop/core/row/value/ValueMetaBase.java
index 4bf25f1d03..652a9d0583 100644
--- a/core/src/main/java/org/apache/hop/core/row/value/ValueMetaBase.java
+++ b/core/src/main/java/org/apache/hop/core/row/value/ValueMetaBase.java
@@ -125,7 +125,7 @@ public class ValueMetaBase implements IValueMeta {
           EnvUtil.getSystemProperty(Const.HOP_DEFAULT_DATE_FORMAT), 
"yyyy/MM/dd HH:mm:ss.SSS");
   public static final String DEFAULT_TIMESTAMP_PARSE_MASK =
       Const.NVL(
-          EnvUtil.getSystemProperty(Const.HOP_DEFAULT_DATE_FORMAT),
+          EnvUtil.getSystemProperty(Const.HOP_DEFAULT_TIMESTAMP_FORMAT),
           "yyyy/MM/dd HH:mm:ss.SSSSSSSSS");
   // endregion
 
diff --git 
a/core/src/test/java/org/apache/hop/core/database/DatabaseMetaTest.java 
b/core/src/test/java/org/apache/hop/core/database/DatabaseMetaTest.java
index efc45966e2..0aa49e2ba9 100644
--- a/core/src/test/java/org/apache/hop/core/database/DatabaseMetaTest.java
+++ b/core/src/test/java/org/apache/hop/core/database/DatabaseMetaTest.java
@@ -196,6 +196,20 @@ class DatabaseMetaTest {
         databaseMeta.getQuotedSchemaTableCombination(variables, 
"catalog.schema", "sample"));
   }
 
+  @Test
+  void splitsCompositeSchemaEvenWhenCatalogsAreUnsupported() {
+    when(iDatabase.getStartQuote()).thenReturn("[");
+    when(iDatabase.getEndQuote()).thenReturn("]");
+    when(iDatabase.isSupportsCatalogs()).thenReturn(false);
+    when(iDatabase.isQuoteAllFields()).thenReturn(true);
+    when(iDatabase.getSchemaTableCombination(anyString(), anyString()))
+        .thenAnswer(invocation -> invocation.getArgument(0) + "." + 
invocation.getArgument(1));
+
+    assertEquals(
+        "[catalog].[schema].[sample]",
+        databaseMeta.getQuotedSchemaTableCombination(variables, 
"catalog.schema", "sample"));
+  }
+
   @Test
   @SuppressWarnings("unchecked")
   void testModifyingName() {
diff --git a/core/src/test/java/org/apache/hop/core/database/DatabaseTest.java 
b/core/src/test/java/org/apache/hop/core/database/DatabaseTest.java
index 5f2dd67a60..3ec4b3d3ce 100644
--- a/core/src/test/java/org/apache/hop/core/database/DatabaseTest.java
+++ b/core/src/test/java/org/apache/hop/core/database/DatabaseTest.java
@@ -43,6 +43,7 @@ import java.lang.reflect.Field;
 import java.sql.BatchUpdateException;
 import java.sql.Connection;
 import java.sql.DatabaseMetaData;
+import java.sql.ParameterMetaData;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.ResultSetMetaData;
@@ -140,6 +141,46 @@ class DatabaseTest {
     assertEquals(columnSize, iRowMeta.getValueMeta(0).getLength());
   }
 
+  @Test
+  void getParameterMetaDataMapsNvarcharAndNumeric() throws Exception {
+    when(meta.getIDatabase()).thenReturn(new NoneDatabaseMeta());
+    ParameterMetaData parameterMetaData = mock(ParameterMetaData.class);
+    when(ps.getParameterMetaData()).thenReturn(parameterMetaData);
+    when(parameterMetaData.getParameterCount()).thenReturn(2);
+    when(parameterMetaData.getParameterType(1)).thenReturn(Types.NVARCHAR);
+    when(parameterMetaData.getPrecision(1)).thenReturn(20);
+    when(parameterMetaData.getScale(1)).thenReturn(0);
+    when(parameterMetaData.getParameterType(2)).thenReturn(Types.NUMERIC);
+    when(parameterMetaData.getPrecision(2)).thenReturn(18);
+    when(parameterMetaData.getScale(2)).thenReturn(4);
+
+    Database db = new Database(log, variables, meta);
+    IRowMeta rowMeta = db.getParameterMetaData(ps);
+
+    assertEquals(2, rowMeta.size());
+    assertTrue(rowMeta.getValueMeta(0).isString());
+    assertTrue(rowMeta.getValueMeta(1).isNumeric());
+    assertFalse(rowMeta.getValueMeta(1).isInteger());
+    assertEquals(4, rowMeta.getValueMeta(1).getPrecision());
+  }
+
+  @Test
+  void getParameterMetaDataMapsUnsizedNumericToInteger() throws Exception {
+    when(meta.getIDatabase()).thenReturn(new NoneDatabaseMeta());
+    ParameterMetaData parameterMetaData = mock(ParameterMetaData.class);
+    when(ps.getParameterMetaData()).thenReturn(parameterMetaData);
+    when(parameterMetaData.getParameterCount()).thenReturn(1);
+    when(parameterMetaData.getParameterType(1)).thenReturn(Types.NUMERIC);
+    when(parameterMetaData.getPrecision(1)).thenReturn(0);
+    when(parameterMetaData.getScale(1)).thenReturn(0);
+
+    Database db = new Database(log, variables, meta);
+    IRowMeta rowMeta = db.getParameterMetaData(ps);
+
+    assertEquals(1, rowMeta.size());
+    assertTrue(rowMeta.getValueMeta(0).isInteger());
+  }
+
   /**
    * When using getLookup calls there is no need to make attempt to retrieve 
row set metadata for
    * every call. That may bring performance penalty depends on jdbc driver 
implementation. For some
diff --git 
a/core/src/test/java/org/apache/hop/core/row/value/ValueMetaTimestampTest.java 
b/core/src/test/java/org/apache/hop/core/row/value/ValueMetaTimestampTest.java
index 9774ce683d..3513023f2f 100644
--- 
a/core/src/test/java/org/apache/hop/core/row/value/ValueMetaTimestampTest.java
+++ 
b/core/src/test/java/org/apache/hop/core/row/value/ValueMetaTimestampTest.java
@@ -39,6 +39,13 @@ import org.mockito.stubbing.Answer;
 @ExtendWith(RestoreHopEnvironmentExtension.class)
 class ValueMetaTimestampTest {
 
+  @Test
+  void timestampParseMaskUsesTheTimestampPattern() {
+    assertEquals("yyyy/MM/dd HH:mm:ss.SSSSSSSSS", 
ValueMetaBase.DEFAULT_TIMESTAMP_PARSE_MASK);
+    assertEquals(
+        ValueMetaBase.DEFAULT_TIMESTAMP_FORMAT_MASK, 
ValueMetaBase.DEFAULT_TIMESTAMP_PARSE_MASK);
+  }
+
   @Test
   void testSetPreparedStatementValue() throws Exception {
     ValueMetaTimestamp vm = new ValueMetaTimestamp();
diff --git 
a/core/src/test/resources/org/apache/hop/core/database/JdbcTypeMappingCharacterizationTest.txt
 
b/core/src/test/resources/org/apache/hop/core/database/JdbcTypeMappingCharacterizationTest.txt
index 654cb89e3c..ddae6f4f81 100644
--- 
a/core/src/test/resources/org/apache/hop/core/database/JdbcTypeMappingCharacterizationTest.txt
+++ 
b/core/src/test/resources/org/apache/hop/core/database/JdbcTypeMappingCharacterizationTest.txt
@@ -59,32 +59,32 @@ TINYINT        p=  16 s=  16 signed  | DB=Integer(2,0)      
 VMB=Integer(2,0)
 TINYINT        p= 126 s=   0 signed  | DB=Integer(2,0)       VMB=Integer(2,0)  
     PRV=Integer(2,0)       NEW=Integer(2,0)       
 DECIMAL        p=   0 s=   0 signed  | DB=Number(0,0)        VMB=Number(0,0)   
     PRV=Number(0,0)        NEW=Number(0,0)        
 DECIMAL        p=  38 s=   0 signed  | DB=BigNumber(38,0)    
VMB=BigNumber(38,0)    PRV=BigNumber(38,0)    NEW=BigNumber(38,0)    
-DECIMAL        p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-DECIMAL        p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+DECIMAL        p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+DECIMAL        p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 DECIMAL        p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 DECIMAL        p= 126 s=   0 signed  | DB=Number(-1,0)       VMB=Number(-1,0)  
     PRV=Number(-1,0)       NEW=Number(-1,0)       
 NUMERIC        p=   0 s=   0 signed  | DB=Number(0,0)        VMB=Number(0,0)   
     PRV=Number(0,0)        NEW=Number(0,0)        
 NUMERIC        p=  38 s=   0 signed  | DB=BigNumber(38,0)    
VMB=BigNumber(38,0)    PRV=BigNumber(38,0)    NEW=BigNumber(38,0)    
-NUMERIC        p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-NUMERIC        p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+NUMERIC        p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+NUMERIC        p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 NUMERIC        p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 NUMERIC        p= 126 s=   0 signed  | DB=Number(-1,0)       VMB=Number(-1,0)  
     PRV=Number(-1,0)       NEW=Number(-1,0)       
 DOUBLE         p=   0 s=   0 signed  | DB=Number(0,-1)       VMB=Number(0,-1)  
     PRV=Number(0,-1)       NEW=Number(0,-1)       
 DOUBLE         p=  38 s=   0 signed  | DB=BigNumber(38,-1)   
VMB=BigNumber(38,-1)   PRV=BigNumber(38,-1)   NEW=BigNumber(38,-1)   
-DOUBLE         p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-DOUBLE         p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+DOUBLE         p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+DOUBLE         p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 DOUBLE         p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 DOUBLE         p= 126 s=   0 signed  | DB=Number(-1,-1)      VMB=Number(-1,-1) 
     PRV=Number(-1,-1)      NEW=Number(-1,-1)      
 FLOAT          p=   0 s=   0 signed  | DB=Number(0,-1)       VMB=Number(0,-1)  
     PRV=Number(0,-1)       NEW=Number(0,-1)       
 FLOAT          p=  38 s=   0 signed  | DB=BigNumber(38,-1)   
VMB=BigNumber(38,-1)   PRV=BigNumber(38,-1)   NEW=BigNumber(38,-1)   
-FLOAT          p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-FLOAT          p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+FLOAT          p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+FLOAT          p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 FLOAT          p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 FLOAT          p= 126 s=   0 signed  | DB=Number(-1,-1)      VMB=Number(-1,-1) 
     PRV=Number(-1,-1)      NEW=Number(-1,-1)      
 REAL           p=   0 s=   0 signed  | DB=Number(0,-1)       VMB=Number(0,-1)  
     PRV=Number(0,-1)       NEW=Number(0,-1)       
 REAL           p=  38 s=   0 signed  | DB=BigNumber(38,-1)   
VMB=BigNumber(38,-1)   PRV=BigNumber(38,-1)   NEW=BigNumber(38,-1)   
-REAL           p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-REAL           p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+REAL           p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+REAL           p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 REAL           p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 REAL           p= 126 s=   0 signed  | DB=Number(-1,-1)      VMB=Number(-1,-1) 
     PRV=Number(-1,-1)      NEW=Number(-1,-1)      
 TIMESTAMP      p=   0 s=   0 signed  | DB=Timestamp(0,-1)    
VMB=Timestamp(0,-1)    PRV=Timestamp(0,-1)    NEW=Timestamp(0,-1)    
@@ -207,31 +207,31 @@ TINYINT        p=  16 s=  16 signed  | DB=Integer(2,0)    
   VMB=Integer(2,0)
 TINYINT        p= 126 s=   0 signed  | DB=Integer(2,0)       VMB=Integer(2,0)  
     PRV=Integer(2,0)       NEW=Integer(2,0)       
 DECIMAL        p=   0 s=   0 signed  | DB=Number(0,0)        VMB=Number(0,0)   
     PRV=Number(0,0)        NEW=Number(0,0)        
 DECIMAL        p=  38 s=   0 signed  | DB=BigNumber(38,0)    
VMB=BigNumber(38,0)    PRV=BigNumber(38,0)    NEW=BigNumber(38,0)    
-DECIMAL        p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-DECIMAL        p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+DECIMAL        p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+DECIMAL        p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 DECIMAL        p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 DECIMAL        p= 126 s=   0 signed  | DB=Number(-1,0)       VMB=Number(-1,0)  
     PRV=Number(-1,0)       NEW=Number(-1,0)       
 NUMERIC        p=   0 s=   0 signed  | DB=Number(0,0)        VMB=Number(0,0)   
     PRV=Number(0,0)        NEW=Number(0,0)        
 NUMERIC        p=  38 s=   0 signed  | DB=BigNumber(38,0)    
VMB=BigNumber(38,0)    PRV=BigNumber(38,0)    NEW=BigNumber(38,0)    
-NUMERIC        p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-NUMERIC        p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+NUMERIC        p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+NUMERIC        p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 NUMERIC        p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 NUMERIC        p= 126 s=   0 signed  | DB=Number(-1,0)       VMB=Number(-1,0)  
     PRV=Number(-1,0)       NEW=Number(-1,0)       
 DOUBLE         p=   0 s=   0 signed  | DB=Number(0,-1)       VMB=Number(0,-1)  
     PRV=Number(0,-1)       NEW=Number(0,-1)       
 DOUBLE         p=  38 s=   0 signed  | DB=BigNumber(38,-1)   
VMB=BigNumber(38,-1)   PRV=Number(-1,-1)      NEW=BigNumber(38,-1)   DIVERGE
-DOUBLE         p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
+DOUBLE         p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
 DOUBLE         p=  20 s=  10 signed  | DB=Number(-1,-1)      
VMB=BigNumber(20,10)   PRV=Number(-1,10)      NEW=Number(-1,-1)      DIVERGE
 DOUBLE         p=  16 s=  16 signed  | DB=Number(-1,-1)      VMB=Number(-1,-1) 
     PRV=Number(-1,-1)      NEW=Number(-1,-1)      
 DOUBLE         p= 126 s=   0 signed  | DB=Number(-1,-1)      VMB=Number(-1,-1) 
     PRV=Number(-1,-1)      NEW=Number(-1,-1)      
 FLOAT          p=   0 s=   0 signed  | DB=Number(0,-1)       VMB=Number(0,-1)  
     PRV=Number(0,-1)       NEW=Number(0,-1)       
 FLOAT          p=  38 s=   0 signed  | DB=BigNumber(38,-1)   
VMB=BigNumber(38,-1)   PRV=BigNumber(38,-1)   NEW=BigNumber(38,-1)   
-FLOAT          p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
+FLOAT          p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
 FLOAT          p=  20 s=  10 signed  | DB=Number(-1,-1)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(-1,-1)      DIVERGE
 FLOAT          p=  16 s=  16 signed  | DB=Number(-1,-1)      VMB=Number(-1,-1) 
     PRV=Number(-1,-1)      NEW=Number(-1,-1)      
 FLOAT          p= 126 s=   0 signed  | DB=Number(-1,-1)      VMB=Number(-1,-1) 
     PRV=Number(-1,-1)      NEW=Number(-1,-1)      
 REAL           p=   0 s=   0 signed  | DB=Number(0,-1)       VMB=Number(0,-1)  
     PRV=Number(0,-1)       NEW=Number(0,-1)       
 REAL           p=  38 s=   0 signed  | DB=BigNumber(38,-1)   
VMB=BigNumber(38,-1)   PRV=BigNumber(38,-1)   NEW=BigNumber(38,-1)   
-REAL           p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
+REAL           p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
 REAL           p=  20 s=  10 signed  | DB=Number(-1,-1)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(-1,-1)      DIVERGE
 REAL           p=  16 s=  16 signed  | DB=Number(-1,-1)      VMB=Number(-1,-1) 
     PRV=Number(-1,-1)      NEW=Number(-1,-1)      
 REAL           p= 126 s=   0 signed  | DB=Number(-1,-1)      VMB=Number(-1,-1) 
     PRV=Number(-1,-1)      NEW=Number(-1,-1)      
@@ -355,32 +355,32 @@ TINYINT        p=  16 s=  16 signed  | DB=Integer(2,0)    
   VMB=Integer(2,0)
 TINYINT        p= 126 s=   0 signed  | DB=Integer(2,0)       VMB=Integer(2,0)  
     PRV=Integer(2,0)       NEW=Integer(2,0)       
 DECIMAL        p=   0 s=   0 signed  | DB=Number(0,0)        VMB=Number(0,0)   
     PRV=Number(0,0)        NEW=Number(0,0)        
 DECIMAL        p=  38 s=   0 signed  | DB=BigNumber(38,0)    
VMB=BigNumber(38,0)    PRV=BigNumber(38,0)    NEW=BigNumber(38,0)    
-DECIMAL        p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-DECIMAL        p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+DECIMAL        p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+DECIMAL        p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 DECIMAL        p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 DECIMAL        p= 126 s=   0 signed  | DB=Number(-1,0)       VMB=Number(-1,0)  
     PRV=Number(-1,0)       NEW=Number(-1,0)       
 NUMERIC        p=   0 s=   0 signed  | DB=Number(0,0)        VMB=Number(0,0)   
     PRV=Number(0,0)        NEW=Number(0,0)        
 NUMERIC        p=  38 s=   0 signed  | DB=BigNumber(38,0)    
VMB=BigNumber(38,0)    PRV=BigNumber(38,0)    NEW=BigNumber(38,0)    
-NUMERIC        p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-NUMERIC        p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+NUMERIC        p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+NUMERIC        p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 NUMERIC        p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 NUMERIC        p= 126 s=   0 signed  | DB=Number(-1,0)       VMB=Number(-1,0)  
     PRV=Number(-1,0)       NEW=Number(-1,0)       
 DOUBLE         p=   0 s=   0 signed  | DB=Number(0,-1)       VMB=Number(0,-1)  
     PRV=Number(0,-1)       NEW=Number(0,-1)       
 DOUBLE         p=  38 s=   0 signed  | DB=BigNumber(38,-1)   
VMB=BigNumber(38,-1)   PRV=BigNumber(38,-1)   NEW=BigNumber(38,-1)   
-DOUBLE         p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-DOUBLE         p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+DOUBLE         p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+DOUBLE         p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 DOUBLE         p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 DOUBLE         p= 126 s=   0 signed  | DB=Number(-1,-1)      VMB=Number(-1,-1) 
     PRV=Number(-1,-1)      NEW=Number(-1,-1)      
 FLOAT          p=   0 s=   0 signed  | DB=Number(0,-1)       VMB=Number(0,-1)  
     PRV=Number(0,-1)       NEW=Number(0,-1)       
 FLOAT          p=  38 s=   0 signed  | DB=BigNumber(38,-1)   
VMB=BigNumber(38,-1)   PRV=BigNumber(38,-1)   NEW=BigNumber(38,-1)   
-FLOAT          p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-FLOAT          p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+FLOAT          p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+FLOAT          p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 FLOAT          p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 FLOAT          p= 126 s=   0 signed  | DB=Number(-1,-1)      VMB=Number(-1,-1) 
     PRV=Number(-1,-1)      NEW=Number(-1,-1)      
 REAL           p=   0 s=   0 signed  | DB=Number(0,-1)       VMB=Number(0,-1)  
     PRV=Number(0,-1)       NEW=Number(0,-1)       
 REAL           p=  38 s=   0 signed  | DB=BigNumber(38,-1)   
VMB=BigNumber(38,-1)   PRV=BigNumber(38,-1)   NEW=BigNumber(38,-1)   
-REAL           p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-REAL           p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+REAL           p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+REAL           p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 REAL           p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 REAL           p= 126 s=   0 signed  | DB=Number(-1,-1)      VMB=Number(-1,-1) 
     PRV=Number(-1,-1)      NEW=Number(-1,-1)      
 TIMESTAMP      p=   0 s=   0 signed  | DB=Timestamp(0,-1)    
VMB=Timestamp(0,-1)    PRV=Timestamp(0,-1)    NEW=Timestamp(0,-1)    
@@ -503,32 +503,32 @@ TINYINT        p=  16 s=  16 signed  | DB=Integer(2,0)    
   VMB=Integer(2,0)
 TINYINT        p= 126 s=   0 signed  | DB=Integer(2,0)       VMB=Integer(2,0)  
     PRV=Integer(2,0)       NEW=Integer(2,0)       
 DECIMAL        p=   0 s=   0 signed  | DB=Number(0,0)        VMB=Number(0,0)   
     PRV=Number(0,0)        NEW=Number(0,0)        
 DECIMAL        p=  38 s=   0 signed  | DB=BigNumber(38,0)    
VMB=BigNumber(38,0)    PRV=BigNumber(38,0)    NEW=BigNumber(38,0)    
-DECIMAL        p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-DECIMAL        p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+DECIMAL        p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+DECIMAL        p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 DECIMAL        p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 DECIMAL        p= 126 s=   0 signed  | DB=Number(-1,0)       VMB=Number(-1,0)  
     PRV=Number(-1,0)       NEW=Number(-1,0)       
 NUMERIC        p=   0 s=   0 signed  | DB=Number(0,0)        VMB=Number(0,0)   
     PRV=Number(0,0)        NEW=Number(0,0)        
 NUMERIC        p=  38 s=   0 signed  | DB=BigNumber(38,0)    
VMB=BigNumber(38,0)    PRV=BigNumber(38,0)    NEW=BigNumber(38,0)    
-NUMERIC        p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-NUMERIC        p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+NUMERIC        p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+NUMERIC        p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 NUMERIC        p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 NUMERIC        p= 126 s=   0 signed  | DB=Number(-1,0)       VMB=Number(-1,0)  
     PRV=Number(-1,0)       NEW=Number(-1,0)       
 DOUBLE         p=   0 s=   0 signed  | DB=Number(0,-1)       VMB=Number(0,-1)  
     PRV=Number(0,-1)       NEW=Number(0,-1)       
 DOUBLE         p=  38 s=   0 signed  | DB=BigNumber(38,-1)   
VMB=BigNumber(38,-1)   PRV=BigNumber(38,-1)   NEW=BigNumber(38,-1)   
-DOUBLE         p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-DOUBLE         p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+DOUBLE         p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+DOUBLE         p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 DOUBLE         p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 DOUBLE         p= 126 s=   0 signed  | DB=Number(-1,-1)      VMB=Number(-1,-1) 
     PRV=Number(-1,-1)      NEW=Number(-1,-1)      
 FLOAT          p=   0 s=   0 signed  | DB=Number(0,-1)       VMB=Number(0,-1)  
     PRV=Number(0,-1)       NEW=Number(0,-1)       
 FLOAT          p=  38 s=   0 signed  | DB=BigNumber(38,-1)   
VMB=BigNumber(38,-1)   PRV=BigNumber(38,-1)   NEW=BigNumber(38,-1)   
-FLOAT          p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-FLOAT          p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+FLOAT          p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+FLOAT          p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 FLOAT          p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 FLOAT          p= 126 s=   0 signed  | DB=Number(-1,-1)      VMB=Number(-1,-1) 
     PRV=Number(-1,-1)      NEW=Number(-1,-1)      
 REAL           p=   0 s=   0 signed  | DB=Number(0,-1)       VMB=Number(0,-1)  
     PRV=Number(0,-1)       NEW=Number(0,-1)       
 REAL           p=  38 s=   0 signed  | DB=BigNumber(38,-1)   
VMB=BigNumber(38,-1)   PRV=BigNumber(38,-1)   NEW=BigNumber(38,-1)   
-REAL           p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-REAL           p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+REAL           p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+REAL           p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 REAL           p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 REAL           p= 126 s=   0 signed  | DB=Number(-1,-1)      VMB=Number(-1,-1) 
     PRV=Number(-1,-1)      NEW=Number(-1,-1)      
 TIMESTAMP      p=   0 s=   0 signed  | DB=Timestamp(0,-1)    
VMB=Timestamp(0,-1)    PRV=Timestamp(0,-1)    NEW=Timestamp(0,-1)    
@@ -651,32 +651,32 @@ TINYINT        p=  16 s=  16 signed  | DB=Integer(2,0)    
   VMB=Integer(2,0)
 TINYINT        p= 126 s=   0 signed  | DB=Integer(2,0)       VMB=Integer(2,0)  
     PRV=Integer(2,0)       NEW=Integer(2,0)       
 DECIMAL        p=   0 s=   0 signed  | DB=Number(0,0)        VMB=Number(0,0)   
     PRV=Number(0,0)        NEW=Number(0,0)        
 DECIMAL        p=  38 s=   0 signed  | DB=BigNumber(38,0)    
VMB=BigNumber(38,0)    PRV=BigNumber(38,0)    NEW=BigNumber(38,0)    
-DECIMAL        p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-DECIMAL        p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+DECIMAL        p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+DECIMAL        p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 DECIMAL        p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 DECIMAL        p= 126 s=   0 signed  | DB=Number(-1,0)       VMB=Number(-1,0)  
     PRV=Number(-1,0)       NEW=Number(-1,0)       
 NUMERIC        p=   0 s=   0 signed  | DB=Number(0,0)        VMB=Number(0,0)   
     PRV=Number(0,0)        NEW=Number(0,0)        
 NUMERIC        p=  38 s=   0 signed  | DB=BigNumber(38,0)    
VMB=BigNumber(38,0)    PRV=BigNumber(38,0)    NEW=BigNumber(38,0)    
-NUMERIC        p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-NUMERIC        p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+NUMERIC        p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+NUMERIC        p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 NUMERIC        p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 NUMERIC        p= 126 s=   0 signed  | DB=Number(-1,0)       VMB=Number(-1,0)  
     PRV=Number(-1,0)       NEW=Number(-1,0)       
 DOUBLE         p=   0 s=   0 signed  | DB=Number(0,-1)       VMB=Number(0,-1)  
     PRV=Number(0,-1)       NEW=Number(0,-1)       
 DOUBLE         p=  38 s=   0 signed  | DB=BigNumber(38,-1)   
VMB=BigNumber(38,-1)   PRV=BigNumber(38,-1)   NEW=BigNumber(38,-1)   
-DOUBLE         p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-DOUBLE         p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+DOUBLE         p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+DOUBLE         p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 DOUBLE         p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 DOUBLE         p= 126 s=   0 signed  | DB=Number(-1,-1)      VMB=Number(-1,-1) 
     PRV=Number(-1,-1)      NEW=Number(-1,-1)      
 FLOAT          p=   0 s=   0 signed  | DB=Number(0,-1)       VMB=Number(0,-1)  
     PRV=Number(0,-1)       NEW=Number(0,-1)       
 FLOAT          p=  38 s=   0 signed  | DB=BigNumber(38,-1)   
VMB=BigNumber(38,-1)   PRV=BigNumber(38,-1)   NEW=BigNumber(38,-1)   
-FLOAT          p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-FLOAT          p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+FLOAT          p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+FLOAT          p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 FLOAT          p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 FLOAT          p= 126 s=   0 signed  | DB=Number(-1,-1)      VMB=Number(-1,-1) 
     PRV=Number(-1,-1)      NEW=Number(-1,-1)      
 REAL           p=   0 s=   0 signed  | DB=Number(0,-1)       VMB=Number(0,-1)  
     PRV=Number(0,-1)       NEW=Number(0,-1)       
 REAL           p=  38 s=   0 signed  | DB=BigNumber(38,-1)   
VMB=BigNumber(38,-1)   PRV=BigNumber(38,-1)   NEW=BigNumber(38,-1)   
-REAL           p=  10 s=   2 signed  | DB=Number(8,2)        VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(8,2)        DIVERGE
-REAL           p=  20 s=  10 signed  | DB=Number(10,10)      
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=Number(10,10)      DIVERGE
+REAL           p=  10 s=   2 signed  | DB=Number(10,2)       VMB=Number(10,2)  
     PRV=Number(10,2)       NEW=Number(10,2)       
+REAL           p=  20 s=  10 signed  | DB=BigNumber(20,10)   
VMB=BigNumber(20,10)   PRV=BigNumber(20,10)   NEW=BigNumber(20,10)   
 REAL           p=  16 s=  16 signed  | DB=BigNumber(16,16)   
VMB=BigNumber(16,16)   PRV=BigNumber(16,16)   NEW=BigNumber(16,16)   
 REAL           p= 126 s=   0 signed  | DB=Number(-1,-1)      VMB=Number(-1,-1) 
     PRV=Number(-1,-1)      NEW=Number(-1,-1)      
 TIMESTAMP      p=   0 s=   0 signed  | DB=Date(-1,-1)        VMB=None(-1,-1)   
     PRV=None(-1,-1)        NEW=Date(-1,-1)        DIVERGE
@@ -748,4 +748,4 @@ STRUCT         p= 126 s=   0 signed  | DB=String(-1,-1)     
 VMB=String(-1,-1)
 BIGINT         p=  20 s=   0 unsigned | DB=BigNumber(16,0)    
VMB=BigNumber(16,0)    PRV=Integer(15,0)      NEW=BigNumber(16,0)    DIVERGE
 YEAR           p=   4 s=   0 signed  | DB=Date(-1,-1)        VMB=Date(-1,-1)   
     PRV=Date(-1,-1)        NEW=Date(-1,-1)        
 
-# diverging cases: 62
+# diverging cases: 15
diff --git a/docs/hop-dev-manual/modules/ROOT/pages/database/column-types.adoc 
b/docs/hop-dev-manual/modules/ROOT/pages/database/column-types.adoc
index ed14dbc521..f58e763d2e 100644
--- a/docs/hop-dev-manual/modules/ROOT/pages/database/column-types.adoc
+++ b/docs/hop-dev-manual/modules/ROOT/pages/database/column-types.adoc
@@ -45,10 +45,14 @@ Hop provides the plain JDBC behaviour; a dialect declares 
the parts it does diff
 
 Two of these carry a meaning that is easy to get wrong.
 
-`length` on a numeric type is the number of digits *before* the decimal, not 
the total.
-A database reports the total, so the scale has to come off.
-`NUMERIC(10,2)` is length 8, precision 2, and is written back as `NUMERIC(8+2, 
2)`.
-Getting this backwards makes a column grow by its scale on every round trip.
+`length` on a numeric type is the *total* number of significant digits, the 
same value the database reports as precision.
+`NUMERIC(10,2)` is length 10, precision 2, and is written back as `NUMERIC(10, 
2)` (or `DECIMAL(10,2)`).
+`numericLength()` on a `DatabaseColumn` is the digits before the decimal (8 
for that example) and is only for dialect rules that test whether a declaration 
is possible.
+
+PostgreSQL used to emit `NUMERIC(length+precision, precision)` when Hop length 
still meant integer digits.
+From 2.20 it emits `NUMERIC(length, precision)`.
+A hand-authored Number field of length 10, precision 3 therefore generates 
`NUMERIC(10, 3)` rather than `NUMERIC(13, 3)`.
+If scale is larger than length, precision is widened so PostgreSQL before 15 
does not reject the statement.
 
 `precision` of exactly 1 on a `TYPE_DATE` is a marker meaning the value is a 
date rather than a timestamp.
 It is not a digit count.
@@ -234,7 +238,8 @@ What survives when no rule claims a column is 
`StandardJdbcTypeMapper`, which do
 
 It exposes a few helpers that rules can use so that a condition does not have 
to restate arithmetic the mapping already does:
 
-* `numericLength(column)` — digits before the decimal, after the scale has 
been taken off
+* `numericLength(column)` — digits before the decimal, after the scale has 
been taken off. Dialect rules use this to test whether a declaration is 
possible at all.
+* `totalDigits(column)` — the total number of significant digits (JDBC 
precision). This is what Hop stores as Number/BigNumber length and what 
`DECIMAL(length, precision)` writers emit.
 * `numericScale(column)` — digits after the decimal
 * `displaySizeIsTwiceThePrecision(databaseMeta, column)` — the `CHAR(X) FOR 
BIT DATA` shape
 
diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/database/databases/postgresql.adoc 
b/docs/hop-user-manual/modules/ROOT/pages/database/databases/postgresql.adoc
index fe93bf3f99..f7b0bc3b04 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/database/databases/postgresql.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/database/databases/postgresql.adoc
@@ -30,3 +30,11 @@ under the License.
 |Documentation | 
https://jdbc.postgresql.org/documentation/head/index.html[Documentation Link]
 |JDBC Url  | jdbc:postgresql://host:port/database
 |===
+
+== Numeric DDL in 2.20
+
+Hop Number/BigNumber *length* is the total number of significant digits (the 
database precision), not the digits before the decimal.
+
+Table Output and similar DDL therefore emit `NUMERIC(length, scale)`.
+A field authored as length 10, precision 3 generates `NUMERIC(10, 3)`.
+In 2.19 and earlier the PostgreSQL dialect added length and scale and emitted 
`NUMERIC(13, 3)` for that same metadata.
diff --git 
a/plugins/databases/cockroachdb/src/test/resources/org/apache/hop/databases/cockroachdb/CockroachFieldDefinitionGoldenTest.txt
 
b/plugins/databases/cockroachdb/src/test/resources/org/apache/hop/databases/cockroachdb/CockroachFieldDefinitionGoldenTest.txt
index 98d949d912..4742377a28 100644
--- 
a/plugins/databases/cockroachdb/src/test/resources/org/apache/hop/databases/cockroachdb/CockroachFieldDefinitionGoldenTest.txt
+++ 
b/plugins/databases/cockroachdb/src/test/resources/org/apache/hop/databases/cockroachdb/CockroachFieldDefinitionGoldenTest.txt
@@ -25,7 +25,7 @@ NUMBER       len=      -1 prec=  -1 -        -> DOUBLE 
PRECISION
 NUMBER       len=       0 prec=   0 -        -> DOUBLE PRECISION
 NUMBER       len=       4 prec=   0 -        -> SMALLINT
 NUMBER       len=       9 prec=   0 -        -> INTEGER
-NUMBER       len=      10 prec=   2 -        -> NUMERIC(12, 2)
+NUMBER       len=      10 prec=   2 -        -> NUMERIC(10, 2)
 NUMBER       len=      15 prec=   0 -        -> BIGINT
 NUMBER       len=      18 prec=   0 -        -> BIGINT
 NUMBER       len=      20 prec=   0 -        -> NUMERIC(20, 0)
@@ -34,7 +34,7 @@ BIGNUMBER    len=      -1 prec=  -1 -        -> DOUBLE 
PRECISION
 BIGNUMBER    len=       0 prec=   0 -        -> DOUBLE PRECISION
 BIGNUMBER    len=       4 prec=   0 -        -> SMALLINT
 BIGNUMBER    len=       9 prec=   0 -        -> INTEGER
-BIGNUMBER    len=      10 prec=   2 -        -> NUMERIC(12, 2)
+BIGNUMBER    len=      10 prec=   2 -        -> NUMERIC(10, 2)
 BIGNUMBER    len=      15 prec=   0 -        -> BIGINT
 BIGNUMBER    len=      18 prec=   0 -        -> BIGINT
 BIGNUMBER    len=      20 prec=   0 -        -> NUMERIC(20, 0)
@@ -232,14 +232,14 @@ INTEGER      len=      15 prec=   0 add      -> ALTER 
TABLE TBL ADD COLUMN COL B
 INTEGER      len=      15 prec=   0 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL BIGINT; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; 
ALTER TABLE TBL RENAME COL_KTL TO COL;
 NUMBER       len=      -1 prec=  -1 add      -> ALTER TABLE TBL ADD COLUMN COL 
DOUBLE PRECISION
 NUMBER       len=      -1 prec=  -1 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL DOUBLE PRECISION; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP 
COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
-NUMBER       len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COLUMN COL 
NUMERIC(12, 2)
-NUMBER       len=      10 prec=   2 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL NUMERIC(12, 2); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN 
COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+NUMBER       len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COLUMN COL 
NUMERIC(10, 2)
+NUMBER       len=      10 prec=   2 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL NUMERIC(10, 2); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN 
COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
 NUMBER       len=      15 prec=   0 add      -> ALTER TABLE TBL ADD COLUMN COL 
BIGINT
 NUMBER       len=      15 prec=   0 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL BIGINT; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; 
ALTER TABLE TBL RENAME COL_KTL TO COL;
 BIGNUMBER    len=      -1 prec=  -1 add      -> ALTER TABLE TBL ADD COLUMN COL 
DOUBLE PRECISION
 BIGNUMBER    len=      -1 prec=  -1 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL DOUBLE PRECISION; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP 
COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
-BIGNUMBER    len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COLUMN COL 
NUMERIC(12, 2)
-BIGNUMBER    len=      10 prec=   2 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL NUMERIC(12, 2); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN 
COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+BIGNUMBER    len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COLUMN COL 
NUMERIC(10, 2)
+BIGNUMBER    len=      10 prec=   2 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL NUMERIC(10, 2); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN 
COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
 BIGNUMBER    len=      15 prec=   0 add      -> ALTER TABLE TBL ADD COLUMN COL 
BIGINT
 BIGNUMBER    len=      15 prec=   0 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL BIGINT; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; 
ALTER TABLE TBL RENAME COL_KTL TO COL;
 DATE         len=      -1 prec=  -1 add      -> ALTER TABLE TBL ADD COLUMN COL 
TIMESTAMP
diff --git 
a/plugins/databases/greenplum/src/test/resources/org/apache/hop/databases/greenplum/GreenplumFieldDefinitionGoldenTest.txt
 
b/plugins/databases/greenplum/src/test/resources/org/apache/hop/databases/greenplum/GreenplumFieldDefinitionGoldenTest.txt
index 79c1b4c2a9..0ad0d9d7d3 100644
--- 
a/plugins/databases/greenplum/src/test/resources/org/apache/hop/databases/greenplum/GreenplumFieldDefinitionGoldenTest.txt
+++ 
b/plugins/databases/greenplum/src/test/resources/org/apache/hop/databases/greenplum/GreenplumFieldDefinitionGoldenTest.txt
@@ -25,7 +25,7 @@ NUMBER       len=      -1 prec=  -1 -        -> DOUBLE 
PRECISION
 NUMBER       len=       0 prec=   0 -        -> DOUBLE PRECISION
 NUMBER       len=       4 prec=   0 -        -> SMALLINT
 NUMBER       len=       9 prec=   0 -        -> INTEGER
-NUMBER       len=      10 prec=   2 -        -> NUMERIC(12, 2)
+NUMBER       len=      10 prec=   2 -        -> NUMERIC(10, 2)
 NUMBER       len=      15 prec=   0 -        -> BIGINT
 NUMBER       len=      18 prec=   0 -        -> BIGINT
 NUMBER       len=      20 prec=   0 -        -> NUMERIC(20, 0)
@@ -34,7 +34,7 @@ BIGNUMBER    len=      -1 prec=  -1 -        -> DOUBLE 
PRECISION
 BIGNUMBER    len=       0 prec=   0 -        -> DOUBLE PRECISION
 BIGNUMBER    len=       4 prec=   0 -        -> SMALLINT
 BIGNUMBER    len=       9 prec=   0 -        -> INTEGER
-BIGNUMBER    len=      10 prec=   2 -        -> NUMERIC(12, 2)
+BIGNUMBER    len=      10 prec=   2 -        -> NUMERIC(10, 2)
 BIGNUMBER    len=      15 prec=   0 -        -> BIGINT
 BIGNUMBER    len=      18 prec=   0 -        -> BIGINT
 BIGNUMBER    len=      20 prec=   0 -        -> NUMERIC(20, 0)
@@ -232,14 +232,14 @@ INTEGER      len=      15 prec=   0 add      -> ALTER 
TABLE TBL ADD COLUMN COL B
 INTEGER      len=      15 prec=   0 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL BIGINT; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; 
ALTER TABLE TBL RENAME COL_KTL TO COL;
 NUMBER       len=      -1 prec=  -1 add      -> ALTER TABLE TBL ADD COLUMN COL 
DOUBLE PRECISION
 NUMBER       len=      -1 prec=  -1 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL DOUBLE PRECISION; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP 
COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
-NUMBER       len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COLUMN COL 
NUMERIC(12, 2)
-NUMBER       len=      10 prec=   2 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL NUMERIC(12, 2); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN 
COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+NUMBER       len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COLUMN COL 
NUMERIC(10, 2)
+NUMBER       len=      10 prec=   2 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL NUMERIC(10, 2); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN 
COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
 NUMBER       len=      15 prec=   0 add      -> ALTER TABLE TBL ADD COLUMN COL 
BIGINT
 NUMBER       len=      15 prec=   0 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL BIGINT; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; 
ALTER TABLE TBL RENAME COL_KTL TO COL;
 BIGNUMBER    len=      -1 prec=  -1 add      -> ALTER TABLE TBL ADD COLUMN COL 
DOUBLE PRECISION
 BIGNUMBER    len=      -1 prec=  -1 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL DOUBLE PRECISION; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP 
COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
-BIGNUMBER    len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COLUMN COL 
NUMERIC(12, 2)
-BIGNUMBER    len=      10 prec=   2 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL NUMERIC(12, 2); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN 
COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+BIGNUMBER    len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COLUMN COL 
NUMERIC(10, 2)
+BIGNUMBER    len=      10 prec=   2 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL NUMERIC(10, 2); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN 
COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
 BIGNUMBER    len=      15 prec=   0 add      -> ALTER TABLE TBL ADD COLUMN COL 
BIGINT
 BIGNUMBER    len=      15 prec=   0 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL BIGINT; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; 
ALTER TABLE TBL RENAME COL_KTL TO COL;
 DATE         len=      -1 prec=  -1 add      -> ALTER TABLE TBL ADD COLUMN COL 
TIMESTAMP
diff --git 
a/plugins/databases/mssql/src/main/java/org/apache/hop/databases/mssql/MsSqlServerDatabaseMeta.java
 
b/plugins/databases/mssql/src/main/java/org/apache/hop/databases/mssql/MsSqlServerDatabaseMeta.java
index b017dd5a15..a4b627dabe 100644
--- 
a/plugins/databases/mssql/src/main/java/org/apache/hop/databases/mssql/MsSqlServerDatabaseMeta.java
+++ 
b/plugins/databases/mssql/src/main/java/org/apache/hop/databases/mssql/MsSqlServerDatabaseMeta.java
@@ -18,7 +18,9 @@
 package org.apache.hop.databases.mssql;
 
 import java.sql.ResultSet;
+import java.sql.Types;
 import java.util.List;
+import java.util.Locale;
 import org.apache.hop.core.Const;
 import org.apache.hop.core.database.BaseDatabaseMeta;
 import org.apache.hop.core.database.Database;
@@ -47,6 +49,9 @@ import org.apache.hop.metadata.api.HopMetadataProperty;
 @GuiPlugin(id = "GUI-MSSQLServerDatabaseMeta")
 public class MsSqlServerDatabaseMeta extends BaseDatabaseMeta implements 
IDatabase {
 
+  /** SQL Server NVARCHAR(n) / NCHAR(n) stop at 4000 UTF-16 code units. */
+  private static final int MAX_NVARCHAR_LENGTH = 4000;
+
   private static final List<IDatabaseTypeRule> TYPE_RULES =
       DatabaseTypes.rules()
           // SQL Server spells a UUID UNIQUEIDENTIFIER, and grew a JSON type 
in 2025: a server
@@ -56,6 +61,10 @@ public class MsSqlServerDatabaseMeta extends 
BaseDatabaseMeta implements IDataba
           .as("UNIQUEIDENTIFIER")
           .write(IValueMeta.TYPE_JSON)
           .as("JSON")
+          .write(IValueMeta.TYPE_DATE, IValueMeta.TYPE_TIMESTAMP)
+          .as(MsSqlServerDatabaseMeta::dateTimeColumnType)
+          .write(IValueMeta.TYPE_STRING)
+          .as(MsSqlServerDatabaseMeta::stringColumnType)
           .build();
 
   @Override
@@ -63,6 +72,71 @@ public class MsSqlServerDatabaseMeta extends 
BaseDatabaseMeta implements IDataba
     return TYPE_RULES;
   }
 
+  /**
+   * SQL Server date/time spelling. Preserve DATE/TIME/DATETIME2 when the 
original JDBC type is
+   * known; new Hop Date fields stay DATETIME, new Timestamp fields become 
DATETIME2.
+   */
+  static String dateTimeColumnType(IValueMeta valueMeta) {
+    // Hop type first: a DATE column the user converted to Timestamp must keep 
the time.
+    if (valueMeta.getType() == IValueMeta.TYPE_TIMESTAMP
+        || 
"datetime2".equalsIgnoreCase(valueMeta.getOriginalColumnTypeName())) {
+      return "DATETIME2";
+    }
+    int original = valueMeta.getOriginalColumnType();
+    if (original == Types.DATE) {
+      return "DATE";
+    }
+    if (original == Types.TIME) {
+      return "TIME";
+    }
+    return "DATETIME";
+  }
+
+  /**
+   * SQL Server string spelling. National-character source columns stay 
NVARCHAR/NCHAR; long strings
+   * use VARCHAR(MAX) rather than the deprecated TEXT type.
+   */
+  static String stringColumnType(IValueMeta valueMeta) {
+    int length = valueMeta.getLength();
+    if (isNationalString(valueMeta)) {
+      if (valueMeta.getOriginalColumnType() == Types.NCHAR
+          && length > 0
+          && length <= MAX_NVARCHAR_LENGTH) {
+        return "NCHAR(" + length + ")";
+      }
+      if (length > 0 && length <= MAX_NVARCHAR_LENGTH) {
+        return "NVARCHAR(" + length + ")";
+      }
+      if (length <= 0) {
+        return "NVARCHAR(100)";
+      }
+      return "NVARCHAR(MAX)";
+    }
+    if (length > 0 && length < 8000) {
+      return "VARCHAR(" + length + ")";
+    }
+    if (length <= 0) {
+      return "VARCHAR(100)";
+    }
+    return "VARCHAR(MAX)";
+  }
+
+  private static boolean isNationalString(IValueMeta valueMeta) {
+    int original = valueMeta.getOriginalColumnType();
+    if (original == Types.NCHAR
+        || original == Types.NVARCHAR
+        || original == Types.LONGNVARCHAR
+        || original == Types.NCLOB) {
+      return true;
+    }
+    String typeName = valueMeta.getOriginalColumnTypeName();
+    if (typeName == null) {
+      return false;
+    }
+    String upper = typeName.toUpperCase(Locale.ROOT);
+    return upper.startsWith("NCHAR") || upper.startsWith("NVARCHAR") || 
upper.startsWith("NTEXT");
+  }
+
   /** SQL Server 2025, which is major version 17, is the first with a JSON 
type. */
   private static final int FIRST_VERSION_WITH_JSON = 17;
 
@@ -331,7 +405,7 @@ public class MsSqlServerDatabaseMeta extends 
BaseDatabaseMeta implements IDataba
     int type = v.getType();
     switch (type) {
       case IValueMeta.TYPE_TIMESTAMP, IValueMeta.TYPE_DATE:
-        retval += "DATETIME";
+        retval += dateTimeColumnType(v);
         break;
       case IValueMeta.TYPE_BOOLEAN:
         if (isSupportsBooleanDataType()) {
@@ -371,16 +445,7 @@ public class MsSqlServerDatabaseMeta extends 
BaseDatabaseMeta implements IDataba
         }
         break;
       case IValueMeta.TYPE_STRING:
-        if (length < getMaxVARCHARLength()) {
-          // Maybe use some default DB String length in case length<=0
-          if (length > 0) {
-            retval += "VARCHAR(" + length + ")";
-          } else {
-            retval += "VARCHAR(100)";
-          }
-        } else {
-          retval += "TEXT"; // Up to 2bilion characters.
-        }
+        retval += stringColumnType(v);
         break;
       case IValueMeta.TYPE_BINARY:
         retval += "VARBINARY(MAX)";
diff --git 
a/plugins/databases/mssql/src/test/java/org/apache/hop/databases/mssql/MsSqlServerDatabaseMetaTest.java
 
b/plugins/databases/mssql/src/test/java/org/apache/hop/databases/mssql/MsSqlServerDatabaseMetaTest.java
index e803c83688..313a26c7fd 100644
--- 
a/plugins/databases/mssql/src/test/java/org/apache/hop/databases/mssql/MsSqlServerDatabaseMetaTest.java
+++ 
b/plugins/databases/mssql/src/test/java/org/apache/hop/databases/mssql/MsSqlServerDatabaseMetaTest.java
@@ -501,7 +501,7 @@ class MsSqlServerDatabaseMetaTest {
         "ALTER TABLE FOO ADD BAR DATETIME",
         nativeMeta.getAddColumnStatement("FOO", new ValueMetaDate("BAR"), "", 
false, "", false));
     assertEquals(
-        "ALTER TABLE FOO ADD BAR DATETIME",
+        "ALTER TABLE FOO ADD BAR DATETIME2",
         nativeMeta.getAddColumnStatement(
             "FOO", new ValueMetaTimestamp("BAR"), "", false, "", false));
 
@@ -584,7 +584,7 @@ class MsSqlServerDatabaseMetaTest {
             new ValueMetaBigNumber("BAR", 21, 4), "", "", false, false, 
false));
 
     assertEquals(
-        "TEXT",
+        "VARCHAR(MAX)",
         nativeMeta.getFieldDefinition(
             new ValueMetaString("BAR", nativeMeta.getMaxVARCHARLength() + 2, 
0),
             "",
diff --git 
a/plugins/databases/mssql/src/test/java/org/apache/hop/databases/mssql/MsSqlServerDatabaseMeta_FieldDefinitionTest.java
 
b/plugins/databases/mssql/src/test/java/org/apache/hop/databases/mssql/MsSqlServerDatabaseMeta_FieldDefinitionTest.java
index f0f7d8a90a..93c05a5468 100644
--- 
a/plugins/databases/mssql/src/test/java/org/apache/hop/databases/mssql/MsSqlServerDatabaseMeta_FieldDefinitionTest.java
+++ 
b/plugins/databases/mssql/src/test/java/org/apache/hop/databases/mssql/MsSqlServerDatabaseMeta_FieldDefinitionTest.java
@@ -37,7 +37,6 @@ class MsSqlServerDatabaseMeta_FieldDefinitionTest {
   private static final String STRING_DECIMAL = "DECIMAL";
   private static final String STRING_FLOAT = "FLOAT";
   private static final String STRING_VARCHAR = "VARCHAR";
-  private static final String STRING_TEXT = "TEXT";
 
   @BeforeEach
   void init() {
@@ -128,7 +127,7 @@ class MsSqlServerDatabaseMeta_FieldDefinitionTest {
     IValueMeta valueMeta = new 
MetaInterfaceBuilder(IValueMeta.TYPE_STRING).length(10_000).build();
 
     assertEquals(
-        STRING_TEXT, dbMeta.getFieldDefinition(valueMeta, null, null, false, 
false, false));
+        "VARCHAR(MAX)", dbMeta.getFieldDefinition(valueMeta, null, null, 
false, false, false));
   }
 
   private static class MetaInterfaceBuilder {
diff --git 
a/plugins/databases/mssql/src/test/java/org/apache/hop/databases/mssql/MsSqlServerTypeRulesTest.java
 
b/plugins/databases/mssql/src/test/java/org/apache/hop/databases/mssql/MsSqlServerTypeRulesTest.java
new file mode 100644
index 0000000000..cb2db281bc
--- /dev/null
+++ 
b/plugins/databases/mssql/src/test/java/org/apache/hop/databases/mssql/MsSqlServerTypeRulesTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.databases.mssql;
+
+import static org.apache.hop.junit.database.TypeRuleFixture.column;
+import static org.apache.hop.junit.database.TypeRuleFixture.meta;
+import static org.apache.hop.junit.database.TypeRuleFixture.numericColumn;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.sql.Types;
+import org.apache.hop.core.HopClientEnvironment;
+import org.apache.hop.core.database.types.ColumnContext;
+import org.apache.hop.core.database.types.DatabaseTypeMapper;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.value.ValueMetaDate;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.core.row.value.ValueMetaTimestamp;
+import org.apache.hop.core.variables.Variables;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+/** SQL Server write rules for date/time and Unicode string round-trips. */
+class MsSqlServerTypeRulesTest {
+
+  @BeforeAll
+  static void setUpClass() throws Exception {
+    HopClientEnvironment.init();
+  }
+
+  @Test
+  void dateColumnsStayDate() throws Exception {
+    assertEquals("DATE", write(column(Types.DATE, "date", 0, 10)));
+  }
+
+  @Test
+  void timeColumnsStayTime() throws Exception {
+    assertEquals("TIME", write(column(Types.TIME, "time", 0, 16)));
+  }
+
+  @Test
+  void datetime2IsPreserved() throws Exception {
+    IValueMeta valueMeta = new ValueMetaTimestamp("COL");
+    valueMeta.setOriginalColumnTypeName("datetime2");
+    assertEquals("DATETIME2", write(valueMeta));
+  }
+
+  @Test
+  void aPlainDateStillWritesDatetime() {
+    assertEquals("DATETIME", write(new ValueMetaDate("COL")));
+  }
+
+  @Test
+  void aPlainTimestampWritesDatetime2() {
+    assertEquals("DATETIME2", write(new ValueMetaTimestamp("COL")));
+  }
+
+  @Test
+  void aTimestampConvertedFromDateKeepsTheTime() {
+    IValueMeta valueMeta = new ValueMetaTimestamp("COL");
+    valueMeta.setOriginalColumnType(Types.DATE);
+    assertEquals("DATETIME2", write(valueMeta));
+  }
+
+  @Test
+  void nvarcharRoundTrips() throws Exception {
+    assertEquals("NVARCHAR(20)", write(column(Types.NVARCHAR, "nvarchar", 20, 
20)));
+  }
+
+  @Test
+  void ncharRoundTrips() throws Exception {
+    assertEquals("NCHAR(10)", write(column(Types.NCHAR, "nchar", 10, 10)));
+  }
+
+  @Test
+  void longNationalStringsUseNvarcharMax() throws Exception {
+    assertEquals("NVARCHAR(MAX)", write(column(Types.NVARCHAR, "nvarchar", 
8000, 8000)));
+  }
+
+  @Test
+  void longAnsiStringsUseVarcharMax() {
+    IValueMeta valueMeta = new ValueMetaString("COL", 10_000, 0);
+    assertEquals("VARCHAR(MAX)", write(valueMeta));
+  }
+
+  @Test
+  void aPlainStringStillWritesVarchar() {
+    IValueMeta valueMeta = new ValueMetaString("COL", 50, 0);
+    assertEquals("VARCHAR(50)", write(valueMeta));
+  }
+
+  @Test
+  void aScaledDecimalRoundTripsAsTheSameDeclaration() throws Exception {
+    assertEquals("DECIMAL(5,2)", numericRoundTrip(5, 2));
+    assertEquals("DECIMAL(24,15)", numericRoundTrip(24, 15));
+    assertEquals("DECIMAL(16,1)", numericRoundTrip(16, 1));
+  }
+
+  private static String numericRoundTrip(int precision, int scale) throws 
Exception {
+    MsSqlServerDatabaseMeta dialect = new MsSqlServerDatabaseMeta();
+    IValueMeta valueMeta =
+        DatabaseTypeMapper.getValueMeta(
+            new Variables(),
+            meta(dialect),
+            numericColumn(Types.DECIMAL, "decimal", precision, scale),
+            false,
+            false);
+    return dialect.getFieldDefinition(valueMeta, null, null, false, false, 
false);
+  }
+
+  private static String 
write(org.apache.hop.core.database.types.DatabaseColumn column)
+      throws Exception {
+    MsSqlServerDatabaseMeta dialect = new MsSqlServerDatabaseMeta();
+    IValueMeta valueMeta =
+        DatabaseTypeMapper.getValueMeta(new Variables(), meta(dialect), 
column, false, false);
+    return write(valueMeta);
+  }
+
+  private static String write(IValueMeta valueMeta) {
+    return new MsSqlServerDatabaseMeta()
+        .getColumnDefinition(
+            valueMeta, null, null, false, false, false, 
ColumnContext.Purpose.CREATE);
+  }
+}
diff --git 
a/plugins/databases/mssql/src/test/resources/org/apache/hop/databases/mssql/MsSqlServerFieldDefinitionGoldenTest.txt
 
b/plugins/databases/mssql/src/test/resources/org/apache/hop/databases/mssql/MsSqlServerFieldDefinitionGoldenTest.txt
index 3112acb5f2..93d1e112af 100644
--- 
a/plugins/databases/mssql/src/test/resources/org/apache/hop/databases/mssql/MsSqlServerFieldDefinitionGoldenTest.txt
+++ 
b/plugins/databases/mssql/src/test/resources/org/apache/hop/databases/mssql/MsSqlServerFieldDefinitionGoldenTest.txt
@@ -11,7 +11,7 @@ STRING       len=      10 prec=   2 -        -> VARCHAR(10)
 STRING       len=      15 prec=   0 -        -> VARCHAR(15)
 STRING       len=      18 prec=   0 -        -> VARCHAR(18)
 STRING       len=      20 prec=   0 -        -> VARCHAR(20)
-STRING       len= 9999999 prec=   0 -        -> TEXT
+STRING       len= 9999999 prec=   0 -        -> VARCHAR(MAX)
 INTEGER      len=      -1 prec=  -1 -        -> INT
 INTEGER      len=       0 prec=   0 -        -> INT
 INTEGER      len=       4 prec=   0 -        -> INT
@@ -48,15 +48,15 @@ DATE         len=      15 prec=   0 -        -> DATETIME
 DATE         len=      18 prec=   0 -        -> DATETIME
 DATE         len=      20 prec=   0 -        -> DATETIME
 DATE         len= 9999999 prec=   0 -        -> DATETIME
-TIMESTAMP    len=      -1 prec=  -1 -        -> DATETIME
-TIMESTAMP    len=       0 prec=   0 -        -> DATETIME
-TIMESTAMP    len=       4 prec=   0 -        -> DATETIME
-TIMESTAMP    len=       9 prec=   0 -        -> DATETIME
-TIMESTAMP    len=      10 prec=   2 -        -> DATETIME
-TIMESTAMP    len=      15 prec=   0 -        -> DATETIME
-TIMESTAMP    len=      18 prec=   0 -        -> DATETIME
-TIMESTAMP    len=      20 prec=   0 -        -> DATETIME
-TIMESTAMP    len= 9999999 prec=   0 -        -> DATETIME
+TIMESTAMP    len=      -1 prec=  -1 -        -> DATETIME2
+TIMESTAMP    len=       0 prec=   0 -        -> DATETIME2
+TIMESTAMP    len=       4 prec=   0 -        -> DATETIME2
+TIMESTAMP    len=       9 prec=   0 -        -> DATETIME2
+TIMESTAMP    len=      10 prec=   2 -        -> DATETIME2
+TIMESTAMP    len=      15 prec=   0 -        -> DATETIME2
+TIMESTAMP    len=      18 prec=   0 -        -> DATETIME2
+TIMESTAMP    len=      20 prec=   0 -        -> DATETIME2
+TIMESTAMP    len= 9999999 prec=   0 -        -> DATETIME2
 BOOLEAN      len=      -1 prec=  -1 -        -> CHAR(1)
 BOOLEAN      len=       0 prec=   0 -        -> CHAR(1)
 BOOLEAN      len=       4 prec=   0 -        -> CHAR(1)
@@ -105,34 +105,34 @@ STRING       len=    3999 prec=   0 -        -> 
VARCHAR(3999)
 STRING       len=    4000 prec=   0 -        -> VARCHAR(4000)
 STRING       len=    4001 prec=   0 -        -> VARCHAR(4001)
 STRING       len=    7999 prec=   0 -        -> VARCHAR(7999)
-STRING       len=    8000 prec=   0 -        -> TEXT
-STRING       len=    8001 prec=   0 -        -> TEXT
-STRING       len=   21843 prec=   0 -        -> TEXT
-STRING       len=   21844 prec=   0 -        -> TEXT
-STRING       len=   32663 prec=   0 -        -> TEXT
-STRING       len=   32664 prec=   0 -        -> TEXT
-STRING       len=   32671 prec=   0 -        -> TEXT
-STRING       len=   32672 prec=   0 -        -> TEXT
-STRING       len=   32699 prec=   0 -        -> TEXT
-STRING       len=   32700 prec=   0 -        -> TEXT
-STRING       len=   32719 prec=   0 -        -> TEXT
-STRING       len=   32720 prec=   0 -        -> TEXT
-STRING       len=   32766 prec=   0 -        -> TEXT
-STRING       len=   32767 prec=   0 -        -> TEXT
-STRING       len=   32768 prec=   0 -        -> TEXT
-STRING       len=   65532 prec=   0 -        -> TEXT
-STRING       len=   65533 prec=   0 -        -> TEXT
-STRING       len=   65534 prec=   0 -        -> TEXT
-STRING       len=   65535 prec=   0 -        -> TEXT
-STRING       len=   65536 prec=   0 -        -> TEXT
-STRING       len= 1999999 prec=   0 -        -> TEXT
-STRING       len= 2000000 prec=   0 -        -> TEXT
-STRING       len= 2000001 prec=   0 -        -> TEXT
-STRING       len= 9999998 prec=   0 -        -> TEXT
-STRING       len= 9999999 prec=   0 -        -> TEXT
-STRING       len=10000000 prec=   0 -        -> TEXT
-STRING       len=16777215 prec=   0 -        -> TEXT
-STRING       len=16777216 prec=   0 -        -> TEXT
+STRING       len=    8000 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=    8001 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   21843 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   21844 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32663 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32664 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32671 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32672 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32699 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32700 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32719 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32720 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32766 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32767 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32768 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   65532 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   65533 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   65534 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   65535 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   65536 prec=   0 -        -> VARCHAR(MAX)
+STRING       len= 1999999 prec=   0 -        -> VARCHAR(MAX)
+STRING       len= 2000000 prec=   0 -        -> VARCHAR(MAX)
+STRING       len= 2000001 prec=   0 -        -> VARCHAR(MAX)
+STRING       len= 9999998 prec=   0 -        -> VARCHAR(MAX)
+STRING       len= 9999999 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=10000000 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=16777215 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=16777216 prec=   0 -        -> VARCHAR(MAX)
 
 [keys]
 INTEGER      len=      -1 prec=  -1 tk       -> BIGINT PRIMARY KEY
@@ -248,12 +248,12 @@ DATE         len=      10 prec=   2 add      -> ALTER 
TABLE TBL ADD COL DATETIME
 DATE         len=      10 prec=   2 modify   -> ALTER TABLE TBL ALTER COLUMN 
COL DATETIME
 DATE         len=      15 prec=   0 add      -> ALTER TABLE TBL ADD COL 
DATETIME
 DATE         len=      15 prec=   0 modify   -> ALTER TABLE TBL ALTER COLUMN 
COL DATETIME
-TIMESTAMP    len=      -1 prec=  -1 add      -> ALTER TABLE TBL ADD COL 
DATETIME
-TIMESTAMP    len=      -1 prec=  -1 modify   -> ALTER TABLE TBL ALTER COLUMN 
COL DATETIME
-TIMESTAMP    len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COL 
DATETIME
-TIMESTAMP    len=      10 prec=   2 modify   -> ALTER TABLE TBL ALTER COLUMN 
COL DATETIME
-TIMESTAMP    len=      15 prec=   0 add      -> ALTER TABLE TBL ADD COL 
DATETIME
-TIMESTAMP    len=      15 prec=   0 modify   -> ALTER TABLE TBL ALTER COLUMN 
COL DATETIME
+TIMESTAMP    len=      -1 prec=  -1 add      -> ALTER TABLE TBL ADD COL 
DATETIME2
+TIMESTAMP    len=      -1 prec=  -1 modify   -> ALTER TABLE TBL ALTER COLUMN 
COL DATETIME2
+TIMESTAMP    len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COL 
DATETIME2
+TIMESTAMP    len=      10 prec=   2 modify   -> ALTER TABLE TBL ALTER COLUMN 
COL DATETIME2
+TIMESTAMP    len=      15 prec=   0 add      -> ALTER TABLE TBL ADD COL 
DATETIME2
+TIMESTAMP    len=      15 prec=   0 modify   -> ALTER TABLE TBL ALTER COLUMN 
COL DATETIME2
 BOOLEAN      len=      -1 prec=  -1 add      -> ALTER TABLE TBL ADD COL CHAR(1)
 BOOLEAN      len=      -1 prec=  -1 modify   -> ALTER TABLE TBL ALTER COLUMN 
COL CHAR(1)
 BOOLEAN      len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COL CHAR(1)
diff --git 
a/plugins/databases/mssqlnative/src/test/java/org/apache/hop/databases/mssqlnative/MsSqlServerNativeDatabaseMetaTest.java
 
b/plugins/databases/mssqlnative/src/test/java/org/apache/hop/databases/mssqlnative/MsSqlServerNativeDatabaseMetaTest.java
index fc7c203d43..9efac6d456 100644
--- 
a/plugins/databases/mssqlnative/src/test/java/org/apache/hop/databases/mssqlnative/MsSqlServerNativeDatabaseMetaTest.java
+++ 
b/plugins/databases/mssqlnative/src/test/java/org/apache/hop/databases/mssqlnative/MsSqlServerNativeDatabaseMetaTest.java
@@ -19,9 +19,16 @@ package org.apache.hop.databases.mssqlnative;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
+import java.sql.Types;
+import org.apache.hop.core.HopClientEnvironment;
 import org.apache.hop.core.database.DatabaseMeta;
 import org.apache.hop.core.database.IDatabase;
+import org.apache.hop.core.database.types.ColumnContext;
+import org.apache.hop.core.database.types.DatabaseTypeMapper;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.variables.Variables;
 import org.apache.hop.databases.mssql.MsSqlServerDatabaseMeta;
+import org.apache.hop.junit.database.TypeRuleFixture;
 import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
@@ -30,6 +37,23 @@ class MsSqlServerNativeDatabaseMetaTest {
   @RegisterExtension
   static RestoreHopEngineEnvironmentExtension env = new 
RestoreHopEngineEnvironmentExtension();
 
+  @Test
+  void nvarcharRoundTripUsesTheInheritedWriteRules() throws Exception {
+    HopClientEnvironment.init();
+    MsSqlServerNativeDatabaseMeta nativeMeta = new 
MsSqlServerNativeDatabaseMeta();
+    IValueMeta valueMeta =
+        DatabaseTypeMapper.getValueMeta(
+            new Variables(),
+            TypeRuleFixture.meta(nativeMeta),
+            TypeRuleFixture.column(Types.NVARCHAR, "nvarchar", 20, 20),
+            false,
+            false);
+    assertEquals(
+        "NVARCHAR(20)",
+        nativeMeta.getColumnDefinition(
+            valueMeta, null, null, false, false, false, 
ColumnContext.Purpose.CREATE));
+  }
+
   @Test
   void testMsSqlOverrides() {
     MsSqlServerNativeDatabaseMeta localNativeMeta = new 
MsSqlServerNativeDatabaseMeta();
diff --git 
a/plugins/databases/mssqlnative/src/test/resources/org/apache/hop/databases/mssqlnative/MsSqlServerNativeFieldDefinitionGoldenTest.txt
 
b/plugins/databases/mssqlnative/src/test/resources/org/apache/hop/databases/mssqlnative/MsSqlServerNativeFieldDefinitionGoldenTest.txt
index ea82087cda..271ebb2ba5 100644
--- 
a/plugins/databases/mssqlnative/src/test/resources/org/apache/hop/databases/mssqlnative/MsSqlServerNativeFieldDefinitionGoldenTest.txt
+++ 
b/plugins/databases/mssqlnative/src/test/resources/org/apache/hop/databases/mssqlnative/MsSqlServerNativeFieldDefinitionGoldenTest.txt
@@ -11,7 +11,7 @@ STRING       len=      10 prec=   2 -        -> VARCHAR(10)
 STRING       len=      15 prec=   0 -        -> VARCHAR(15)
 STRING       len=      18 prec=   0 -        -> VARCHAR(18)
 STRING       len=      20 prec=   0 -        -> VARCHAR(20)
-STRING       len= 9999999 prec=   0 -        -> TEXT
+STRING       len= 9999999 prec=   0 -        -> VARCHAR(MAX)
 INTEGER      len=      -1 prec=  -1 -        -> INT
 INTEGER      len=       0 prec=   0 -        -> INT
 INTEGER      len=       4 prec=   0 -        -> INT
@@ -48,15 +48,15 @@ DATE         len=      15 prec=   0 -        -> DATETIME
 DATE         len=      18 prec=   0 -        -> DATETIME
 DATE         len=      20 prec=   0 -        -> DATETIME
 DATE         len= 9999999 prec=   0 -        -> DATETIME
-TIMESTAMP    len=      -1 prec=  -1 -        -> DATETIME
-TIMESTAMP    len=       0 prec=   0 -        -> DATETIME
-TIMESTAMP    len=       4 prec=   0 -        -> DATETIME
-TIMESTAMP    len=       9 prec=   0 -        -> DATETIME
-TIMESTAMP    len=      10 prec=   2 -        -> DATETIME
-TIMESTAMP    len=      15 prec=   0 -        -> DATETIME
-TIMESTAMP    len=      18 prec=   0 -        -> DATETIME
-TIMESTAMP    len=      20 prec=   0 -        -> DATETIME
-TIMESTAMP    len= 9999999 prec=   0 -        -> DATETIME
+TIMESTAMP    len=      -1 prec=  -1 -        -> DATETIME2
+TIMESTAMP    len=       0 prec=   0 -        -> DATETIME2
+TIMESTAMP    len=       4 prec=   0 -        -> DATETIME2
+TIMESTAMP    len=       9 prec=   0 -        -> DATETIME2
+TIMESTAMP    len=      10 prec=   2 -        -> DATETIME2
+TIMESTAMP    len=      15 prec=   0 -        -> DATETIME2
+TIMESTAMP    len=      18 prec=   0 -        -> DATETIME2
+TIMESTAMP    len=      20 prec=   0 -        -> DATETIME2
+TIMESTAMP    len= 9999999 prec=   0 -        -> DATETIME2
 BOOLEAN      len=      -1 prec=  -1 -        -> CHAR(1)
 BOOLEAN      len=       0 prec=   0 -        -> CHAR(1)
 BOOLEAN      len=       4 prec=   0 -        -> CHAR(1)
@@ -105,34 +105,34 @@ STRING       len=    3999 prec=   0 -        -> 
VARCHAR(3999)
 STRING       len=    4000 prec=   0 -        -> VARCHAR(4000)
 STRING       len=    4001 prec=   0 -        -> VARCHAR(4001)
 STRING       len=    7999 prec=   0 -        -> VARCHAR(7999)
-STRING       len=    8000 prec=   0 -        -> TEXT
-STRING       len=    8001 prec=   0 -        -> TEXT
-STRING       len=   21843 prec=   0 -        -> TEXT
-STRING       len=   21844 prec=   0 -        -> TEXT
-STRING       len=   32663 prec=   0 -        -> TEXT
-STRING       len=   32664 prec=   0 -        -> TEXT
-STRING       len=   32671 prec=   0 -        -> TEXT
-STRING       len=   32672 prec=   0 -        -> TEXT
-STRING       len=   32699 prec=   0 -        -> TEXT
-STRING       len=   32700 prec=   0 -        -> TEXT
-STRING       len=   32719 prec=   0 -        -> TEXT
-STRING       len=   32720 prec=   0 -        -> TEXT
-STRING       len=   32766 prec=   0 -        -> TEXT
-STRING       len=   32767 prec=   0 -        -> TEXT
-STRING       len=   32768 prec=   0 -        -> TEXT
-STRING       len=   65532 prec=   0 -        -> TEXT
-STRING       len=   65533 prec=   0 -        -> TEXT
-STRING       len=   65534 prec=   0 -        -> TEXT
-STRING       len=   65535 prec=   0 -        -> TEXT
-STRING       len=   65536 prec=   0 -        -> TEXT
-STRING       len= 1999999 prec=   0 -        -> TEXT
-STRING       len= 2000000 prec=   0 -        -> TEXT
-STRING       len= 2000001 prec=   0 -        -> TEXT
-STRING       len= 9999998 prec=   0 -        -> TEXT
-STRING       len= 9999999 prec=   0 -        -> TEXT
-STRING       len=10000000 prec=   0 -        -> TEXT
-STRING       len=16777215 prec=   0 -        -> TEXT
-STRING       len=16777216 prec=   0 -        -> TEXT
+STRING       len=    8000 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=    8001 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   21843 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   21844 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32663 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32664 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32671 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32672 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32699 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32700 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32719 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32720 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32766 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32767 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   32768 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   65532 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   65533 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   65534 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   65535 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=   65536 prec=   0 -        -> VARCHAR(MAX)
+STRING       len= 1999999 prec=   0 -        -> VARCHAR(MAX)
+STRING       len= 2000000 prec=   0 -        -> VARCHAR(MAX)
+STRING       len= 2000001 prec=   0 -        -> VARCHAR(MAX)
+STRING       len= 9999998 prec=   0 -        -> VARCHAR(MAX)
+STRING       len= 9999999 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=10000000 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=16777215 prec=   0 -        -> VARCHAR(MAX)
+STRING       len=16777216 prec=   0 -        -> VARCHAR(MAX)
 
 [keys]
 INTEGER      len=      -1 prec=  -1 tk       -> BIGINT PRIMARY KEY
@@ -248,12 +248,12 @@ DATE         len=      10 prec=   2 add      -> ALTER 
TABLE TBL ADD COL DATETIME
 DATE         len=      10 prec=   2 modify   -> ALTER TABLE TBL ALTER COLUMN 
COL DATETIME
 DATE         len=      15 prec=   0 add      -> ALTER TABLE TBL ADD COL 
DATETIME
 DATE         len=      15 prec=   0 modify   -> ALTER TABLE TBL ALTER COLUMN 
COL DATETIME
-TIMESTAMP    len=      -1 prec=  -1 add      -> ALTER TABLE TBL ADD COL 
DATETIME
-TIMESTAMP    len=      -1 prec=  -1 modify   -> ALTER TABLE TBL ALTER COLUMN 
COL DATETIME
-TIMESTAMP    len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COL 
DATETIME
-TIMESTAMP    len=      10 prec=   2 modify   -> ALTER TABLE TBL ALTER COLUMN 
COL DATETIME
-TIMESTAMP    len=      15 prec=   0 add      -> ALTER TABLE TBL ADD COL 
DATETIME
-TIMESTAMP    len=      15 prec=   0 modify   -> ALTER TABLE TBL ALTER COLUMN 
COL DATETIME
+TIMESTAMP    len=      -1 prec=  -1 add      -> ALTER TABLE TBL ADD COL 
DATETIME2
+TIMESTAMP    len=      -1 prec=  -1 modify   -> ALTER TABLE TBL ALTER COLUMN 
COL DATETIME2
+TIMESTAMP    len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COL 
DATETIME2
+TIMESTAMP    len=      10 prec=   2 modify   -> ALTER TABLE TBL ALTER COLUMN 
COL DATETIME2
+TIMESTAMP    len=      15 prec=   0 add      -> ALTER TABLE TBL ADD COL 
DATETIME2
+TIMESTAMP    len=      15 prec=   0 modify   -> ALTER TABLE TBL ALTER COLUMN 
COL DATETIME2
 BOOLEAN      len=      -1 prec=  -1 add      -> ALTER TABLE TBL ADD COL CHAR(1)
 BOOLEAN      len=      -1 prec=  -1 modify   -> ALTER TABLE TBL ALTER COLUMN 
COL CHAR(1)
 BOOLEAN      len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COL CHAR(1)
diff --git 
a/plugins/databases/mysql/src/test/java/org/apache/hop/databases/mysql/MySqlTypeRulesTest.java
 
b/plugins/databases/mysql/src/test/java/org/apache/hop/databases/mysql/MySqlTypeRulesTest.java
index ba78e10c5b..b63cc26279 100644
--- 
a/plugins/databases/mysql/src/test/java/org/apache/hop/databases/mysql/MySqlTypeRulesTest.java
+++ 
b/plugins/databases/mysql/src/test/java/org/apache/hop/databases/mysql/MySqlTypeRulesTest.java
@@ -19,6 +19,7 @@ package org.apache.hop.databases.mysql;
 
 import static org.apache.hop.junit.database.TypeRuleFixture.column;
 import static org.apache.hop.junit.database.TypeRuleFixture.meta;
+import static org.apache.hop.junit.database.TypeRuleFixture.numericColumn;
 import static org.apache.hop.junit.database.TypeRuleFixture.properties;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -92,4 +93,21 @@ class MySqlTypeRulesTest {
     assertTrue(valueMeta.isBinary());
     assertEquals(8, valueMeta.getLength());
   }
+
+  @Test
+  void aWideExactDecimalRoundTripsAsDecimalNotDouble() throws Exception {
+    MySqlDatabaseMeta dialect = new MySqlDatabaseMeta();
+    IValueMeta valueMeta =
+        DatabaseTypeMapper.getValueMeta(
+            new Variables(),
+            meta(dialect),
+            numericColumn(Types.DECIMAL, "decimal", 24, 15),
+            false,
+            false);
+    assertTrue(valueMeta.isBigNumber() || valueMeta.isNumber());
+    assertEquals(24, valueMeta.getLength());
+    assertEquals(15, valueMeta.getPrecision());
+    assertEquals(
+        "DECIMAL(24, 15)", dialect.getFieldDefinition(valueMeta, null, null, 
false, false, false));
+  }
 }
diff --git 
a/plugins/databases/postgresql/src/main/java/org/apache/hop/databases/postgresql/PostgreSqlDatabaseMeta.java
 
b/plugins/databases/postgresql/src/main/java/org/apache/hop/databases/postgresql/PostgreSqlDatabaseMeta.java
index 539dd41e24..1bb119c35f 100644
--- 
a/plugins/databases/postgresql/src/main/java/org/apache/hop/databases/postgresql/PostgreSqlDatabaseMeta.java
+++ 
b/plugins/databases/postgresql/src/main/java/org/apache/hop/databases/postgresql/PostgreSqlDatabaseMeta.java
@@ -484,8 +484,10 @@ public class PostgreSqlDatabaseMeta extends 
BaseDatabaseMeta implements IDatabas
         } else {
           if (length > 0) {
             if (precision > 0 || length > 18) {
-              // Numeric(Precision, Scale): Precision = total length; Scale = 
decimal places
-              int numericPrecision = length + precision;
+              // Numeric(p, s): Hop length is the total number of significant 
digits. PostgreSQL
+              // before 15 rejects s > p, so widen p when a field was authored 
with scale larger
+              // than length.
+              int numericPrecision = Math.max(length, precision);
               if (numericPrecision > MAX_NUMERIC_PRECISION) {
                 // PostgreSQL refuses a declared precision above 1000 
outright: "NUMERIC precision
                 // 1073741824 must be between 1 and 1000". A length that large 
only ever arrives
diff --git 
a/plugins/databases/postgresql/src/test/java/org/apache/hop/databases/postgresql/PostgreSqlDatabaseMetaTest.java
 
b/plugins/databases/postgresql/src/test/java/org/apache/hop/databases/postgresql/PostgreSqlDatabaseMetaTest.java
index 84c48b79d6..bafdbec968 100644
--- 
a/plugins/databases/postgresql/src/test/java/org/apache/hop/databases/postgresql/PostgreSqlDatabaseMetaTest.java
+++ 
b/plugins/databases/postgresql/src/test/java/org/apache/hop/databases/postgresql/PostgreSqlDatabaseMetaTest.java
@@ -764,17 +764,17 @@ public class PostgreSqlDatabaseMetaTest {
             "FOO", new ValueMetaNumber("BAR", 5, 0), "", false, "", false));
 
     assertEquals(
-        "ALTER TABLE FOO ADD COLUMN BAR NUMERIC(13, 3)",
+        "ALTER TABLE FOO ADD COLUMN BAR NUMERIC(10, 3)",
         nativeMeta.getAddColumnStatement(
             "FOO", new ValueMetaNumber("BAR", 10, 3), "", false, "", false));
 
     assertEquals(
-        "ALTER TABLE FOO ADD COLUMN BAR NUMERIC(13, 3)",
+        "ALTER TABLE FOO ADD COLUMN BAR NUMERIC(10, 3)",
         nativeMeta.getAddColumnStatement(
             "FOO", new ValueMetaBigNumber("BAR", 10, 3), "", false, "", 
false));
 
     assertEquals(
-        "ALTER TABLE FOO ADD COLUMN BAR NUMERIC(25, 4)",
+        "ALTER TABLE FOO ADD COLUMN BAR NUMERIC(21, 4)",
         nativeMeta.getAddColumnStatement(
             "FOO", new ValueMetaBigNumber("BAR", 21, 4), "", false, "", 
false));
 
@@ -804,7 +804,7 @@ public class PostgreSqlDatabaseMetaTest {
             false)); // Bug here - invalid SQL
 
     assertEquals(
-        "ALTER TABLE FOO ADD COLUMN BAR NUMERIC(29, 7)",
+        "ALTER TABLE FOO ADD COLUMN BAR NUMERIC(22, 7)",
         nativeMeta.getAddColumnStatement(
             "FOO", new ValueMetaBigNumber("BAR", 22, 7), "", false, "", 
false));
     assertEquals(
@@ -812,7 +812,7 @@ public class PostgreSqlDatabaseMetaTest {
         nativeMeta.getAddColumnStatement(
             "FOO", new ValueMetaNumber("BAR", -10, 7), "", false, "", false));
     assertEquals(
-        "ALTER TABLE FOO ADD COLUMN BAR NUMERIC(12, 7)",
+        "ALTER TABLE FOO ADD COLUMN BAR NUMERIC(7, 7)",
         nativeMeta.getAddColumnStatement(
             "FOO", new ValueMetaNumber("BAR", 5, 7), "", false, "", false));
     // An ALTER TABLE spells a column the way a CREATE TABLE does: through the 
dialect's type
diff --git 
a/plugins/databases/postgresql/src/test/java/org/apache/hop/databases/postgresql/PostgreSqlTypeRulesTest.java
 
b/plugins/databases/postgresql/src/test/java/org/apache/hop/databases/postgresql/PostgreSqlTypeRulesTest.java
index 1e0b197bdd..abcff310bf 100644
--- 
a/plugins/databases/postgresql/src/test/java/org/apache/hop/databases/postgresql/PostgreSqlTypeRulesTest.java
+++ 
b/plugins/databases/postgresql/src/test/java/org/apache/hop/databases/postgresql/PostgreSqlTypeRulesTest.java
@@ -71,11 +71,25 @@ class PostgreSqlTypeRulesTest {
     IValueMeta valueMeta = map(Types.NUMERIC, "numeric", 10, 2);
 
     assertTrue(valueMeta.isNumber());
-    // Hop length is the digits before the decimal, so it round-trips as 
NUMERIC(8+2, 2).
-    assertEquals(8, valueMeta.getLength());
+    // Hop length is the total number of significant digits, the same as the 
database precision.
+    assertEquals(10, valueMeta.getLength());
     assertEquals(2, valueMeta.getPrecision());
   }
 
+  @Test
+  void aScaledNumericRoundTripsAsTheSameDeclaration() throws Exception {
+    assertEquals("NUMERIC(5, 2)", roundTrip(Types.NUMERIC, "numeric", 5, 2));
+    assertEquals("NUMERIC(24, 15)", roundTrip(Types.DECIMAL, "numeric", 24, 
15));
+    assertEquals("NUMERIC(16, 1)", roundTrip(Types.NUMERIC, "numeric", 16, 1));
+  }
+
+  private String roundTrip(int sqlType, String typeName, int precision, int 
scale)
+      throws Exception {
+    PostgreSqlDatabaseMeta dialect = new PostgreSqlDatabaseMeta();
+    IValueMeta valueMeta = map(sqlType, typeName, precision, scale);
+    return dialect.getFieldDefinition(valueMeta, null, null, false, false, 
false);
+  }
+
   /**
    * An address is Types.OTHER, which the standard mapping takes as a string. 
The dialect has to
    * name it, or the value type Hop has for addresses never sees the column.
diff --git 
a/plugins/databases/postgresql/src/test/resources/org/apache/hop/databases/postgresql/PostgreSqlFieldDefinitionGoldenTest.txt
 
b/plugins/databases/postgresql/src/test/resources/org/apache/hop/databases/postgresql/PostgreSqlFieldDefinitionGoldenTest.txt
index 679a316226..d053b4739d 100644
--- 
a/plugins/databases/postgresql/src/test/resources/org/apache/hop/databases/postgresql/PostgreSqlFieldDefinitionGoldenTest.txt
+++ 
b/plugins/databases/postgresql/src/test/resources/org/apache/hop/databases/postgresql/PostgreSqlFieldDefinitionGoldenTest.txt
@@ -25,7 +25,7 @@ NUMBER       len=      -1 prec=  -1 -        -> DOUBLE 
PRECISION
 NUMBER       len=       0 prec=   0 -        -> DOUBLE PRECISION
 NUMBER       len=       4 prec=   0 -        -> SMALLINT
 NUMBER       len=       9 prec=   0 -        -> INTEGER
-NUMBER       len=      10 prec=   2 -        -> NUMERIC(12, 2)
+NUMBER       len=      10 prec=   2 -        -> NUMERIC(10, 2)
 NUMBER       len=      15 prec=   0 -        -> BIGINT
 NUMBER       len=      18 prec=   0 -        -> BIGINT
 NUMBER       len=      20 prec=   0 -        -> NUMERIC(20, 0)
@@ -34,7 +34,7 @@ BIGNUMBER    len=      -1 prec=  -1 -        -> DOUBLE 
PRECISION
 BIGNUMBER    len=       0 prec=   0 -        -> DOUBLE PRECISION
 BIGNUMBER    len=       4 prec=   0 -        -> SMALLINT
 BIGNUMBER    len=       9 prec=   0 -        -> INTEGER
-BIGNUMBER    len=      10 prec=   2 -        -> NUMERIC(12, 2)
+BIGNUMBER    len=      10 prec=   2 -        -> NUMERIC(10, 2)
 BIGNUMBER    len=      15 prec=   0 -        -> BIGINT
 BIGNUMBER    len=      18 prec=   0 -        -> BIGINT
 BIGNUMBER    len=      20 prec=   0 -        -> NUMERIC(20, 0)
@@ -232,14 +232,14 @@ INTEGER      len=      15 prec=   0 add      -> ALTER 
TABLE TBL ADD COLUMN COL B
 INTEGER      len=      15 prec=   0 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL BIGINT; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; 
ALTER TABLE TBL RENAME COL_KTL TO COL;
 NUMBER       len=      -1 prec=  -1 add      -> ALTER TABLE TBL ADD COLUMN COL 
DOUBLE PRECISION
 NUMBER       len=      -1 prec=  -1 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL DOUBLE PRECISION; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP 
COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
-NUMBER       len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COLUMN COL 
NUMERIC(12, 2)
-NUMBER       len=      10 prec=   2 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL NUMERIC(12, 2); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN 
COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+NUMBER       len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COLUMN COL 
NUMERIC(10, 2)
+NUMBER       len=      10 prec=   2 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL NUMERIC(10, 2); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN 
COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
 NUMBER       len=      15 prec=   0 add      -> ALTER TABLE TBL ADD COLUMN COL 
BIGINT
 NUMBER       len=      15 prec=   0 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL BIGINT; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; 
ALTER TABLE TBL RENAME COL_KTL TO COL;
 BIGNUMBER    len=      -1 prec=  -1 add      -> ALTER TABLE TBL ADD COLUMN COL 
DOUBLE PRECISION
 BIGNUMBER    len=      -1 prec=  -1 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL DOUBLE PRECISION; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP 
COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
-BIGNUMBER    len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COLUMN COL 
NUMERIC(12, 2)
-BIGNUMBER    len=      10 prec=   2 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL NUMERIC(12, 2); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN 
COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+BIGNUMBER    len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COLUMN COL 
NUMERIC(10, 2)
+BIGNUMBER    len=      10 prec=   2 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL NUMERIC(10, 2); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN 
COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
 BIGNUMBER    len=      15 prec=   0 add      -> ALTER TABLE TBL ADD COLUMN COL 
BIGINT
 BIGNUMBER    len=      15 prec=   0 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL BIGINT; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; 
ALTER TABLE TBL RENAME COL_KTL TO COL;
 DATE         len=      -1 prec=  -1 add      -> ALTER TABLE TBL ADD COLUMN COL 
TIMESTAMP
diff --git 
a/plugins/databases/redshift/src/test/resources/org/apache/hop/databases/redshift/RedshiftFieldDefinitionGoldenTest.txt
 
b/plugins/databases/redshift/src/test/resources/org/apache/hop/databases/redshift/RedshiftFieldDefinitionGoldenTest.txt
index 5407684bad..cd569d0e1f 100644
--- 
a/plugins/databases/redshift/src/test/resources/org/apache/hop/databases/redshift/RedshiftFieldDefinitionGoldenTest.txt
+++ 
b/plugins/databases/redshift/src/test/resources/org/apache/hop/databases/redshift/RedshiftFieldDefinitionGoldenTest.txt
@@ -25,7 +25,7 @@ NUMBER       len=      -1 prec=  -1 -        -> DOUBLE 
PRECISION
 NUMBER       len=       0 prec=   0 -        -> DOUBLE PRECISION
 NUMBER       len=       4 prec=   0 -        -> SMALLINT
 NUMBER       len=       9 prec=   0 -        -> INTEGER
-NUMBER       len=      10 prec=   2 -        -> NUMERIC(12, 2)
+NUMBER       len=      10 prec=   2 -        -> NUMERIC(10, 2)
 NUMBER       len=      15 prec=   0 -        -> BIGINT
 NUMBER       len=      18 prec=   0 -        -> BIGINT
 NUMBER       len=      20 prec=   0 -        -> NUMERIC(20, 0)
@@ -34,7 +34,7 @@ BIGNUMBER    len=      -1 prec=  -1 -        -> DOUBLE 
PRECISION
 BIGNUMBER    len=       0 prec=   0 -        -> DOUBLE PRECISION
 BIGNUMBER    len=       4 prec=   0 -        -> SMALLINT
 BIGNUMBER    len=       9 prec=   0 -        -> INTEGER
-BIGNUMBER    len=      10 prec=   2 -        -> NUMERIC(12, 2)
+BIGNUMBER    len=      10 prec=   2 -        -> NUMERIC(10, 2)
 BIGNUMBER    len=      15 prec=   0 -        -> BIGINT
 BIGNUMBER    len=      18 prec=   0 -        -> BIGINT
 BIGNUMBER    len=      20 prec=   0 -        -> NUMERIC(20, 0)
@@ -232,14 +232,14 @@ INTEGER      len=      15 prec=   0 add      -> ALTER 
TABLE TBL ADD COLUMN COL B
 INTEGER      len=      15 prec=   0 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL BIGINT; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; 
ALTER TABLE TBL RENAME COL_KTL TO COL;
 NUMBER       len=      -1 prec=  -1 add      -> ALTER TABLE TBL ADD COLUMN COL 
DOUBLE PRECISION
 NUMBER       len=      -1 prec=  -1 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL DOUBLE PRECISION; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP 
COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
-NUMBER       len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COLUMN COL 
NUMERIC(12, 2)
-NUMBER       len=      10 prec=   2 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL NUMERIC(12, 2); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN 
COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+NUMBER       len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COLUMN COL 
NUMERIC(10, 2)
+NUMBER       len=      10 prec=   2 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL NUMERIC(10, 2); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN 
COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
 NUMBER       len=      15 prec=   0 add      -> ALTER TABLE TBL ADD COLUMN COL 
BIGINT
 NUMBER       len=      15 prec=   0 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL BIGINT; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; 
ALTER TABLE TBL RENAME COL_KTL TO COL;
 BIGNUMBER    len=      -1 prec=  -1 add      -> ALTER TABLE TBL ADD COLUMN COL 
DOUBLE PRECISION
 BIGNUMBER    len=      -1 prec=  -1 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL DOUBLE PRECISION; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP 
COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
-BIGNUMBER    len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COLUMN COL 
NUMERIC(12, 2)
-BIGNUMBER    len=      10 prec=   2 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL NUMERIC(12, 2); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN 
COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+BIGNUMBER    len=      10 prec=   2 add      -> ALTER TABLE TBL ADD COLUMN COL 
NUMERIC(10, 2)
+BIGNUMBER    len=      10 prec=   2 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL NUMERIC(10, 2); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN 
COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
 BIGNUMBER    len=      15 prec=   0 add      -> ALTER TABLE TBL ADD COLUMN COL 
BIGINT
 BIGNUMBER    len=      15 prec=   0 modify   -> ALTER TABLE TBL ADD COLUMN 
COL_KTL BIGINT; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; 
ALTER TABLE TBL RENAME COL_KTL TO COL;
 DATE         len=      -1 prec=  -1 add      -> ALTER TABLE TBL ADD COLUMN COL 
TIMESTAMP
diff --git 
a/plugins/transforms/streamlookup/src/main/java/org/apache/hop/pipeline/transforms/streamlookup/StreamLookup.java
 
b/plugins/transforms/streamlookup/src/main/java/org/apache/hop/pipeline/transforms/streamlookup/StreamLookup.java
index 16208e81ca..748b5e90ba 100644
--- 
a/plugins/transforms/streamlookup/src/main/java/org/apache/hop/pipeline/transforms/streamlookup/StreamLookup.java
+++ 
b/plugins/transforms/streamlookup/src/main/java/org/apache/hop/pipeline/transforms/streamlookup/StreamLookup.java
@@ -74,7 +74,7 @@ public class StreamLookup extends 
BaseTransform<StreamLookupMeta, StreamLookupDa
             data.nullIf[i] = data.valueDefault[i];
           }
           break;
-        case IValueMeta.TYPE_DATE:
+        case IValueMeta.TYPE_DATE, IValueMeta.TYPE_TIMESTAMP:
           try {
             data.nullIf[i] = 
DateFormat.getInstance().parse(data.valueDefault[i]);
           } catch (Exception e) {
diff --git 
a/plugins/transforms/streamlookup/src/test/java/org/apache/hop/pipeline/transforms/streamlookup/StreamLookupTest.java
 
b/plugins/transforms/streamlookup/src/test/java/org/apache/hop/pipeline/transforms/streamlookup/StreamLookupTest.java
new file mode 100644
index 0000000000..efaf8c4459
--- /dev/null
+++ 
b/plugins/transforms/streamlookup/src/test/java/org/apache/hop/pipeline/transforms/streamlookup/StreamLookupTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.streamlookup;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import java.lang.reflect.Method;
+import java.text.DateFormat;
+import java.util.Date;
+import org.apache.hop.core.logging.ILoggingObject;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.value.ValueMetaPluginType;
+import org.apache.hop.junit.rules.RestoreHopEnvironmentExtension;
+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;
+
+@ExtendWith(RestoreHopEnvironmentExtension.class)
+class StreamLookupTest {
+
+  private TransformMockHelper<StreamLookupMeta, StreamLookupData> mockHelper;
+
+  @BeforeEach
+  void setUp() throws Exception {
+    ValueMetaPluginType.getInstance().searchPlugins();
+    mockHelper =
+        new TransformMockHelper<>("lookup", StreamLookupMeta.class, 
StreamLookupData.class);
+    when(mockHelper.logChannelFactory.create(any(), any(ILoggingObject.class)))
+        .thenReturn(mockHelper.iLogChannel);
+    
when(mockHelper.logChannelFactory.create(any())).thenReturn(mockHelper.iLogChannel);
+  }
+
+  @AfterEach
+  void tearDown() {
+    mockHelper.cleanUp();
+  }
+
+  @Test
+  void timestampDefaultValueDoesNotThrowConversionNotImplemented() throws 
Exception {
+    StreamLookupMeta meta = new StreamLookupMeta();
+    StreamLookupMeta.ReturnValue returnValue = new 
StreamLookupMeta.ReturnValue();
+    returnValue.setValue("ts");
+    returnValue.setValueName("ts");
+    returnValue.setValueDefault(DateFormat.getInstance().format(new Date()));
+    returnValue.setValueDefaultType(IValueMeta.TYPE_TIMESTAMP);
+    meta.getLookup().getReturnValues().add(returnValue);
+
+    StreamLookupData data = new StreamLookupData();
+    data.valueDefault = new String[] {returnValue.getValueDefault()};
+
+    StreamLookup transform =
+        new StreamLookup(
+            mockHelper.transformMeta, meta, data, 0, mockHelper.pipelineMeta, 
mockHelper.pipeline);
+
+    Method handleNullIf = StreamLookup.class.getDeclaredMethod("handleNullIf");
+    handleNullIf.setAccessible(true);
+    assertDoesNotThrow(() -> handleNullIf.invoke(transform));
+    assertNotNull(data.nullIf);
+    assertNotNull(data.nullIf[0]);
+  }
+}
diff --git a/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java 
b/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
index 8202d76abb..94eb314fb5 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
@@ -94,7 +94,6 @@ public class PropsUi extends Props {
   private static final String DISABLE_ZOOM_SCROLLING = "DisableZoomScrolling";
   private static final String METRICS_ABOVE_SELECTED_TRANSFORMS = 
"MetricsAboveSelectedTransforms";
   private static final String ENABLE_INFINITE_CANVAS_MOVE = 
"EnableInfiniteCanvasMove";
-  private static final String USE_ADVANCED_TERMINAL = "UseAdvancedTerminal";
   private static final String REMEMBER_DIALOG_POSITIONS = 
"RememberDialogPositions";
   private static final String RESET_DIALOG_POSITIONS_ON_RESTART = 
"ResetDialogPositionsOnRestart";
 
diff --git 
a/ui/src/main/java/org/apache/hop/ui/core/dialog/ProgressMonitorDialog.java 
b/ui/src/main/java/org/apache/hop/ui/core/dialog/ProgressMonitorDialog.java
index 2fb4bb054a..d39d360925 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/dialog/ProgressMonitorDialog.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/dialog/ProgressMonitorDialog.java
@@ -188,6 +188,10 @@ public class ProgressMonitorDialog {
     } catch (InterruptedException e) {
       interruptedException = e;
       Thread.currentThread().interrupt();
+    } finally {
+      // Callers that forget monitor.done() must not leave the modal dialog up 
forever.
+      // dispose() is idempotent, so callers that do call done() are 
unaffected.
+      dispose();
     }
   }
 
@@ -206,6 +210,13 @@ public class ProgressMonitorDialog {
         display.sleep();
       }
     }
+    // The worker may dispose the shell from its finally before this loop 
re-checks the flags.
+    if (interruptedException != null) {
+      throw interruptedException;
+    }
+    if (targetException != null) {
+      throw targetException;
+    }
   }
 
   public Shell getShell() {
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java 
b/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
index b19c9b3f45..7c9942adc5 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
@@ -251,8 +251,6 @@ public class HopGui
 
   public static final String ID_MAIN_MENU_VIEW_PARENT_ID = "25000-menu-view";
   public static final String ID_MAIN_MENU_VIEW_FULL_SCREEN = 
"25010-menu-view-full-screen";
-  public static final String ID_MAIN_MENU_VIEW_TERMINAL = 
"25010-menu-view-terminal";
-  public static final String ID_MAIN_MENU_VIEW_NEW_TERMINAL = 
"25020-menu-view-new-terminal";
 
   public static final String ID_MAIN_MENU_RUN_PARENT_ID = "30000-menu-run";
   public static final String ID_MAIN_MENU_RUN_START = "30010-menu-run-execute";
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/metadata/MetadataPerspective.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/metadata/MetadataPerspective.java
index bba346d341..8520712f8d 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/metadata/MetadataPerspective.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/metadata/MetadataPerspective.java
@@ -2546,11 +2546,15 @@ public class MetadataPerspective implements 
IHopPerspective, TabClosable, IMetad
           if (!typeFolder.isFolder() || knownKeys.contains(key)) {
             continue;
           }
+          List<FileObject> jsonFiles = HopVfs.findFiles(typeFolder, "json", 
false);
+          if (jsonFiles.isEmpty()) {
+            continue;
+          }
           String reason =
               BaseMessages.getString(PKG, 
"MetadataPerspective.Unknown.NoPluginForType", key);
           UnknownTypeModel unknownType =
               unknownByKey.computeIfAbsent(key, k -> new UnknownTypeModel(k, 
k));
-          for (FileObject jsonFile : HopVfs.findFiles(typeFolder, "json", 
false)) {
+          for (FileObject jsonFile : jsonFiles) {
             String name = 
jsonFile.getName().getBaseName().replaceAll("\\.json$", "");
             // The same element can live in a parent project as well: like 
anywhere else the first
             // provider which has it wins, so we don't list it twice.

Reply via email to