You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hop.apache.org by ha...@apache.org on 2026/08/20 08:41:37 UTC
(hop) branch main updated: Let each database dialect describe its own column types, fixes #7997 (#8017)
This is an automated email from the ASF dual-hosted git repository.
hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new 362140499c Let each database dialect describe its own column types, fixes #7997 (#8017)
362140499c is described below
commit 362140499c9afbff3e6ae3afede89a1796eaf972
Author: Hans Van Akelyen <ha...@gmail.com>
AuthorDate: Thu Aug 20 10:41:29 2026 +0200
Let each database dialect describe its own column types, fixes #7997 (#8017)
* Let each database dialect describe its own column types, fixes #7997
* Document writing database plugins in the developer manual
The developer manual had nothing about databases, which is Hop's largest plugin
family: around fifty dialects, and forty-five pages in the user manual telling
people how to connect to them, against two passing mentions of IDatabase for
anyone wanting to add one.
Adds a database section covering what a database plugin is made of, writing a
dialect, how column types are decided and declared, when a value needs a binding
rather than a type rule, and how a plugin can add a database specific type such
as Oracle's SDO_GEOMETRY without a change to Hop or to the plugin it extends.
Also a page on testing, since the golden files only work if people know that a
changed golden is a change to the SQL a user's database receives, and one on
migrating off the deprecated isXVariant() methods.
Value types get their own page outside the database section, because adding a
Hop data type is a row concern; supporting a database specific type needs both
halves and each links to the other.
Co-Authored-By: Claude Opus 5 <no...@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <no...@anthropic.com>
---
.gitignore | 8 +
.../org/apache/hop/core/HopClientEnvironment.java | 2 +
.../apache/hop/core/database/BaseDatabaseMeta.java | 165 ++++-
.../org/apache/hop/core/database/Database.java | 403 ++---------
.../org/apache/hop/core/database/DatabaseMeta.java | 46 +-
.../org/apache/hop/core/database/IDatabase.java | 90 +++
.../hop/core/database/types/ColumnContext.java | 112 +++
.../hop/core/database/types/ColumnTypeRules.java | 101 +++
.../hop/core/database/types/DatabaseColumn.java | 180 +++++
.../core/database/types/DatabaseTypeMapper.java | 127 ++++
.../database/types/DatabaseTypeRuleRegistry.java | 176 +++++
.../database/types/DatabaseTypeRulesPlugin.java | 80 +++
.../types/DatabaseTypeRulesPluginType.java | 102 +++
.../hop/core/database/types/DatabaseTypes.java | 380 +++++++++++
.../hop/core/database/types/IDatabaseTypeRule.java | 93 +++
.../database/types/IDatabaseTypeRuleProvider.java | 22 +-
.../hop/core/database/types/IValueBinding.java | 67 ++
.../hop/core/database/types/JdbcDateValues.java | 110 +++
.../core/database/types/LegacyVariantBridge.java | 142 ++++
.../database/types/StandardJdbcTypeMapper.java | 321 +++++++++
.../java/org/apache/hop/core/row/IValueMeta.java | 9 +
.../apache/hop/core/row/value/ValueMetaBase.java | 132 ++--
.../core/row/value/ValueMetaInternetAddress.java | 30 +-
.../apache/hop/core/row/value/ValueMetaJson.java | 20 +-
.../hop/core/row/value/ValueMetaTimestamp.java | 13 +
.../org/apache/hop/core/database/DatabaseTest.java | 4 +-
.../JdbcTypeMappingCharacterizationTest.java | 395 +++++++++++
.../core/database/types/DatabaseTypeRulesTest.java | 404 +++++++++++
.../ValueMetaBaseSetPreparedStmntValueTest.java | 3 +
.../hop/core/row/value/ValueMetaJsonTest.java | 10 +-
.../database/BaseFieldDefinitionGoldenTest.java | 313 +++++++++
.../apache/hop/junit/database/TypeRuleFixture.java | 92 +++
.../JdbcTypeMappingCharacterizationTest.txt | 751 +++++++++++++++++++++
docs/hop-dev-manual/modules/ROOT/nav.adoc | 8 +
.../modules/ROOT/pages/database/column-types.adoc | 197 ++++++
.../ROOT/pages/database/creating-a-dialect.adoc | 150 ++++
.../pages/database/extending-another-dialect.adoc | 121 ++++
.../modules/ROOT/pages/database/index.adoc | 74 ++
.../pages/database/migrating-from-variants.adoc | 87 +++
.../modules/ROOT/pages/database/testing.adoc | 113 ++++
.../ROOT/pages/database/value-bindings.adoc | 109 +++
.../modules/ROOT/pages/value-types.adoc | 89 +++
.../hop/databases/access/AccessDatabaseMeta.java | 6 +-
.../access/AccessFieldDefinitionGoldenTest.java} | 21 +-
.../access/AccessFieldDefinitionGoldenTest.txt | 240 +++++++
.../hop/databases/as400/AS400DatabaseMeta.java | 9 +-
.../actions/as400command/ActionAs400Command.java | 3 +-
.../as400/AS400FieldDefinitionGoldenTest.java} | 21 +-
.../as400/AS400FieldDefinitionGoldenTest.txt | 240 +++++++
.../hop/databases/cache/CacheDatabaseMeta.java | 9 +-
.../cache/CacheFieldDefinitionGoldenTest.java} | 21 +-
.../cache/CacheFieldDefinitionGoldenTest.txt | 240 +++++++
.../clickhouse/ClickhouseDatabaseMeta.java | 9 +-
.../ClickhouseFieldDefinitionGoldenTest.java} | 21 +-
.../ClickhouseFieldDefinitionGoldenTest.txt | 240 +++++++
.../cockroachdb/CockroachDatabaseMeta.java | 3 +-
.../CockroachFieldDefinitionGoldenTest.java} | 19 +-
.../CockroachFieldDefinitionGoldenTest.txt | 240 +++++++
.../databases/cratedb/CrateDBDatabaseMetaTest.java | 5 +-
.../cratedb/CrateDBFieldDefinitionGoldenTest.java} | 21 +-
.../cratedb/CrateDBFieldDefinitionGoldenTest.txt | 240 +++++++
.../apache/hop/databases/db2/DB2DatabaseMeta.java | 9 +-
.../db2/DB2FieldDefinitionGoldenTest.java} | 21 +-
.../databases/db2/DB2FieldDefinitionGoldenTest.txt | 240 +++++++
.../hop/databases/derby/DerbyDatabaseMeta.java | 9 +-
.../derby/DerbyFieldDefinitionGoldenTest.java} | 21 +-
.../derby/DerbyFieldDefinitionGoldenTest.txt | 240 +++++++
.../hop/databases/doris/DorisDatabaseMeta.java | 3 +-
.../dorisbulkloader/DorisBulkLoaderMeta.java | 3 +-
.../doris/DorisFieldDefinitionGoldenTest.java} | 21 +-
.../doris/DorisFieldDefinitionGoldenTest.txt | 240 +++++++
.../hop/databases/duckdb/DuckDBDatabaseMeta.java | 34 +-
.../duckdb/DuckDBFieldDefinitionGoldenTest.java} | 21 +-
.../databases/duckdb/DuckDBValueBindingTest.java | 94 +++
.../duckdb/DuckDBFieldDefinitionGoldenTest.txt | 240 +++++++
.../hop/databases/exasol4/Exasol4DatabaseMeta.java | 9 +-
.../exasol4/Exasol4FieldDefinitionGoldenTest.java} | 21 +-
.../exasol4/Exasol4FieldDefinitionGoldenTest.txt | 240 +++++++
.../databases/firebird/FirebirdDatabaseMeta.java | 9 +-
.../FirebirdFieldDefinitionGoldenTest.java} | 21 +-
.../firebird/FirebirdFieldDefinitionGoldenTest.txt | 240 +++++++
.../hop/databases/generic/GenericDatabaseMeta.java | 28 +-
.../generic/GenericFieldDefinitionGoldenTest.java} | 21 +-
.../generic/GenericFieldDefinitionGoldenTest.txt | 240 +++++++
.../googlebigquery/GoogleBigQueryDatabaseMeta.java | 3 +-
.../GoogleBigQueryFieldDefinitionGoldenTest.java} | 21 +-
.../GoogleBigQueryFieldDefinitionGoldenTest.txt | 240 +++++++
.../databases/greenplum/GreenplumDatabaseMeta.java | 3 +-
.../GreenplumFieldDefinitionGoldenTest.java} | 21 +-
.../GreenplumFieldDefinitionGoldenTest.txt | 240 +++++++
.../apache/hop/databases/h2/H2DatabaseMeta.java | 9 +-
.../databases/h2/H2FieldDefinitionGoldenTest.java} | 21 +-
.../databases/h2/H2FieldDefinitionGoldenTest.txt | 240 +++++++
.../hop/databases/hive/HiveDatabaseMeta.java | 9 +-
.../hive/HiveFieldDefinitionGoldenTest.java} | 21 +-
.../hive/HiveFieldDefinitionGoldenTest.txt | 240 +++++++
.../hypersonic/HypersonicDatabaseMeta.java | 9 +-
.../HypersonicFieldDefinitionGoldenTest.java} | 21 +-
.../HypersonicFieldDefinitionGoldenTest.txt | 240 +++++++
.../hop/databases/impala/ImpalaDatabaseMeta.java | 10 +-
.../impala/ImpalaFieldDefinitionGoldenTest.java} | 21 +-
.../impala/ImpalaFieldDefinitionGoldenTest.txt | 240 +++++++
.../infobright/InfobrightDatabaseMeta.java | 3 +-
.../InfobrightFieldDefinitionGoldenTest.java} | 21 +-
.../InfobrightFieldDefinitionGoldenTest.txt | 240 +++++++
.../databases/informix/InformixDatabaseMeta.java | 9 +-
.../InformixFieldDefinitionGoldenTest.java} | 21 +-
.../informix/InformixFieldDefinitionGoldenTest.txt | 240 +++++++
.../hop/databases/ingres/IngresDatabaseMeta.java | 9 +-
.../ingres/IngresFieldDefinitionGoldenTest.java} | 21 +-
.../ingres/IngresFieldDefinitionGoldenTest.txt | 240 +++++++
.../databases/interbase/InterbaseDatabaseMeta.java | 9 +-
.../InterbaseFieldDefinitionGoldenTest.java} | 21 +-
.../InterbaseFieldDefinitionGoldenTest.txt | 240 +++++++
.../hop/databases/iris/IrisDatabaseMeta.java | 9 +-
.../iris/IrisFieldDefinitionGoldenTest.java} | 21 +-
.../iris/IrisFieldDefinitionGoldenTest.txt | 240 +++++++
.../kingbasees/KingbaseESDatabaseMeta.java | 9 +-
.../KingbaseESFieldDefinitionGoldenTest.java} | 21 +-
.../KingbaseESFieldDefinitionGoldenTest.txt | 240 +++++++
.../hop/databases/mariadb/MariaDBDatabaseMeta.java | 3 +-
.../mariadb/MariaDBFieldDefinitionGoldenTest.java} | 21 +-
.../mariadb/MariaDBFieldDefinitionGoldenTest.txt | 240 +++++++
.../hop/databases/monetdb/MonetDBDatabaseMeta.java | 6 +-
.../databases/monetdb/MonetDBDatabaseMetaTest.java | 8 +
.../monetdb/MonetDBFieldDefinitionGoldenTest.java} | 21 +-
.../monetdb/MonetDBFieldDefinitionGoldenTest.txt | 240 +++++++
.../databases/mssql/MsSqlServerDatabaseMeta.java | 9 +-
.../MsSqlServerFieldDefinitionGoldenTest.java} | 21 +-
.../mssql/MsSqlServerFieldDefinitionGoldenTest.txt | 240 +++++++
.../mssqlnative/MsSqlServerNativeDatabaseMeta.java | 18 +-
.../actions/mssqlbulkload/ActionMssqlBulkLoad.java | 3 +-
...sSqlServerNativeFieldDefinitionGoldenTest.java} | 21 +-
.../MsSqlServerNativeFieldDefinitionGoldenTest.txt | 240 +++++++
.../hop/databases/mysql/MySqlDatabaseMeta.java | 14 +-
.../mysql/MySqlFieldDefinitionGoldenTest.java} | 21 +-
.../hop/databases/mysql/MySqlTypeRulesTest.java | 95 +++
.../mysql/MySqlFieldDefinitionGoldenTest.txt | 240 +++++++
.../hop/databases/netezza/NetezzaDatabaseMeta.java | 55 +-
.../core/row/value/NettezaValueMetaBaseTest.java | 7 +-
.../netezza/NetezzaFieldDefinitionGoldenTest.java} | 21 +-
.../databases/netezza/NetezzaValueBindingTest.java | 67 ++
.../netezza/NetezzaFieldDefinitionGoldenTest.txt | 240 +++++++
.../hop/databases/oracle/OracleDatabaseMeta.java | 55 +-
.../orabulkloader/OraBulkLoaderMeta.java | 3 +-
.../oracle/OracleFieldDefinitionGoldenTest.java} | 21 +-
.../oracle/OracleNumericTypeRulesTest.java | 74 ++
.../hop/databases/oracle/OracleTypeRulesTest.java | 74 ++
.../oracle/OracleFieldDefinitionGoldenTest.txt | 240 +++++++
.../databases/oraclerdb/OracleRDBDatabaseMeta.java | 9 +-
.../OracleRDBFieldDefinitionGoldenTest.java} | 21 +-
.../OracleRDBFieldDefinitionGoldenTest.txt | 240 +++++++
.../postgresql/PostgreSqlDatabaseMeta.java | 72 +-
.../postgresql/PostgreSqlColumnDefinitionTest.java | 82 +++
.../postgresql/PostgreSqlDatabaseMetaTest.java | 4 +-
.../PostgreSqlFieldDefinitionGoldenTest.java} | 21 +-
.../postgresql/PostgreSqlTypeRulesTest.java | 78 +++
.../postgresql/PostgreSqlValueBindingTest.java | 80 +++
.../PostgreSqlFieldDefinitionGoldenTest.txt | 240 +++++++
.../databases/redshift/RedshiftDatabaseMeta.java | 3 +-
.../RedshiftFieldDefinitionGoldenTest.java} | 21 +-
.../redshift/RedshiftFieldDefinitionGoldenTest.txt | 240 +++++++
.../hop/databases/sapdb/SAPDBDatabaseMeta.java | 9 +-
.../sapdb/SAPDBFieldDefinitionGoldenTest.java} | 21 +-
.../sapdb/SAPDBFieldDefinitionGoldenTest.txt | 240 +++++++
.../singlestore/SingleStoreDatabaseMeta.java | 10 +-
.../SingleStoreFieldDefinitionGoldenTest.java} | 21 +-
.../SingleStoreFieldDefinitionGoldenTest.txt | 240 +++++++
.../databases/snowflake/SnowflakeDatabaseMeta.java | 6 +-
.../addsnowflakeid/AddSnowflakeIdMeta.java | 3 +-
.../actions/snowflake/WarehouseManager.java | 3 +-
.../SnowflakeFieldDefinitionGoldenTest.java} | 21 +-
.../SnowflakeFieldDefinitionGoldenTest.txt | 240 +++++++
.../hop/databases/sqlbase/GuptaDatabaseMeta.java | 9 +-
.../sqlbase/GuptaFieldDefinitionGoldenTest.java} | 21 +-
.../sqlbase/GuptaFieldDefinitionGoldenTest.txt | 240 +++++++
.../hop/databases/sqlite/SqliteDatabaseMeta.java | 30 +-
.../sqlite/SqliteFieldDefinitionGoldenTest.java} | 21 +-
.../hop/databases/sqlite/SqliteTypeRulesTest.java | 53 ++
.../sqlite/SqliteFieldDefinitionGoldenTest.txt | 240 +++++++
.../hop/databases/sybase/SybaseDatabaseMeta.java | 9 +-
.../sybase/SybaseFieldDefinitionGoldenTest.java} | 21 +-
.../sybase/SybaseFieldDefinitionGoldenTest.txt | 240 +++++++
.../databases/sybaseiq/SybaseIQDatabaseMeta.java | 34 +-
.../SybaseIQFieldDefinitionGoldenTest.java} | 21 +-
.../sybaseiq/SybaseIQFieldDefinitionGoldenTest.txt | 240 +++++++
.../databases/teradata/TeradataDatabaseMeta.java | 25 +-
.../pipeline/transforms/terafast/TeraFastMeta.java | 3 +-
.../TeradataFieldDefinitionGoldenTest.java} | 21 +-
.../databases/teradata/TeradataTypeRulesTest.java | 54 ++
.../teradata/TeradataFieldDefinitionGoldenTest.txt | 240 +++++++
.../databases/universe/UniVerseDatabaseMeta.java | 9 +-
.../UniVerseFieldDefinitionGoldenTest.java} | 21 +-
.../universe/UniVerseFieldDefinitionGoldenTest.txt | 240 +++++++
.../vectorwise/VectorWiseDatabaseMeta.java | 9 +-
.../VectorWiseFieldDefinitionGoldenTest.java} | 21 +-
.../VectorWiseFieldDefinitionGoldenTest.txt | 240 +++++++
.../hop/databases/vertica/VerticaDatabaseMeta.java | 9 +-
.../Vertica5FieldDefinitionGoldenTest.java} | 21 +-
.../vertica/VerticaFieldDefinitionGoldenTest.java} | 21 +-
.../vertica/Vertica5FieldDefinitionGoldenTest.txt | 240 +++++++
.../vertica/VerticaFieldDefinitionGoldenTest.txt | 240 +++++++
.../databricks/DatabricksDatabaseMeta.java | 12 +-
.../DatabricksFieldDefinitionGoldenTest.java} | 21 +-
.../DatabricksFieldDefinitionGoldenTest.txt | 240 +++++++
.../java/org/apache/hop/uuid/ValueMetaUuid.java | 9 +-
206 files changed, 18599 insertions(+), 1226 deletions(-)
diff --git a/.gitignore b/.gitignore
index 26af981e96..2975fcb11d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -97,3 +97,11 @@ google-key-apache-hop-it.json
# (it checks a folder ending in .jar isn't treated as a layered file system).
# Without this the *.jar rule above silently keeps it out of the commit.
!plugins/tech/sftp/src/test/resources/test-data/read-tests/dir1/subdir4.jar/
+
+# Hop writes its HOP_CONFIG_FOLDER content into the current working directory when
+# that variable isn't set, so running unit tests drops a config folder into the module
+# (e.g. core/config/security/users.json from HopUserStoreTest). Test artifacts, not sources.
+# Deliberately NOT a blanket "config/": ~20 tracked directories are named config, including
+# Java packages (org/apache/hop/core/config) and assemblies/static/src/main/resources/config.
+/core/config/
+config/security/
diff --git a/core/src/main/java/org/apache/hop/core/HopClientEnvironment.java b/core/src/main/java/org/apache/hop/core/HopClientEnvironment.java
index fdf358bf67..57f9fa0755 100644
--- a/core/src/main/java/org/apache/hop/core/HopClientEnvironment.java
+++ b/core/src/main/java/org/apache/hop/core/HopClientEnvironment.java
@@ -25,6 +25,7 @@ import java.util.List;
import java.util.Set;
import org.apache.hop.core.config.HopResolvedSettings;
import org.apache.hop.core.database.DatabasePluginType;
+import org.apache.hop.core.database.types.DatabaseTypeRulesPluginType;
import org.apache.hop.core.encryption.Encr;
import org.apache.hop.core.encryption.TwoWayPasswordEncoderPluginType;
import org.apache.hop.core.exception.HopException;
@@ -80,6 +81,7 @@ public class HopClientEnvironment {
LoggingPluginType.getInstance(),
ValueMetaPluginType.getInstance(),
DatabasePluginType.getInstance(),
+ DatabaseTypeRulesPluginType.getInstance(),
ExtensionPointPluginType.getInstance(),
TwoWayPasswordEncoderPluginType.getInstance(),
VariableResolverPluginType.getInstance(),
diff --git a/core/src/main/java/org/apache/hop/core/database/BaseDatabaseMeta.java b/core/src/main/java/org/apache/hop/core/database/BaseDatabaseMeta.java
index 120fe5ab75..b108527c70 100644
--- a/core/src/main/java/org/apache/hop/core/database/BaseDatabaseMeta.java
+++ b/core/src/main/java/org/apache/hop/core/database/BaseDatabaseMeta.java
@@ -34,6 +34,9 @@ import java.util.stream.Stream;
import lombok.Getter;
import lombok.Setter;
import org.apache.hop.core.Const;
+import org.apache.hop.core.database.types.ColumnContext;
+import org.apache.hop.core.database.types.DatabaseTypeMapper;
+import org.apache.hop.core.database.types.IValueBinding;
import org.apache.hop.core.exception.HopDatabaseException;
import org.apache.hop.core.exception.HopPluginException;
import org.apache.hop.core.exception.HopRuntimeException;
@@ -724,6 +727,53 @@ public abstract class BaseDatabaseMeta implements Cloneable, IDatabase {
return true;
}
+ /**
+ * The definition of a single column, for whatever DDL is being assembled.
+ *
+ * <p>Dialects call this rather than {@link #getFieldDefinition} so that a column keeps the same
+ * spelling in an ALTER TABLE as it has in the CREATE TABLE. The type rules get first say; only
+ * when no rule claims the value does the dialect's own hand written mapping answer.
+ *
+ * <p>There are no variables to resolve with here. A dialect assembles an ALTER TABLE from a
+ * method that has never carried an {@link IVariables}, and adding one would move the statement
+ * body to a new signature, which would quietly stop calling the override of anything that
+ * subclasses a dialect. A rule that keys off a variable therefore only fires on the CREATE path,
+ * which does have them; none do today.
+ *
+ * @param v the value to describe
+ * @param tk the name of the technical key field
+ * @param pk the name of the primary key field
+ * @param useAutoIncrement whether this field uses auto increment
+ * @param addFieldName whether to prefix the definition with the column name
+ * @param addCr whether to end the definition with a carriage return
+ * @param purpose what the definition is going into
+ * @return the column definition
+ */
+ public String getColumnDefinition(
+ IValueMeta v,
+ String tk,
+ String pk,
+ boolean useAutoIncrement,
+ boolean addFieldName,
+ boolean addCr,
+ ColumnContext.Purpose purpose) {
+ ColumnContext context =
+ new ColumnContext(purpose, tk, pk, useAutoIncrement, addFieldName, addCr);
+ String columnType = DatabaseTypeMapper.getColumnType(null, this, v, context);
+ if (!Utils.isEmpty(columnType)) {
+ return (addFieldName ? v.getName() + " " : "") + columnType + (addCr ? Const.CR : "");
+ }
+
+ // The same fallback order DatabaseMeta uses for a CREATE TABLE, so that the two agree.
+ String definition =
+ v.getDatabaseColumnTypeDefinition(this, tk, pk, useAutoIncrement, addFieldName, addCr);
+ if (!Utils.isEmpty(definition)) {
+ return definition;
+ }
+
+ return getFieldDefinition(v, tk, pk, useAutoIncrement, addFieldName, addCr);
+ }
+
/**
* Generates the SQL statement to drop a column from the specified table
*
@@ -1205,6 +1255,12 @@ public abstract class BaseDatabaseMeta implements Cloneable, IDatabase {
}
@Override
+ /**
+ * @deprecated An Oracle-specific option that has no business on the interface every dialect *
+ * implements. Oracle now expresses it through its own {@link #getTypeRules()}; this accessor
+ * * remains so that existing dialects and callers keep working.
+ */
+ @Deprecated(since = "2.20")
public boolean isStrictBigNumberInterpretation() {
return false;
}
@@ -1608,6 +1664,13 @@ public abstract class BaseDatabaseMeta implements Cloneable, IDatabase {
* on.
*/
@Override
+ /**
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather * than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so * existing implementations
+ * keep working, and will be removed once the migration completes.
+ */
+ @Deprecated(since = "2.20")
public boolean isMySqlVariant() {
return false;
}
@@ -1617,6 +1680,13 @@ public abstract class BaseDatabaseMeta implements Cloneable, IDatabase {
* on.
*/
@Override
+ /**
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather * than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so * existing implementations
+ * keep working, and will be removed once the migration completes.
+ */
+ @Deprecated(since = "2.20")
public boolean isPostgresVariant() {
return false;
}
@@ -1625,6 +1695,13 @@ public abstract class BaseDatabaseMeta implements Cloneable, IDatabase {
* @return true if the database is a Teradata variant.
*/
@Override
+ /**
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather * than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so * existing implementations
+ * keep working, and will be removed once the migration completes.
+ */
+ @Deprecated(since = "2.20")
public boolean isTeradataVariant() {
return false;
}
@@ -1633,6 +1710,13 @@ public abstract class BaseDatabaseMeta implements Cloneable, IDatabase {
* @return true if the database is a Sybase variant.
*/
@Override
+ /**
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather * than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so * existing implementations
+ * keep working, and will be removed once the migration completes.
+ */
+ @Deprecated(since = "2.20")
public boolean isSybaseVariant() {
return false;
}
@@ -1641,6 +1725,13 @@ public abstract class BaseDatabaseMeta implements Cloneable, IDatabase {
* @return true if the database is a Sybase variant.
*/
@Override
+ /**
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather * than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so * existing implementations
+ * keep working, and will be removed once the migration completes.
+ */
+ @Deprecated(since = "2.20")
public boolean isSybaseIQVariant() {
return false;
}
@@ -1649,6 +1740,13 @@ public abstract class BaseDatabaseMeta implements Cloneable, IDatabase {
* @return true if the database is a Neoview variant.
*/
@Override
+ /**
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather * than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so * existing implementations
+ * keep working, and will be removed once the migration completes.
+ */
+ @Deprecated(since = "2.20")
public boolean isNeoviewVariant() {
return false;
}
@@ -1657,13 +1755,25 @@ public abstract class BaseDatabaseMeta implements Cloneable, IDatabase {
* @return true if the database is a Exasol variant.
*/
@Override
+ /**
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather * than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so * existing implementations
+ * keep working, and will be removed once the migration completes.
+ */
+ @Deprecated(since = "2.20")
public boolean isExasolVariant() {
return false;
}
/**
* @return true if the database is a DuckDb variant.
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so existing implementations
+ * keep working, and will be removed once the migration completes.
*/
+ @Deprecated(since = "2.20")
public boolean isDuckDbVariant() {
return false;
}
@@ -1672,6 +1782,13 @@ public abstract class BaseDatabaseMeta implements Cloneable, IDatabase {
* @return true if the database is a Informix variant.
*/
@Override
+ /**
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather * than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so * existing implementations
+ * keep working, and will be removed once the migration completes.
+ */
+ @Deprecated(since = "2.20")
public boolean isInformixVariant() {
return false;
}
@@ -1680,6 +1797,13 @@ public abstract class BaseDatabaseMeta implements Cloneable, IDatabase {
* @return true if the database is a MS SQL Server (native) variant.
*/
@Override
+ /**
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather * than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so * existing implementations
+ * keep working, and will be removed once the migration completes.
+ */
+ @Deprecated(since = "2.20")
public boolean isMsSqlServerNativeVariant() {
return false;
}
@@ -1688,6 +1812,13 @@ public abstract class BaseDatabaseMeta implements Cloneable, IDatabase {
* @return true if the database is a MS SQL Server variant.
*/
@Override
+ /**
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather * than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so * existing implementations
+ * keep working, and will be removed once the migration completes.
+ */
+ @Deprecated(since = "2.20")
public boolean isMsSqlServerVariant() {
return false;
}
@@ -1696,6 +1827,13 @@ public abstract class BaseDatabaseMeta implements Cloneable, IDatabase {
* @return true if the database is an Oracle variant.
*/
@Override
+ /**
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather * than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so * existing implementations
+ * keep working, and will be removed once the migration completes.
+ */
+ @Deprecated(since = "2.20")
public boolean isOracleVariant() {
return false;
}
@@ -1704,6 +1842,13 @@ public abstract class BaseDatabaseMeta implements Cloneable, IDatabase {
* @return true if the database is an Netezza variant.
*/
@Override
+ /**
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather * than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so * existing implementations
+ * keep working, and will be removed once the migration completes.
+ */
+ @Deprecated(since = "2.20")
public boolean isNetezzaVariant() {
return false;
}
@@ -1712,6 +1857,13 @@ public abstract class BaseDatabaseMeta implements Cloneable, IDatabase {
* @return true if the database is a SQLite variant.
*/
@Override
+ /**
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather * than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so * existing implementations
+ * keep working, and will be removed once the migration completes.
+ */
+ @Deprecated(since = "2.20")
public boolean isSqliteVariant() {
return false;
}
@@ -1803,7 +1955,18 @@ public abstract class BaseDatabaseMeta implements Cloneable, IDatabase {
@Override
public Object getValueFromResultSet(ResultSet rs, IValueMeta val, int i)
throws HopDatabaseException {
-
+ // Asked here rather than inside ValueMetaBase, so that a value type which handles its own
+ // reading still gets the binding its database declared.
+ IValueBinding binding = DatabaseTypeMapper.getBinding(this, val);
+ if (binding != null) {
+ try {
+ Object data = binding.read(this, val, rs, i + 1);
+ return rs.wasNull() ? null : data;
+ } catch (SQLException e) {
+ throw new HopDatabaseException(
+ "Unable to read value '" + val.getName() + "' from the result set", e);
+ }
+ }
return val.getValueFromResultSet(this, rs, i);
}
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 452c01cd7f..3b9ed239b3 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
@@ -59,6 +59,9 @@ import org.apache.hop.core.IProgressMonitor;
import org.apache.hop.core.Result;
import org.apache.hop.core.RowMetaAndData;
import org.apache.hop.core.database.map.DatabaseConnectionMap;
+import org.apache.hop.core.database.types.DatabaseColumn;
+import org.apache.hop.core.database.types.DatabaseTypeMapper;
+import org.apache.hop.core.database.types.IValueBinding;
import org.apache.hop.core.encryption.Encr;
import org.apache.hop.core.exception.HopDatabaseBatchException;
import org.apache.hop.core.exception.HopDatabaseException;
@@ -108,7 +111,7 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
private static final Map<String, Set<String>> registeredDrivers = new HashMap<>();
- private final DatabaseMeta databaseMeta;
+ @Getter private final DatabaseMeta databaseMeta;
private static final String DATA_SERVICES_PLUGIN_ID = "HopThin";
private static final String CONST_INOUT = "INOUT";
@@ -133,13 +136,13 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
private int commitsize;
- private Connection connection;
+ @Getter @Setter private Connection connection;
private Statement selStmt;
private PreparedStatement pstmt;
- private PreparedStatement prepStatementLookup;
- private PreparedStatement prepStatementUpdate;
- private PreparedStatement prepStatementInsert;
+ @Getter private PreparedStatement prepStatementLookup;
+ @Getter private PreparedStatement prepStatementUpdate;
+ @Getter private PreparedStatement prepStatementInsert;
private PreparedStatement pstmtSeq;
private CallableStatement cstmt;
@@ -163,14 +166,9 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
private final ILoggingObject parentLoggingObject;
private static final String TABLES_META_DATA_TABLE_NAME = "TABLE_NAME";
- /**
- * Number of times a connection was opened using this object. Only used in the context of a
- * database connection map
- */
- private volatile int opened;
+ @Getter private volatile int opened;
- /** The copy is equal to opened at the time of creation. */
- private volatile int copy;
+ @Getter private volatile int copy;
@Getter @Setter private String connectionGroup;
@Getter @Setter private String partitionId;
@@ -179,7 +177,7 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
private LogLevel logLevel = DefaultLogLevel.getLogLevel();
- private String containerObjectId;
+ @Setter private String containerObjectId;
@Getter @Setter private int nrExecutedCommits;
@@ -260,23 +258,6 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
}
}
- /**
- * Allows for the injection of a "life" connection, generated by a piece of software outside of
- * Hop.
- *
- * @param connection
- */
- public void setConnection(Connection connection) {
- this.connection = connection;
- }
-
- /**
- * @return Returns the connection.
- */
- public Connection getConnection() {
- return connection;
- }
-
/**
* Set the maximum number of records to retrieve from a query.
*
@@ -303,27 +284,6 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
}
}
- /**
- * @return Returns the prepStatementInsert.
- */
- public PreparedStatement getPrepStatementInsert() {
- return prepStatementInsert;
- }
-
- /**
- * @return Returns the prepStatementLookup.
- */
- public PreparedStatement getPrepStatementLookup() {
- return prepStatementLookup;
- }
-
- /**
- * @return Returns the prepStatementUpdate.
- */
- public PreparedStatement getPrepStatementUpdate() {
- return prepStatementUpdate;
- }
-
/**
* Open the database connection.
*
@@ -526,6 +486,8 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
// one variables
properties.put("user", Const.NVL(username, " "));
properties.put("password", Const.NVL(password, ""));
+ // Deprecated path. Building the URL is the dialect's job; this is here only because a
+ // BaseDatabaseMeta subclass cannot resolve variables. It moves once it can.
if (databaseMeta.getIDatabase().isMsSqlServerNativeVariant()) {
// Handle MSSQL Instance name. Would rather this was handled in the dialect
// but cannot (without refactor) get to variablespace for variable substitution from
@@ -800,6 +762,8 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
// Canceling statements only if we're not streaming results on MySQL with
// the v3 driver
//
+ // Deprecated path. A driver quirk, not a column type: the dialect should answer whether
+ // cancelling is safe while streaming, rather than core naming the vendor.
if (databaseMeta.isMySqlVariant()
&& databaseMeta.isStreamingResults()
&& getDatabaseMetaData().getDriverMajorVersion() == 3) {
@@ -1100,7 +1064,19 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
public void setValue(PreparedStatement ps, IValueMeta v, Object object, int pos)
throws HopDatabaseException {
-
+ // Asked here rather than inside ValueMetaBase, so that a value type which handles its own
+ // writing still gets the binding its database declared.
+ IDatabase iDatabase = databaseMeta.getIDatabase();
+ IValueBinding binding = DatabaseTypeMapper.getBinding(iDatabase, v);
+ if (binding != null) {
+ try {
+ binding.write(iDatabase, v, ps, pos, object);
+ return;
+ } catch (SQLException | HopValueException e) {
+ throw new HopDatabaseException(
+ "Unable to write value '" + v.getName() + "' to the prepared statement", e);
+ }
+ }
v.setPreparedStatementValue(databaseMeta, ps, pos, object);
}
@@ -1740,6 +1716,8 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
if (canWeSetFetchSize(pstmt)) {
int maxRows = pstmt.getMaxRows();
int fs = Const.FETCH_SIZE <= maxRows ? maxRows : Const.FETCH_SIZE;
+ // Deprecated path. How large a fetch this driver tolerates belongs to the dialect,
+ // alongside isFetchSizeSupported.
if (databaseMeta.isMySqlVariant()) {
setMysqlFetchSize(pstmt, fs, maxRows);
} else {
@@ -1765,6 +1743,7 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
if (canWeSetFetchSize(selStmt)) {
int fs =
Const.FETCH_SIZE <= selStmt.getMaxRows() ? selStmt.getMaxRows() : Const.FETCH_SIZE;
+ // Deprecated path. The streaming fetch size a driver wants belongs to the dialect.
if (databaseMeta.getIDatabase().isMySqlVariant() && databaseMeta.isStreamingResults()) {
selStmt.setFetchSize(Integer.MIN_VALUE);
} else {
@@ -1788,6 +1767,8 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
// to get the length of a String field. So, on MySQL, we ingore the length
// of Strings in result rows.
//
+ // Deprecated path. Whether reported string lengths can be trusted is a driver property,
+ // so the dialect should say so rather than core recognising MySQL.
rowMeta = getRowInfo(res.getMetaData(), databaseMeta.isMySqlVariant(), lazyConversion);
} catch (SQLException ex) {
throw new HopDatabaseException("An error occurred executing SQL: " + Const.CR + sql, ex);
@@ -1802,6 +1783,8 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
private boolean canWeSetFetchSize(Statement statement) throws SQLException {
return databaseMeta.isFetchSizeSupported()
+ // Deprecated path. Which drivers accept a fetch size is a capability, and the two named
+ // here should answer it themselves.
&& (statement.getMaxRows() > 0
|| databaseMeta.getIDatabase().isPostgresVariant()
|| (databaseMeta.isMySqlVariant() && databaseMeta.isStreamingResults()));
@@ -1823,6 +1806,7 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
int maxRows = ps.getMaxRows();
int fs = Const.FETCH_SIZE <= maxRows ? maxRows : Const.FETCH_SIZE;
// mysql have some restriction on fetch size assignment
+ // Deprecated path. See setMysqlFetchSize: a fetch size restriction the dialect should own.
if (databaseMeta.isMySqlVariant()) {
setMysqlFetchSize(ps, fs, maxRows);
} else {
@@ -1853,6 +1837,7 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
//
if (ps != openQueryStatement || openQueryRowMeta == null) {
log.snap(Metrics.METRIC_DATABASE_GET_ROW_META_START, databaseMeta.getName());
+ // Deprecated path. Same reported-length question as in openQuery above.
openQueryRowMeta = getRowInfo(res.getMetaData(), databaseMeta.isMySqlVariant(), false);
openQueryStatement = ps;
log.snap(Metrics.METRIC_DATABASE_GET_ROW_META_STOP, databaseMeta.getName());
@@ -2119,12 +2104,15 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
IDatabase iDatabase = databaseMeta.getIDatabase();
// Exasol does not support explicit handling of indexes
+ // Deprecated path. Whether a database has indexes at all belongs in its own DDL, next to
+ // the other index handling.
if (iDatabase.isExasolVariant()) {
return "";
}
crIndex.append("CREATE ");
+ // Deprecated path. Whether a technical key implies a unique index is dialect DDL.
if (unique || (tk && iDatabase.isSybaseVariant())) {
crIndex.append("UNIQUE ");
}
@@ -2418,12 +2406,16 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
if ((inform == null
// Hack for MSSQL jtds 1.2 when using xxx NOT IN yyy we have to use a
// prepared statement (see BugID 3214)
+ // Deprecated path. A jtds driver bug, which the dialect should declare rather than
+ // core
+ // recognising the vendor.
&& databaseMeta.getIDatabase().isMsSqlServerVariant())
|| databaseMeta.getIDatabase().isSupportsResultSetMetadataRetrievalOnly()) {
selStmt =
connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
try {
if (databaseMeta.isFetchSizeSupported() && selStmt.getMaxRows() >= 1) {
+ // Deprecated path. Another fetch size restriction belonging to the dialect.
if (databaseMeta.getIDatabase().isMySqlVariant()) {
selStmt.setFetchSize(Integer.MIN_VALUE);
} else {
@@ -2552,6 +2544,8 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
// Extract the name from the result set metadata...
//
String name;
+ // Deprecated path. getLegacyColumnName is already on the dialect; only the decision to
+ // call it is still made by vendor name, and the dialect can make that too.
if (databaseMeta.isMySqlVariant()) {
name =
databaseMeta.getIDatabase().getLegacyColumnName(getDatabaseMetaData(), rm, columnIndex);
@@ -2596,9 +2590,12 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
}
/**
- * Map well-known JDBC {@link Types} values to Hop value metadata using hard-coded rules. Returns
- * {@code null} when the SQL type is not one of the standard mappings so that specialized value
- * meta plugins can handle it.
+ * Map well-known JDBC {@link Types} values to Hop value metadata. Returns {@code null} when the
+ * SQL type is not one of the standard mappings so that specialized value meta plugins can handle
+ * it.
+ *
+ * <p>The dialect-specific branches this used to carry are the dialects' own rules now, so this
+ * asks {@link DatabaseTypeMapper}: the dialect's rules first, then the standard JDBC mapping.
*/
private IValueMeta getDataTypeFromKnownSqlType(
ResultSetMetaData rm,
@@ -2607,244 +2604,9 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
boolean lazyConversion,
String name)
throws HopDatabaseException, SQLException {
- int sqlType = rm.getColumnType(columnIndex);
- int valtype = IValueMeta.TYPE_NONE;
- int length = -1;
- int precision = -1;
- boolean isClob = false;
- boolean signed = false;
- try {
- signed = rm.isSigned(columnIndex);
- } catch (Exception ignored) {
- // This JDBC driver doesn't support the isSigned method.
- }
-
- switch (sqlType) {
- case Types.CHAR,
- Types.VARCHAR,
- Types.NVARCHAR,
- Types.LONGVARCHAR,
- Types.NCHAR,
- Types.LONGNVARCHAR:
- valtype = IValueMeta.TYPE_STRING;
- if (!ignoreLength) {
- length = rm.getColumnDisplaySize(columnIndex);
- }
- break;
-
- case Types.CLOB, Types.NCLOB:
- valtype = IValueMeta.TYPE_STRING;
- length = DatabaseMeta.CLOB_LENGTH;
- isClob = true;
- break;
-
- case Types.BIGINT:
- if (signed) {
- valtype = IValueMeta.TYPE_INTEGER;
- precision = 0;
- length = 15;
- } else {
- valtype = IValueMeta.TYPE_BIGNUMBER;
- precision = 0;
- length = 16;
- }
- break;
-
- case Types.INTEGER:
- valtype = IValueMeta.TYPE_INTEGER;
- precision = 0;
- length = 9;
- break;
-
- case Types.SMALLINT:
- valtype = IValueMeta.TYPE_INTEGER;
- precision = 0;
- length = 4;
- break;
-
- case Types.TINYINT:
- valtype = IValueMeta.TYPE_INTEGER;
- precision = 0;
- length = 2;
- break;
-
- case Types.DECIMAL, Types.DOUBLE, Types.FLOAT, Types.REAL, Types.NUMERIC:
- valtype = IValueMeta.TYPE_NUMBER;
- length = rm.getPrecision(columnIndex);
- precision = rm.getScale(columnIndex);
- // Precision in the database means the number of significant digits.
- // In Hop, it means the digits before the decimal.
- //
- if (length > 0 && length > precision) {
- length -= precision;
- }
-
- if (length >= 126) {
- length = -1;
- }
- if (precision >= 126) {
- precision = -1;
- }
-
- if (sqlType == Types.DOUBLE || sqlType == Types.FLOAT || sqlType == Types.REAL) {
- if (precision == 0) {
- precision = -1;
- }
-
- if (databaseMeta.getIDatabase().isPostgresVariant()
- && sqlType == Types.DOUBLE
- && precision >= 16
- && length >= 16) {
- precision = -1;
- length = -1;
- }
-
- if (databaseMeta.getIDatabase().isMySqlVariant() && precision >= length) {
- precision = -1;
- length = -1;
- }
-
- if (length > 15 || precision > 15) {
- valtype = IValueMeta.TYPE_BIGNUMBER;
- }
- } else {
- if (precision == 0) {
- if (length <= 18 && length > 0) {
- valtype = IValueMeta.TYPE_INTEGER;
- } else if (length > 18) {
- valtype = IValueMeta.TYPE_BIGNUMBER;
- }
- } else if (length > 15 || precision > 15) {
- valtype = IValueMeta.TYPE_BIGNUMBER;
- }
- }
-
- if (databaseMeta.getIDatabase().isPostgresVariant()
- && sqlType == Types.NUMERIC
- && length == 0
- && precision == 0) {
- valtype = IValueMeta.TYPE_BIGNUMBER;
- length = -1;
- precision = -1;
- }
-
- if (databaseMeta.getIDatabase().isOracleVariant()) {
- if (precision == 0 && length == 38) {
- valtype =
- databaseMeta.getIDatabase().isStrictBigNumberInterpretation()
- ? IValueMeta.TYPE_BIGNUMBER
- : IValueMeta.TYPE_INTEGER;
- }
- if (precision <= 0 && length <= 0) {
- valtype = IValueMeta.TYPE_BIGNUMBER;
- length = -1;
- precision = -1;
- }
- }
- break;
-
- case Types.TIMESTAMP:
- if (databaseMeta.supportsTimestampDataType()) {
- valtype = IValueMeta.TYPE_TIMESTAMP;
- length = rm.getScale(columnIndex);
- } else {
- valtype = IValueMeta.TYPE_DATE;
- }
- break;
-
- case Types.DATE, Types.TIME:
- valtype = IValueMeta.TYPE_DATE;
- if (databaseMeta.getIDatabase().isMySqlVariant()) {
- String property =
- databaseMeta.getConnectionProperties(this).getProperty("yearIsDateType");
- if (property != null
- && property.equalsIgnoreCase("false")
- && rm.getColumnTypeName(columnIndex).equalsIgnoreCase("YEAR")) {
- valtype = IValueMeta.TYPE_INTEGER;
- precision = 0;
- length = 4;
- }
- }
- if (databaseMeta.getIDatabase().isTeradataVariant()) {
- precision = 1;
- }
- break;
-
- case Types.BOOLEAN, Types.BIT:
- valtype = IValueMeta.TYPE_BOOLEAN;
- break;
-
- case Types.BINARY, Types.BLOB, Types.VARBINARY, Types.LONGVARBINARY:
- valtype = IValueMeta.TYPE_BINARY;
-
- if (databaseMeta.isDisplaySizeTwiceThePrecision()
- && (2 * rm.getPrecision(columnIndex)) == rm.getColumnDisplaySize(columnIndex)) {
- length = rm.getPrecision(columnIndex);
- } else if (databaseMeta.getIDatabase().isOracleVariant()
- && (sqlType == Types.VARBINARY || sqlType == Types.LONGVARBINARY)) {
- valtype = IValueMeta.TYPE_STRING;
- length = rm.getColumnDisplaySize(columnIndex);
- } else if (databaseMeta.isMySqlVariant()
- && (sqlType == Types.VARBINARY || sqlType == Types.LONGVARBINARY)) {
- length = -1;
- } else if (databaseMeta.getIDatabase().isSqliteVariant()) {
- valtype = IValueMeta.TYPE_STRING;
- } else {
- length = -1;
- }
- precision = -1;
- break;
-
- default:
- return null;
- }
-
- try {
- IValueMeta valueMeta = ValueMetaFactory.createValueMeta(name, valtype);
- valueMeta.setLength(length);
- valueMeta.setPrecision(precision);
- valueMeta.setLargeTextField(isClob);
-
- setOriginalColumnMetadata(valueMeta, rm, columnIndex, ignoreLength);
-
- if (lazyConversion && valtype == IValueMeta.TYPE_STRING) {
- valueMeta.setStorageType(IValueMeta.STORAGE_TYPE_BINARY_STRING);
- IValueMeta storageMetaData =
- ValueMetaFactory.cloneValueMeta(valueMeta, IValueMeta.TYPE_STRING);
- storageMetaData.setStorageType(IValueMeta.STORAGE_TYPE_NORMAL);
- valueMeta.setStorageMetadata(storageMetaData);
- }
-
- IValueMeta customized =
- databaseMeta.getIDatabase().customizeValueFromSqlType(valueMeta, rm, columnIndex);
- return customized == null ? valueMeta : customized;
- } catch (Exception e) {
- throw new HopDatabaseException(
- "Error determining value metadata from SQL resultset metadata", e);
- }
- }
-
- private void setOriginalColumnMetadata(
- IValueMeta valueMeta, ResultSetMetaData rm, int columnIndex, boolean ignoreLength)
- throws SQLException {
- valueMeta.setComments(rm.getColumnLabel(columnIndex));
- valueMeta.setOriginalColumnType(rm.getColumnType(columnIndex));
- valueMeta.setOriginalColumnTypeName(rm.getColumnTypeName(columnIndex));
-
- int originalPrecision = -1;
- if (!ignoreLength) {
- originalPrecision = rm.getPrecision(columnIndex);
- }
- valueMeta.setOriginalPrecision(originalPrecision);
- valueMeta.setOriginalScale(rm.getScale(columnIndex));
-
- boolean originalSigned = false;
- try {
- originalSigned = rm.isSigned(columnIndex);
- } catch (Exception ignored) {
- // This JDBC driver doesn't support the isSigned method.
- }
- valueMeta.setOriginalSigned(originalSigned);
+ DatabaseColumn column = DatabaseColumn.of(rm, columnIndex, name);
+ return DatabaseTypeMapper.getValueMeta(
+ this, databaseMeta, column, ignoreLength, lazyConversion);
}
private IValueMeta getDataTypeFromAllValueMetaLegacy(
@@ -3444,7 +3206,7 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
}
IValueMeta v = fields.getValueMeta(i);
- retval.append(databaseMeta.getFieldDefinition(v, tk, pk, useAutoIncrement));
+ retval.append(databaseMeta.getFieldDefinition(this, v, tk, pk, useAutoIncrement));
}
// At the end, before the closing of the statement, we might need to add
// some constraints...
@@ -3461,11 +3223,6 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
retval.append(databaseMeta.getIDatabase().getDataTablespaceDDL(variables, databaseMeta));
- if (pk == null && tk == null && databaseMeta.getIDatabase().isNeoviewVariant()) {
- retval.append("NO PARTITION"); // use this as a default when no pk/tk is
- // there, otherwise you get an error
- }
-
if (semicolon) {
retval.append(";");
}
@@ -3534,8 +3291,10 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
IValueMeta desiredField = fields.getValueMeta(i);
IValueMeta currentField = tabFields.searchValueMeta(desiredField.getName());
if (desiredField != null && currentField != null) {
- String desiredDDL = databaseMeta.getFieldDefinition(desiredField, tk, pk, useAutoIncrement);
- String currentDDL = databaseMeta.getFieldDefinition(currentField, tk, pk, useAutoIncrement);
+ String desiredDDL =
+ databaseMeta.getFieldDefinition(this, desiredField, tk, pk, useAutoIncrement);
+ String currentDDL =
+ databaseMeta.getFieldDefinition(this, currentField, tk, pk, useAutoIncrement);
boolean mod = !desiredDDL.equalsIgnoreCase(currentDDL);
if (mod) {
@@ -3956,14 +3715,10 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
*/
public List<Object[]> getFirstRows(String tableName, int limit, IProgressMonitor monitor)
throws HopDatabaseException {
- String sql = "SELECT";
- if (databaseMeta.getIDatabase().isNeoviewVariant()) {
- sql += " [FIRST " + limit + "]";
- } else if (databaseMeta.getIDatabase().isSybaseIQVariant()) {
- // improve support for Sybase IQ
- sql += " TOP " + limit + " ";
- }
- sql += " * FROM " + tableName;
+ // How a database limits rows is its own syntax, so it is asked rather than recognised here.
+ // The row count is capped while reading in any case, so a dialect with no limit clause simply
+ // fetches a little more than it needs.
+ String sql = "SELECT * FROM " + tableName;
if (limit > 0) {
sql += databaseMeta.getLimitClause(limit);
@@ -4444,13 +4199,6 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
return commitsize <= 0;
}
- /**
- * @return Returns the databaseMeta.
- */
- public DatabaseMeta getDatabaseMeta() {
- return databaseMeta;
- }
-
/**
* Lock a tables in the database for write operations
*
@@ -4497,13 +4245,6 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
}
}
- /**
- * @return the opened
- */
- public int getOpened() {
- return opened;
- }
-
/**
* @param opened the opened to set
*/
@@ -4511,13 +4252,6 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
this.opened = opened;
}
- /**
- * @return the copy
- */
- public int getCopy() {
- return copy;
- }
-
/**
* @param copy the copy to set
*/
@@ -4850,6 +4584,8 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
Date date = fields.getDate(r, i);
if (Utils.isEmpty(dateFormat)) {
+ // Deprecated path. How a date literal is spelled in generated SQL is dialect
+ // syntax.
if (databaseMeta.getIDatabase().isOracleVariant()) {
if (fieldDateFormatters[i] == null) {
fieldDateFormatters[i] = new java.text.SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
@@ -5031,13 +4767,6 @@ public class Database implements IVariables, ILoggingObject, AutoCloseable {
return containerObjectId;
}
- /**
- * @param containerObjectId the execution container Object id to set
- */
- public void setContainerObjectId(String containerObjectId) {
- this.containerObjectId = containerObjectId;
- }
-
/** Stub */
@Override
public Date getRegistrationDate() {
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 525c136faa..9ecf163eda 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
@@ -33,6 +33,8 @@ import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.apache.hop.core.Const;
import org.apache.hop.core.RowMetaAndData;
+import org.apache.hop.core.database.types.ColumnContext;
+import org.apache.hop.core.database.types.DatabaseTypeMapper;
import org.apache.hop.core.exception.HopDatabaseException;
import org.apache.hop.core.exception.HopPluginException;
import org.apache.hop.core.exception.HopRuntimeException;
@@ -1117,7 +1119,12 @@ public class DatabaseMeta extends HopMetadataBase implements Cloneable, IHopMeta
}
public String getFieldDefinition(IValueMeta v, String tk, String pk, boolean useAutoIncrement) {
- return getFieldDefinition(v, tk, pk, useAutoIncrement, true, true);
+ return getFieldDefinition(null, v, tk, pk, useAutoIncrement, true, true);
+ }
+
+ public String getFieldDefinition(
+ IVariables variables, IValueMeta v, String tk, String pk, boolean useAutoIncrement) {
+ return getFieldDefinition(variables, v, tk, pk, useAutoIncrement, true, true);
}
public String getFieldDefinition(
@@ -1127,6 +1134,35 @@ public class DatabaseMeta extends HopMetadataBase implements Cloneable, IHopMeta
boolean useAutoIncrement,
boolean addFieldname,
boolean addCr) {
+ return getFieldDefinition(null, v, tk, pk, useAutoIncrement, addFieldname, addCr);
+ }
+
+ /**
+ * Describe a value as a column in this database.
+ *
+ * @param variables the variables to resolve with. Type rules are handed these, so a rule can read
+ * a variable or reach the metadata provider through them. May be null, in which case a rule
+ * that needs either simply does not fire.
+ */
+ public String getFieldDefinition(
+ IVariables variables,
+ IValueMeta v,
+ String tk,
+ String pk,
+ boolean useAutoIncrement,
+ boolean addFieldname,
+ boolean addCr) {
+
+ // Ask the dialect first. A database knows how it spells its own types; a value type only
+ // knows what it is. Until a dialect declares a write rule this changes nothing, because the
+ // rule lists are empty.
+ ColumnContext context =
+ new ColumnContext(
+ ColumnContext.Purpose.CREATE, tk, pk, useAutoIncrement, addFieldname, addCr);
+ String columnType = DatabaseTypeMapper.getColumnType(variables, iDatabase, v, context);
+ if (!Utils.isEmpty(columnType)) {
+ return (addFieldname ? v.getName() + " " : "") + columnType + (addCr ? Const.CR : "");
+ }
String definition =
v.getDatabaseColumnTypeDefinition(iDatabase, tk, pk, useAutoIncrement, addFieldname, addCr);
@@ -2252,6 +2288,14 @@ public class DatabaseMeta extends HopMetadataBase implements Cloneable, IHopMeta
return iDatabase.generateColumnAlias(columnIndex, suggestedName);
}
+ /**
+ * @deprecated Dialects now describe their own column types through {@link
+ * IDatabase#getTypeRules()}, which core matches by dialect plugin type and class hierarchy
+ * rather * than by vendor name. This flag is still honoured for dialects that have not
+ * migrated, so * existing implementations keep working, and will be removed once the
+ * migration completes.
+ */
+ @Deprecated(since = "2.20")
public boolean isMySqlVariant() {
return iDatabase.isMySqlVariant();
}
diff --git a/core/src/main/java/org/apache/hop/core/database/IDatabase.java b/core/src/main/java/org/apache/hop/core/database/IDatabase.java
index c9fca7c8b7..ecca2f56ff 100644
--- a/core/src/main/java/org/apache/hop/core/database/IDatabase.java
+++ b/core/src/main/java/org/apache/hop/core/database/IDatabase.java
@@ -749,6 +749,12 @@ public interface IDatabase extends Cloneable {
boolean isRequiringTransactionsOnQueries();
/** Handles the special case of Oracle where NUMBER(38) is interpreted as Integer or BigNumber */
+ /**
+ * @deprecated An Oracle-specific option that has no business on the interface every dialect *
+ * implements. Oracle now expresses it through its own {@link #getTypeRules()}; this accessor
+ * * remains so that existing dialects and callers keep working.
+ */
+ @Deprecated(since = "2.20")
boolean isStrictBigNumberInterpretation();
/**
@@ -917,76 +923,160 @@ public interface IDatabase extends Cloneable {
*/
List<SqlScriptStatement> getSqlScriptStatements(String sqlScript);
+ /**
+ * The column type rules this dialect contributes, most specific first.
+ *
+ * <p>Rules are inherited through the class hierarchy in the ordinary Java way, so a dialect that
+ * extends another starts from its rules and can prepend its own. This is the replacement for the
+ * {@code isXVariant()} methods below: instead of core asking "is this Postgres-like?" and
+ * switching on the answer, the dialect states what it does.
+ *
+ * @return the rules, empty by default
+ */
+ default java.util.List<org.apache.hop.core.database.types.IDatabaseTypeRule> getTypeRules() {
+ return java.util.List.of();
+ }
+
/**
* @return true if the database is a MySQL variant, like MySQL 5.1, InfiniDB, InfoBright, and so
* on.
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so existing implementations
+ * keep working, and will be removed once the migration completes.
*/
+ @Deprecated(since = "2.20")
boolean isMySqlVariant();
/**
* @return true if the database is a Postgres variant like Postgres, Greenplum, Redshift, and so
* on.
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so existing implementations
+ * keep working, and will be removed once the migration completes.
*/
+ @Deprecated(since = "2.20")
boolean isPostgresVariant();
/**
* @return true if the database is a Sybase variant.
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so existing implementations
+ * keep working, and will be removed once the migration completes.
*/
+ @Deprecated(since = "2.20")
boolean isSybaseVariant();
/**
* @return true if the database is a SybaseIQ variant.
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so existing implementations
+ * keep working, and will be removed once the migration completes.
*/
+ @Deprecated(since = "2.20")
boolean isSybaseIQVariant();
/**
* @return true if the database is a neoview variant.
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so existing implementations
+ * keep working, and will be removed once the migration completes.
*/
+ @Deprecated(since = "2.20")
boolean isNeoviewVariant();
/**
* @return true if the database is a DuckDB variant.
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so existing implementations
+ * keep working, and will be removed once the migration completes.
*/
+ @Deprecated(since = "2.20")
boolean isDuckDbVariant();
/**
* @return true if the database is a DuckDB variant.
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so existing implementations
+ * keep working, and will be removed once the migration completes.
*/
+ @Deprecated(since = "2.20")
boolean isExasolVariant();
/**
* @return true if the database is an Informix variant.
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so existing implementations
+ * keep working, and will be removed once the migration completes.
*/
+ @Deprecated(since = "2.20")
boolean isInformixVariant();
/**
* @return true if the database is a MS SQL Server (native) variant.
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so existing implementations
+ * keep working, and will be removed once the migration completes.
*/
+ @Deprecated(since = "2.20")
boolean isMsSqlServerNativeVariant();
/**
* @return true if the database is a MS SQL Server variant.
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so existing implementations
+ * keep working, and will be removed once the migration completes.
*/
+ @Deprecated(since = "2.20")
boolean isMsSqlServerVariant();
/**
* @return true if the database is an Oracle variant.
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so existing implementations
+ * keep working, and will be removed once the migration completes.
*/
+ @Deprecated(since = "2.20")
boolean isOracleVariant();
/**
* @return true if the database is a Netezza variant.
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so existing implementations
+ * keep working, and will be removed once the migration completes.
*/
+ @Deprecated(since = "2.20")
boolean isNetezzaVariant();
/**
* @return true if the database is a SQLite variant.
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so existing implementations
+ * keep working, and will be removed once the migration completes.
*/
+ @Deprecated(since = "2.20")
boolean isSqliteVariant();
/**
* @return true if the database is a Terradata variant.
+ * @deprecated Dialects now describe their own column types through {@link #getTypeRules()}, which
+ * core matches by dialect plugin type and class hierarchy rather than by vendor name. This
+ * flag is still honoured for dialects that have not migrated, so existing implementations
+ * keep working, and will be removed once the migration completes.
*/
+ @Deprecated(since = "2.20")
boolean isTeradataVariant();
/**
diff --git a/core/src/main/java/org/apache/hop/core/database/types/ColumnContext.java b/core/src/main/java/org/apache/hop/core/database/types/ColumnContext.java
new file mode 100644
index 0000000000..3f27058204
--- /dev/null
+++ b/core/src/main/java/org/apache/hop/core/database/types/ColumnContext.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.core.database.types;
+
+/**
+ * Why a column definition is being generated, rather than just how to format it.
+ *
+ * <p>This replaces the trailing boolean parameters of {@code IDatabase.getFieldDefinition}. Those
+ * booleans do not mean the same thing in every dialect: DuckDB reads {@code addFieldName == false}
+ * as "I am inside an ALTER COLUMN" and emits the column name plus the TYPE keyword, which is the
+ * opposite of what every other dialect does with it. Carrying the intent explicitly is what lets a
+ * rule answer the question it was actually asked.
+ */
+public final class ColumnContext {
+
+ /** What the generated definition is going into. */
+ public enum Purpose {
+ /** A CREATE TABLE column list. */
+ CREATE,
+ /** An ALTER TABLE ... ADD COLUMN. */
+ ADD_COLUMN,
+ /** An ALTER TABLE ... ALTER/MODIFY COLUMN. */
+ MODIFY_COLUMN
+ }
+
+ private final Purpose purpose;
+ private final String technicalKeyField;
+ private final String primaryKeyField;
+ private final boolean useAutoIncrement;
+ private final boolean addFieldName;
+ private final boolean addCarriageReturn;
+
+ public ColumnContext(
+ Purpose purpose,
+ String technicalKeyField,
+ String primaryKeyField,
+ boolean useAutoIncrement,
+ boolean addFieldName,
+ boolean addCarriageReturn) {
+ this.purpose = purpose;
+ this.technicalKeyField = technicalKeyField;
+ this.primaryKeyField = primaryKeyField;
+ this.useAutoIncrement = useAutoIncrement;
+ this.addFieldName = addFieldName;
+ this.addCarriageReturn = addCarriageReturn;
+ }
+
+ public Purpose getPurpose() {
+ return purpose;
+ }
+
+ /**
+ * @return the name of the technical key field, or null when there is none.
+ */
+ public String getTechnicalKeyField() {
+ return technicalKeyField;
+ }
+
+ /**
+ * @return the name of the primary key field, or null when there is none.
+ */
+ public String getPrimaryKeyField() {
+ return primaryKeyField;
+ }
+
+ public boolean isUseAutoIncrement() {
+ return useAutoIncrement;
+ }
+
+ public boolean isAddFieldName() {
+ return addFieldName;
+ }
+
+ public boolean isAddCarriageReturn() {
+ return addCarriageReturn;
+ }
+
+ /**
+ * @return true when the named column is this table's technical key.
+ */
+ public boolean isTechnicalKey(String fieldName) {
+ return technicalKeyField != null && technicalKeyField.equalsIgnoreCase(fieldName);
+ }
+
+ /**
+ * @return true when the named column is this table's primary key.
+ */
+ public boolean isPrimaryKey(String fieldName) {
+ return primaryKeyField != null && primaryKeyField.equalsIgnoreCase(fieldName);
+ }
+
+ /**
+ * @return true when the named column is either the technical key or the primary key.
+ */
+ public boolean isKey(String fieldName) {
+ return isTechnicalKey(fieldName) || isPrimaryKey(fieldName);
+ }
+}
diff --git a/core/src/main/java/org/apache/hop/core/database/types/ColumnTypeRules.java b/core/src/main/java/org/apache/hop/core/database/types/ColumnTypeRules.java
new file mode 100644
index 0000000000..30e301aca4
--- /dev/null
+++ b/core/src/main/java/org/apache/hop/core/database/types/ColumnTypeRules.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.core.database.types;
+
+import java.sql.Types;
+import java.util.List;
+import org.apache.hop.core.row.IValueMeta;
+
+/**
+ * Reusable column rules, named after the behaviour rather than after a vendor.
+ *
+ * <p>These are shapes several dialects share, not one dialect's private business: MySQL, Hive and
+ * SingleStore all read variable length binary the same way, because they all speak to a MySQL
+ * driver. A dialect opts into the ones it wants from its own {@code getTypeRules()}; core never
+ * decides which dialect gets what, which is the whole difference from the {@code isXVariant()}
+ * switch these replace.
+ *
+ * <p>Keeping them here rather than in one dialect's plugin also means a dialect can share them
+ * without taking a dependency on another database plugin, and that {@link LegacyVariantBridge} can
+ * honour the deprecated flags without needing any particular plugin to be installed.
+ */
+public final class ColumnTypeRules {
+
+ private ColumnTypeRules() {
+ // Utility class.
+ }
+
+ /**
+ * A YEAR column is reported as a date, but is really a four digit integer when the driver has
+ * been told not to treat it as a date.
+ */
+ public static final IDatabaseTypeRule YEAR_AS_INTEGER =
+ DatabaseTypes.rules()
+ .read(Types.DATE, Types.TIME)
+ .nativeName("YEAR")
+ .where(
+ (variables, databaseMeta, column) -> {
+ String property =
+ databaseMeta.getConnectionProperties(variables).getProperty("yearIsDateType");
+ return property != null && "false".equalsIgnoreCase(property);
+ })
+ .as(IValueMeta.TYPE_INTEGER, 4, 0)
+ .build()
+ .get(0);
+
+ /**
+ * Variable length binary carries no length, deliberately, so that string functions such as CONCAT
+ * still work on the result.
+ */
+ public static final IDatabaseTypeRule UNSIZED_VARIABLE_BINARY =
+ DatabaseTypes.rules()
+ .read(Types.VARBINARY, Types.LONGVARBINARY)
+ .where(ColumnTypeRules::notCharForBitData)
+ .as(IValueMeta.TYPE_BINARY, -1, -1)
+ .build()
+ .get(0);
+
+ /**
+ * An approximate column reporting at least as many decimals as digits, which cannot be a real
+ * declaration. MySQL reports (12,31) for a plain double.
+ */
+ public static final IDatabaseTypeRule OVERSCALED_APPROXIMATE_AS_UNSIZED_NUMBER =
+ DatabaseTypes.rules()
+ .read(Types.DOUBLE, Types.FLOAT, Types.REAL)
+ .where(
+ (variables, databaseMeta, column) ->
+ StandardJdbcTypeMapper.numericScale(column)
+ >= StandardJdbcTypeMapper.numericLength(column))
+ .as(IValueMeta.TYPE_NUMBER, -1, -1)
+ .build()
+ .get(0);
+
+ /** What a dialect speaking to a MySQL driver reads differently. */
+ public static final List<IDatabaseTypeRule> MYSQL_COMPATIBLE =
+ List.of(YEAR_AS_INTEGER, UNSIZED_VARIABLE_BINARY, OVERSCALED_APPROXIMATE_AS_UNSIZED_NUMBER);
+
+ /**
+ * The "CHAR(X) FOR BIT DATA" shape outranks every dialect rule for binary columns, and rules run
+ * before the standard mapping, so the binary rules above have to defer to it explicitly.
+ */
+ private static boolean notCharForBitData(
+ org.apache.hop.core.variables.IVariables variables,
+ org.apache.hop.core.database.DatabaseMeta databaseMeta,
+ DatabaseColumn column) {
+ return !StandardJdbcTypeMapper.displaySizeIsTwiceThePrecision(databaseMeta, column);
+ }
+}
diff --git a/core/src/main/java/org/apache/hop/core/database/types/DatabaseColumn.java b/core/src/main/java/org/apache/hop/core/database/types/DatabaseColumn.java
new file mode 100644
index 0000000000..06bb512181
--- /dev/null
+++ b/core/src/main/java/org/apache/hop/core/database/types/DatabaseColumn.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.core.database.types;
+
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+
+/**
+ * A database column described independently of which JDBC metadata API it came from.
+ *
+ * <p>JDBC exposes column metadata two different ways: {@link ResultSetMetaData}, from a query or a
+ * prepared statement, and a row of {@link java.sql.DatabaseMetaData#getColumns}. Hop reads both,
+ * and historically carried a separate copy of the type mapping for each, which is how they drifted
+ * apart. Normalizing to this one shape is what lets a single mapper serve both.
+ *
+ * <p>Instances are immutable.
+ */
+public final class DatabaseColumn {
+
+ private final String name;
+ private final int sqlType;
+ private final String nativeTypeName;
+ private final int precision;
+ private final int scale;
+ private final int displaySize;
+ private final boolean signed;
+ private final String comment;
+
+ /**
+ * The result set metadata this column came from, or null when it came from a getColumns() row.
+ * Only kept so that {@code IDatabase.customizeValueFromSqlType} can still be handed the raw JDBC
+ * metadata it takes today; nothing else should reach for it.
+ */
+ private final ResultSetMetaData resultSetMetaData;
+
+ private final int columnIndex;
+
+ private DatabaseColumn(
+ String name,
+ int sqlType,
+ String nativeTypeName,
+ int precision,
+ int scale,
+ int displaySize,
+ boolean signed,
+ String comment,
+ ResultSetMetaData resultSetMetaData,
+ int columnIndex) {
+ this.name = name;
+ this.sqlType = sqlType;
+ this.nativeTypeName = nativeTypeName;
+ this.precision = precision;
+ this.scale = scale;
+ this.displaySize = displaySize;
+ this.signed = signed;
+ this.comment = comment;
+ this.resultSetMetaData = resultSetMetaData;
+ this.columnIndex = columnIndex;
+ }
+
+ /** Describes column {@code index} (1-based) of the given result set metadata. */
+ public static DatabaseColumn of(ResultSetMetaData rm, int index) throws SQLException {
+ return of(rm, index, rm.getColumnName(index));
+ }
+
+ /**
+ * Describes column {@code index} (1-based) under a caller-supplied name. Callers that resolve the
+ * name themselves (MySQL's legacy column naming, for example) use this overload.
+ */
+ public static DatabaseColumn of(ResultSetMetaData rm, int index, String name)
+ throws SQLException {
+ return new DatabaseColumn(
+ name,
+ rm.getColumnType(index),
+ rm.getColumnTypeName(index),
+ rm.getPrecision(index),
+ rm.getScale(index),
+ rm.getColumnDisplaySize(index),
+ readSigned(rm, index),
+ rm.getColumnLabel(index),
+ rm,
+ index);
+ }
+
+ /**
+ * Describes the column on the current row of a {@link java.sql.DatabaseMetaData#getColumns}
+ * result set.
+ *
+ * <p>That API reports neither display size nor signedness. Display size falls back to {@code
+ * COLUMN_SIZE}; signedness falls back to true, because every SQL dialect's BIGINT is signed
+ * unless explicitly declared otherwise (unsigned integers are a MySQL extension). Assuming
+ * unsigned here would needlessly widen every BIGINT to a BigNumber.
+ */
+ public static DatabaseColumn ofColumnsRow(ResultSet columnsRow) throws SQLException {
+ int columnSize = columnsRow.getInt("COLUMN_SIZE");
+ Object decimalDigits = columnsRow.getObject("DECIMAL_DIGITS");
+ return new DatabaseColumn(
+ columnsRow.getString("COLUMN_NAME"),
+ columnsRow.getInt("DATA_TYPE"),
+ columnsRow.getString("TYPE_NAME"),
+ columnSize,
+ decimalDigits == null ? 0 : columnsRow.getInt("DECIMAL_DIGITS"),
+ columnSize,
+ true,
+ columnsRow.getString("REMARKS"),
+ null,
+ -1);
+ }
+
+ /** Not every JDBC driver implements isSigned(); those that don't are treated as unsigned. */
+ private static boolean readSigned(ResultSetMetaData rm, int index) {
+ try {
+ return rm.isSigned(index);
+ } catch (Exception ignored) {
+ // This JDBC driver doesn't support the isSigned method. Nothing more we can do here.
+ return false;
+ }
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return the {@link java.sql.Types} constant reported for this column.
+ */
+ public int getSqlType() {
+ return sqlType;
+ }
+
+ /**
+ * @return the database's own name for the type, e.g. NUMBER, JSONB, SDO_GEOMETRY, YEAR.
+ */
+ public String getNativeTypeName() {
+ return nativeTypeName;
+ }
+
+ public int getPrecision() {
+ return precision;
+ }
+
+ public int getScale() {
+ return scale;
+ }
+
+ public int getDisplaySize() {
+ return displaySize;
+ }
+
+ public boolean isSigned() {
+ return signed;
+ }
+
+ public String getComment() {
+ return comment;
+ }
+
+ public ResultSetMetaData getResultSetMetaData() {
+ return resultSetMetaData;
+ }
+
+ public int getColumnIndex() {
+ return columnIndex;
+ }
+}
diff --git a/core/src/main/java/org/apache/hop/core/database/types/DatabaseTypeMapper.java b/core/src/main/java/org/apache/hop/core/database/types/DatabaseTypeMapper.java
new file mode 100644
index 0000000000..9f25eab3e1
--- /dev/null
+++ b/core/src/main/java/org/apache/hop/core/database/types/DatabaseTypeMapper.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.core.database.types;
+
+import java.util.List;
+import org.apache.hop.core.database.DatabaseMeta;
+import org.apache.hop.core.database.IDatabase;
+import org.apache.hop.core.exception.HopDatabaseException;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.variables.IVariables;
+
+/**
+ * Resolves a column type through the dialect's rules, falling back to plain JDBC behaviour.
+ *
+ * <p>This is the entry point that will replace the {@code isXVariant()} switches once callers are
+ * migrated. It is not wired into the engine yet.
+ */
+public final class DatabaseTypeMapper {
+
+ private DatabaseTypeMapper() {
+ // Utility class.
+ }
+
+ /**
+ * Maps a column to Hop value metadata: dialect rules first, then the standard JDBC mapping.
+ *
+ * @return the value metadata, or null when nothing claimed the column, so that the caller can
+ * still consult the value meta plugins as it does today
+ */
+ public static IValueMeta getValueMeta(
+ IVariables variables,
+ DatabaseMeta databaseMeta,
+ DatabaseColumn column,
+ boolean ignoreLength,
+ boolean lazyConversion)
+ throws HopDatabaseException {
+ for (IDatabaseTypeRule rule : rulesFor(databaseMeta)) {
+ IValueMeta valueMeta = rule.getValueMeta(variables, databaseMeta, column);
+ if (valueMeta != null) {
+ return valueMeta;
+ }
+ }
+ return StandardJdbcTypeMapper.getValueMeta(
+ variables, databaseMeta, column, ignoreLength, lazyConversion);
+ }
+
+ /**
+ * Renders the column type for a value, without the column name.
+ *
+ * @return the DDL type, or null when no rule claims it, so that the caller can fall back to the
+ * dialect's own getFieldDefinition
+ */
+ public static String getColumnType(
+ IVariables variables,
+ DatabaseMeta databaseMeta,
+ IValueMeta valueMeta,
+ ColumnContext context) {
+ if (databaseMeta == null) {
+ return null;
+ }
+ return getColumnType(variables, databaseMeta.getIDatabase(), valueMeta, context);
+ }
+
+ /**
+ * The column type for this value, asked of the dialect directly.
+ *
+ * <p>A dialect assembling an ALTER TABLE statement has itself but no {@link DatabaseMeta} wrapper
+ * to hand, so this is the form the write path is actually reached through.
+ *
+ * @return the DDL type, or null when no rule claims the value and the caller should fall back
+ */
+ public static String getColumnType(
+ IVariables variables, IDatabase database, IValueMeta valueMeta, ColumnContext context) {
+ if (database == null || valueMeta == null) {
+ return null;
+ }
+ for (IDatabaseTypeRule rule : DatabaseTypeRuleRegistry.getTypeRules(database)) {
+ String columnType = rule.getColumnType(variables, database, valueMeta, context);
+ if (columnType != null) {
+ return columnType;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * How values of this type move across JDBC on this database.
+ *
+ * <p>Called for every value of every row, so it is kept to a cached lookup that usually finds
+ * nothing.
+ *
+ * @return the binding, or null to use the default JDBC handling for the Hop type
+ */
+ public static IValueBinding getBinding(IDatabase database, IValueMeta valueMeta) {
+ if (database == null || valueMeta == null) {
+ return null;
+ }
+ for (IDatabaseTypeRule rule : DatabaseTypeRuleRegistry.getBindingRules(database)) {
+ IValueBinding binding = rule.getBinding(database, valueMeta);
+ if (binding != null) {
+ return binding;
+ }
+ }
+ return null;
+ }
+
+ private static List<IDatabaseTypeRule> rulesFor(DatabaseMeta databaseMeta) {
+ if (databaseMeta == null || databaseMeta.getIDatabase() == null) {
+ return List.of();
+ }
+ return DatabaseTypeRuleRegistry.getTypeRules(databaseMeta.getIDatabase());
+ }
+}
diff --git a/core/src/main/java/org/apache/hop/core/database/types/DatabaseTypeRuleRegistry.java b/core/src/main/java/org/apache/hop/core/database/types/DatabaseTypeRuleRegistry.java
new file mode 100644
index 0000000000..22fa6050fb
--- /dev/null
+++ b/core/src/main/java/org/apache/hop/core/database/types/DatabaseTypeRuleRegistry.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.core.database.types;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.hop.core.database.DatabaseMetaPlugin;
+import org.apache.hop.core.database.IDatabase;
+import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.core.plugins.IPlugin;
+import org.apache.hop.core.plugins.PluginRegistry;
+import org.apache.hop.core.row.value.ValueMetaFactory;
+
+/**
+ * Collects the type rules that apply to a dialect, from the dialect itself and from any {@link
+ * DatabaseTypeRulesPlugin} that targets it.
+ *
+ * <p>Externally contributed rules come first, so a plugin can both add types a dialect never had
+ * and correct one it gets wrong.
+ */
+public final class DatabaseTypeRuleRegistry {
+
+ /**
+ * Contributed rules keyed by the dialect's type chain. Only the plugin lookup is cached: the
+ * legacy variant bridge reads flags off the instance, so caching it per class would be wrong for
+ * any dialect that decides them at runtime.
+ */
+ private static final Map<List<String>, List<IDatabaseTypeRule>> contributedCache =
+ new ConcurrentHashMap<>();
+
+ private DatabaseTypeRuleRegistry() {
+ // Utility class.
+ }
+
+ /**
+ * The dialect's own plugin type and those of its ancestors, most specific first.
+ *
+ * <p>This is the replacement for asking {@code isPostgresVariant()}: Redshift reports {@code
+ * [REDSHIFT, POSTGRESQL]} because it extends the Postgres dialect, so a rule written for
+ * POSTGRESQL applies to it without anyone maintaining a list of Postgres-like databases.
+ */
+ public static List<String> getDialectTypes(IDatabase database) {
+ List<String> types = new ArrayList<>();
+ for (Class<?> clazz = database.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
+ // Not @Inherited, so this only ever returns the annotation declared on that exact class.
+ DatabaseMetaPlugin annotation = clazz.getAnnotation(DatabaseMetaPlugin.class);
+ if (annotation != null && !types.contains(annotation.type())) {
+ types.add(annotation.type());
+ }
+ }
+ return types;
+ }
+
+ /** All rules that apply to this dialect, in the order they should be consulted. */
+ public static List<IDatabaseTypeRule> getTypeRules(IDatabase database) {
+ List<String> dialectTypes = getDialectTypes(database);
+ List<IDatabaseTypeRule> contributed =
+ contributedCache.computeIfAbsent(dialectTypes, types -> contributedRules(database));
+
+ List<IDatabaseTypeRule> own = database.getTypeRules();
+ List<IDatabaseTypeRule> legacy = LegacyVariantBridge.forDialect(database, dialectTypes);
+ if (own.isEmpty() && legacy.isEmpty()) {
+ return contributed;
+ }
+
+ List<IDatabaseTypeRule> rules =
+ new ArrayList<>(contributed.size() + own.size() + legacy.size());
+ rules.addAll(contributed);
+ rules.addAll(own);
+ // Last, so that a dialect which has migrated overrides the flags it may still answer.
+ rules.addAll(legacy);
+ return rules;
+ }
+
+ private static List<IDatabaseTypeRule> contributedRules(IDatabase database) {
+ List<String> dialectTypes = getDialectTypes(database);
+ List<IDatabaseTypeRule> rules = new ArrayList<>();
+
+ List<IPlugin> plugins;
+ try {
+ plugins = PluginRegistry.getInstance().getPlugins(DatabaseTypeRulesPluginType.class);
+ } catch (Exception e) {
+ // No plugin registry in this context, for example a unit test using dialects directly.
+ return rules;
+ }
+
+ for (IPlugin plugin : plugins) {
+ try {
+ Object loaded = PluginRegistry.getInstance().loadClass(plugin);
+ if (!(loaded instanceof IDatabaseTypeRuleProvider provider)) {
+ continue;
+ }
+ DatabaseTypeRulesPlugin annotation =
+ loaded.getClass().getAnnotation(DatabaseTypeRulesPlugin.class);
+ if (annotation == null || !appliesTo(annotation, dialectTypes)) {
+ continue;
+ }
+ if (!valueTypesAvailable(annotation)) {
+ // A rule set for a value type nobody installed is skipped, not an error.
+ continue;
+ }
+ rules.addAll(provider.getTypeRules());
+ } catch (Exception e) {
+ // One broken contributor must not take down every database connection in the process.
+ LogChannel.GENERAL.logError(
+ "Unable to load database type rules from plugin " + plugin.getIds()[0], e);
+ }
+ }
+ return rules;
+ }
+
+ private static boolean appliesTo(DatabaseTypeRulesPlugin annotation, List<String> dialectTypes) {
+ if (annotation.dialects().length == 0) {
+ return true;
+ }
+ return Arrays.stream(annotation.dialects()).anyMatch(dialectTypes::contains);
+ }
+
+ private static boolean valueTypesAvailable(DatabaseTypeRulesPlugin annotation) {
+ if (annotation.valueTypes().length == 0) {
+ return true;
+ }
+ List<String> available = Arrays.asList(ValueMetaFactory.getValueMetaNames());
+ return available.containsAll(Arrays.asList(annotation.valueTypes()));
+ }
+
+ /**
+ * The rules that can supply a value binding, cached per dialect class.
+ *
+ * <p>This is read for every value of every row, so it must not rebuild the rule list each time.
+ * Bindings describe how a driver behaves, which is a property of the dialect rather than of one
+ * connection, so caching them per class is safe where caching the full rule list would not be:
+ * Oracle, for one, chooses different type rules per connection.
+ */
+ private static final Map<Class<?>, List<IDatabaseTypeRule>> bindingRuleCache =
+ new ConcurrentHashMap<>();
+
+ /** The rules that can supply a value binding for this dialect. Usually empty. */
+ public static List<IDatabaseTypeRule> getBindingRules(IDatabase database) {
+ return bindingRuleCache.computeIfAbsent(
+ database.getClass(),
+ clazz -> {
+ List<IDatabaseTypeRule> candidates = new ArrayList<>();
+ for (IDatabaseTypeRule rule : getTypeRules(database)) {
+ if (rule.suppliesBindings()) {
+ candidates.add(rule);
+ }
+ }
+ return List.copyOf(candidates);
+ });
+ }
+
+ /** Drops the cached rules. Plugin registration changes between tests need this. */
+ public static void clearCache() {
+ contributedCache.clear();
+ bindingRuleCache.clear();
+ LegacyVariantBridge.clearCache();
+ }
+}
diff --git a/core/src/main/java/org/apache/hop/core/database/types/DatabaseTypeRulesPlugin.java b/core/src/main/java/org/apache/hop/core/database/types/DatabaseTypeRulesPlugin.java
new file mode 100644
index 0000000000..8c050427eb
--- /dev/null
+++ b/core/src/main/java/org/apache/hop/core/database/types/DatabaseTypeRulesPlugin.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.core.database.types;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Contributes column-type rules to one or more database dialects, from outside those dialects.
+ *
+ * <p>This is what lets a plugin Hop does not ship teach an existing dialect a new type. A geometry
+ * plugin, for example, can supply the Oracle mapping for SDO_GEOMETRY without any change to the
+ * Oracle plugin:
+ *
+ * <pre>
+ * @DatabaseTypeRulesPlugin(
+ * id = "oracle-geometry",
+ * dialects = {"ORACLE"},
+ * valueTypes = {"Geometry"})
+ * public class OracleGeometryTypeRules implements IDatabaseTypeRuleProvider { ... }
+ * </pre>
+ *
+ * <p>Rules from here are consulted before the dialect's own, so a third party can also correct a
+ * built-in dialect without patching it.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface DatabaseTypeRulesPlugin {
+
+ String id();
+
+ String name() default "";
+
+ String description() default "";
+
+ /**
+ * The database dialects these rules apply to, named by {@code DatabaseMetaPlugin.type()} — for
+ * example ORACLE or POSTGRESQL. Naming them as strings rather than classes is deliberate: a
+ * contributor must not have to compile against the dialect it extends.
+ *
+ * <p>Matching follows the dialect's class hierarchy, so POSTGRESQL also covers Redshift,
+ * Greenplum and CrateDB. Empty means every dialect.
+ */
+ String[] dialects() default {};
+
+ /**
+ * Value type names these rules depend on, as reported by {@code ValueMetaFactory}. When one is
+ * not installed the whole plugin is skipped rather than failing, so a dialect can ship optional
+ * rules for a value type that may or may not be present.
+ */
+ String[] valueTypes() default {};
+
+ boolean isSeparateClassLoaderNeeded() default false;
+
+ String classLoaderGroup() default "";
+
+ String documentationUrl() default "";
+
+ String casesUrl() default "";
+
+ String forumUrl() default "";
+}
diff --git a/core/src/main/java/org/apache/hop/core/database/types/DatabaseTypeRulesPluginType.java b/core/src/main/java/org/apache/hop/core/database/types/DatabaseTypeRulesPluginType.java
new file mode 100644
index 0000000000..cc201e4cc4
--- /dev/null
+++ b/core/src/main/java/org/apache/hop/core/database/types/DatabaseTypeRulesPluginType.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.core.database.types;
+
+import java.util.Map;
+import org.apache.hop.core.plugins.BasePluginType;
+import org.apache.hop.core.plugins.PluginAnnotationType;
+import org.apache.hop.core.plugins.PluginMainClassType;
+
+/** The plugin type for externally contributed database type rules. */
+@PluginMainClassType(IDatabaseTypeRuleProvider.class)
+@PluginAnnotationType(DatabaseTypeRulesPlugin.class)
+public class DatabaseTypeRulesPluginType extends BasePluginType<DatabaseTypeRulesPlugin> {
+
+ private static DatabaseTypeRulesPluginType instance;
+
+ private DatabaseTypeRulesPluginType() {
+ super(DatabaseTypeRulesPlugin.class, "DATABASE_TYPE_RULES", "Database type rules");
+ }
+
+ public static DatabaseTypeRulesPluginType getInstance() {
+ if (instance == null) {
+ instance = new DatabaseTypeRulesPluginType();
+ }
+ return instance;
+ }
+
+ @Override
+ protected String extractCategory(DatabaseTypeRulesPlugin annotation) {
+ return null;
+ }
+
+ @Override
+ protected String extractDesc(DatabaseTypeRulesPlugin annotation) {
+ return annotation.description();
+ }
+
+ @Override
+ protected String extractID(DatabaseTypeRulesPlugin annotation) {
+ return annotation.id();
+ }
+
+ @Override
+ protected String extractName(DatabaseTypeRulesPlugin annotation) {
+ return annotation.name().isEmpty() ? annotation.id() : annotation.name();
+ }
+
+ @Override
+ protected String extractImageFile(DatabaseTypeRulesPlugin annotation) {
+ return null;
+ }
+
+ @Override
+ protected boolean extractSeparateClassLoader(DatabaseTypeRulesPlugin annotation) {
+ return annotation.isSeparateClassLoaderNeeded();
+ }
+
+ @Override
+ protected void addExtraClasses(
+ Map<Class<?>, String> classMap, Class<?> clazz, DatabaseTypeRulesPlugin annotation) {
+ // Nothing extra.
+ }
+
+ @Override
+ protected String extractDocumentationUrl(DatabaseTypeRulesPlugin annotation) {
+ return annotation.documentationUrl();
+ }
+
+ @Override
+ protected String extractSuggestion(DatabaseTypeRulesPlugin annotation) {
+ return null;
+ }
+
+ @Override
+ protected String extractCasesUrl(DatabaseTypeRulesPlugin annotation) {
+ return annotation.casesUrl();
+ }
+
+ @Override
+ protected String extractForumUrl(DatabaseTypeRulesPlugin annotation) {
+ return annotation.forumUrl();
+ }
+
+ @Override
+ protected String extractClassLoaderGroup(DatabaseTypeRulesPlugin annotation) {
+ return annotation.classLoaderGroup();
+ }
+}
diff --git a/core/src/main/java/org/apache/hop/core/database/types/DatabaseTypes.java b/core/src/main/java/org/apache/hop/core/database/types/DatabaseTypes.java
new file mode 100644
index 0000000000..1bad17c85c
--- /dev/null
+++ b/core/src/main/java/org/apache/hop/core/database/types/DatabaseTypes.java
@@ -0,0 +1,380 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.core.database.types;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.ToIntFunction;
+import java.util.regex.Pattern;
+import org.apache.hop.core.database.DatabaseMeta;
+import org.apache.hop.core.database.IDatabase;
+import org.apache.hop.core.exception.HopDatabaseException;
+import org.apache.hop.core.exception.HopPluginException;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.value.ValueMetaFactory;
+import org.apache.hop.core.variables.IVariables;
+
+/**
+ * Builds a dialect's type rules as a table of declarations rather than a switch statement.
+ *
+ * <pre>
+ * DatabaseTypes.rules()
+ * .readNative("json", "jsonb").as(IValueMeta.TYPE_JSON)
+ * .read(Types.TIME).as(IValueMeta.TYPE_DATE)
+ * .write(IValueMeta.TYPE_JSON).as("JSONB")
+ * .write(IValueMeta.TYPE_BINARY).as("BYTEA")
+ * .write(IValueMeta.TYPE_INTEGER)
+ * .as(v -> v.getLength() < 5 ? "SMALLINT" : v.getLength() <= 9 ? "INTEGER" : "BIGINT")
+ * .build();
+ * </pre>
+ *
+ * <p>Rules are matched in declaration order, first match wins. Anything the table cannot express
+ * can be added as a hand-written {@link IDatabaseTypeRule} through {@link Builder#rule}, and a
+ * dialect can inherit another's table with {@link Builder#include}.
+ */
+public final class DatabaseTypes {
+
+ /** A read rule condition with access to everything the mapping can depend on. */
+ @FunctionalInterface
+ public interface RuleCondition {
+ boolean test(IVariables variables, DatabaseMeta databaseMeta, DatabaseColumn column);
+ }
+
+ private DatabaseTypes() {
+ // Utility class.
+ }
+
+ /** Starts a new rule table. */
+ public static Builder rules() {
+ return new Builder();
+ }
+
+ /** Collects rules in declaration order. */
+ public static final class Builder {
+ private final List<IDatabaseTypeRule> rules = new ArrayList<>();
+
+ /** Matches columns reported as any of these {@link java.sql.Types} constants. */
+ public ReadBuilder read(int... sqlTypes) {
+ return new ReadBuilder(this, sqlTypes, null, null);
+ }
+
+ /** Matches columns whose database type name is any of these, ignoring case. */
+ public ReadBuilder readNative(String... nativeTypeNames) {
+ Set<String> names = new LinkedHashSet<>();
+ for (String name : nativeTypeNames) {
+ names.add(name.toUpperCase(Locale.ROOT));
+ }
+ return new ReadBuilder(this, null, names, null);
+ }
+
+ /** Matches columns whose database type name matches this regular expression, ignoring case. */
+ public ReadBuilder readNativeMatching(String regex) {
+ return new ReadBuilder(this, null, null, Pattern.compile(regex, Pattern.CASE_INSENSITIVE));
+ }
+
+ /** Declares how one or more Hop types are written as a column definition. */
+ public WriteBuilder write(int... hopTypes) {
+ return new WriteBuilder(this, hopTypes);
+ }
+
+ /** Declares how values of a Hop type move across JDBC on this database. */
+ public Builder bind(int hopType, IValueBinding binding) {
+ return bind(hopType, (database, valueMeta) -> true, binding);
+ }
+
+ /**
+ * Declares how values of a Hop type move across JDBC, when the given condition holds. The
+ * condition sees the dialect and the value metadata, which is everything available once rows
+ * are moving.
+ */
+ public Builder bind(
+ int hopType, BiPredicate<IDatabase, IValueMeta> when, IValueBinding binding) {
+ rules.add(new BindingRule(hopType, when, binding));
+ return this;
+ }
+
+ /** Adds a rule the table cannot express. */
+ public Builder rule(IDatabaseTypeRule rule) {
+ rules.add(rule);
+ return this;
+ }
+
+ /** Appends another dialect's rules, for dialects that borrow rather than inherit. */
+ public Builder include(List<IDatabaseTypeRule> other) {
+ rules.addAll(other);
+ return this;
+ }
+
+ public List<IDatabaseTypeRule> build() {
+ return List.copyOf(rules);
+ }
+ }
+
+ /** Declares how a database column is read into Hop value metadata. */
+ public static final class ReadBuilder {
+ private final Builder parent;
+ private final int[] sqlTypes;
+ private final Set<String> nativeNames;
+ private final Pattern namePattern;
+ private Set<String> extraNativeNames;
+ private Predicate<DatabaseColumn> condition;
+ private RuleCondition ruleCondition;
+ private IValueBinding binding;
+
+ private ReadBuilder(
+ Builder parent, int[] sqlTypes, Set<String> nativeNames, Pattern namePattern) {
+ this.parent = parent;
+ this.sqlTypes = sqlTypes;
+ this.nativeNames = nativeNames;
+ this.namePattern = namePattern;
+ }
+
+ /** Narrows the match, typically on precision, scale or signedness. */
+ public ReadBuilder where(Predicate<DatabaseColumn> condition) {
+ this.condition = condition;
+ return this;
+ }
+
+ /**
+ * Narrows the match on the connection as well as the column, for rules that depend on a
+ * connection property or on another of the dialect's capabilities.
+ */
+ public ReadBuilder where(RuleCondition condition) {
+ this.ruleCondition = condition;
+ return this;
+ }
+
+ /** Additionally requires one of these database type names, ignoring case. */
+ public ReadBuilder nativeName(String... typeNames) {
+ Set<String> names = new LinkedHashSet<>();
+ for (String typeName : typeNames) {
+ names.add(typeName.toUpperCase(Locale.ROOT));
+ }
+ this.extraNativeNames = names;
+ return this;
+ }
+
+ /** Supplies driver-specific handling for values of columns this rule claims. */
+ public ReadBuilder bind(IValueBinding binding) {
+ this.binding = binding;
+ return this;
+ }
+
+ /** Maps to a Hop type with no length or precision. Terminal. */
+ public Builder as(int hopType) {
+ return as(hopType, column -> -1, column -> -1);
+ }
+
+ /** Maps to a Hop type with a fixed length and precision. Terminal. */
+ public Builder as(int hopType, int length, int precision) {
+ return as(hopType, column -> length, column -> precision);
+ }
+
+ /** Maps to a Hop type whose length and precision are derived from the column. Terminal. */
+ public Builder as(
+ int hopType,
+ ToIntFunction<DatabaseColumn> length,
+ ToIntFunction<DatabaseColumn> precision) {
+ Set<String> names = nativeNames;
+ if (extraNativeNames != null) {
+ names = extraNativeNames;
+ }
+ parent.rules.add(
+ new ReadRule(
+ sqlTypes,
+ names,
+ namePattern,
+ condition,
+ ruleCondition,
+ hopType,
+ length,
+ precision,
+ binding));
+ return parent;
+ }
+ }
+
+ /** Declares how a Hop value is written as a column definition. */
+ public static final class WriteBuilder {
+ private final Builder parent;
+ private final int[] hopTypes;
+ private Predicate<IValueMeta> condition;
+
+ private WriteBuilder(Builder parent, int[] hopTypes) {
+ this.parent = parent;
+ this.hopTypes = hopTypes;
+ }
+
+ /** Narrows the match, typically on length or precision. */
+ public WriteBuilder where(Predicate<IValueMeta> condition) {
+ this.condition = condition;
+ return this;
+ }
+
+ /** Uses a fixed column type. Terminal. */
+ public Builder as(String columnType) {
+ return as(valueMeta -> columnType);
+ }
+
+ /** Derives the column type from the value, for size-dependent types. Terminal. */
+ public Builder as(Function<IValueMeta, String> columnType) {
+ parent.rules.add(new WriteRule(hopTypes, condition, columnType));
+ return parent;
+ }
+ }
+
+ /** A declared read rule. */
+ private static final class ReadRule implements IDatabaseTypeRule {
+ private final int[] sqlTypes;
+ private final Set<String> nativeNames;
+ private final Pattern namePattern;
+ private final Predicate<DatabaseColumn> condition;
+ private final RuleCondition ruleCondition;
+ private final int hopType;
+ private final ToIntFunction<DatabaseColumn> length;
+ private final ToIntFunction<DatabaseColumn> precision;
+ private final IValueBinding binding;
+
+ private ReadRule(
+ int[] sqlTypes,
+ Set<String> nativeNames,
+ Pattern namePattern,
+ Predicate<DatabaseColumn> condition,
+ RuleCondition ruleCondition,
+ int hopType,
+ ToIntFunction<DatabaseColumn> length,
+ ToIntFunction<DatabaseColumn> precision,
+ IValueBinding binding) {
+ this.sqlTypes = sqlTypes;
+ this.nativeNames = nativeNames;
+ this.namePattern = namePattern;
+ this.condition = condition;
+ this.ruleCondition = ruleCondition;
+ this.hopType = hopType;
+ this.length = length;
+ this.precision = precision;
+ this.binding = binding;
+ }
+
+ @Override
+ public IValueMeta getValueMeta(
+ IVariables variables, DatabaseMeta databaseMeta, DatabaseColumn column)
+ throws HopDatabaseException {
+ if (!matches(column)
+ || (ruleCondition != null && !ruleCondition.test(variables, databaseMeta, column))) {
+ return null;
+ }
+ try {
+ IValueMeta valueMeta = ValueMetaFactory.createValueMeta(column.getName(), hopType);
+ valueMeta.setLength(length.applyAsInt(column));
+ valueMeta.setPrecision(precision.applyAsInt(column));
+ StandardJdbcTypeMapper.setOriginalColumnMetadata(valueMeta, column, false);
+ return valueMeta;
+ } catch (HopPluginException e) {
+ throw new HopDatabaseException(
+ "Unable to create value metadata of type "
+ + hopType
+ + " for column "
+ + column.getName(),
+ e);
+ }
+ }
+
+ private boolean matches(DatabaseColumn column) {
+ if (sqlTypes != null && Arrays.stream(sqlTypes).noneMatch(t -> t == column.getSqlType())) {
+ return false;
+ }
+ String nativeName = column.getNativeTypeName();
+ if (nativeNames != null
+ && (nativeName == null || !nativeNames.contains(nativeName.toUpperCase(Locale.ROOT)))) {
+ return false;
+ }
+ if (namePattern != null
+ && (nativeName == null || !namePattern.matcher(nativeName).matches())) {
+ return false;
+ }
+ return condition == null || condition.test(column);
+ }
+
+ @Override
+ public IValueBinding getBinding(IDatabase database, IValueMeta valueMeta) {
+ return valueMeta.getType() == hopType ? binding : null;
+ }
+
+ @Override
+ public boolean suppliesBindings() {
+ return binding != null;
+ }
+ }
+
+ /** A declared binding. */
+ private static final class BindingRule implements IDatabaseTypeRule {
+ private final int hopType;
+ private final BiPredicate<IDatabase, IValueMeta> when;
+ private final IValueBinding binding;
+
+ private BindingRule(
+ int hopType, BiPredicate<IDatabase, IValueMeta> when, IValueBinding binding) {
+ this.hopType = hopType;
+ this.when = when;
+ this.binding = binding;
+ }
+
+ @Override
+ public IValueBinding getBinding(IDatabase database, IValueMeta valueMeta) {
+ return valueMeta.getType() == hopType && when.test(database, valueMeta) ? binding : null;
+ }
+
+ @Override
+ public boolean suppliesBindings() {
+ return true;
+ }
+ }
+
+ /** A declared write rule. */
+ private static final class WriteRule implements IDatabaseTypeRule {
+ private final int[] hopTypes;
+ private final Predicate<IValueMeta> condition;
+ private final Function<IValueMeta, String> columnType;
+
+ private WriteRule(
+ int[] hopTypes, Predicate<IValueMeta> condition, Function<IValueMeta, String> columnType) {
+ this.hopTypes = hopTypes;
+ this.condition = condition;
+ this.columnType = columnType;
+ }
+
+ @Override
+ public String getColumnType(
+ IVariables variables, IDatabase database, IValueMeta valueMeta, ColumnContext context) {
+ if (Arrays.stream(hopTypes).noneMatch(t -> t == valueMeta.getType())) {
+ return null;
+ }
+ if (condition != null && !condition.test(valueMeta)) {
+ return null;
+ }
+ return columnType.apply(valueMeta);
+ }
+ }
+}
diff --git a/core/src/main/java/org/apache/hop/core/database/types/IDatabaseTypeRule.java b/core/src/main/java/org/apache/hop/core/database/types/IDatabaseTypeRule.java
new file mode 100644
index 0000000000..49ac023384
--- /dev/null
+++ b/core/src/main/java/org/apache/hop/core/database/types/IDatabaseTypeRule.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.core.database.types;
+
+import org.apache.hop.core.database.DatabaseMeta;
+import org.apache.hop.core.database.IDatabase;
+import org.apache.hop.core.exception.HopDatabaseException;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.variables.IVariables;
+
+/**
+ * One bidirectional column-type rule, owned by whoever knows the database rather than by core.
+ *
+ * <p>Rules are contributed from three places, and consulted in this order:
+ *
+ * <ol>
+ * <li>a {@code DatabaseTypeRulesPlugin}, which anyone can ship, including for a dialect they do
+ * not own
+ * <li>the dialect itself, through {@code IDatabase.getTypeRules()}
+ * <li>the value types, for their own neutral defaults
+ * </ol>
+ *
+ * <p>Falling off the end of that list lands on {@link StandardJdbcTypeMapper}, the plain JDBC
+ * behaviour. Every method here returns null to mean "not mine, ask the next rule", so a rule only
+ * has to describe what it actually knows.
+ */
+public interface IDatabaseTypeRule {
+
+ /**
+ * Read path: claim this column and describe it as Hop value metadata.
+ *
+ * @return the value metadata, or null to defer to the next rule
+ */
+ default IValueMeta getValueMeta(
+ IVariables variables, DatabaseMeta databaseMeta, DatabaseColumn column)
+ throws HopDatabaseException {
+ return null;
+ }
+
+ /**
+ * Write path: render the column type for this value, without the column name.
+ *
+ * <p>The dialect is handed over rather than the {@link DatabaseMeta} wrapper, because the write
+ * path is also reached from inside the dialect itself, when it assembles an ALTER TABLE
+ * statement, and there is no wrapper in sight there. Nothing is lost by it: a connection option
+ * that a type decision depends on lives on the dialect instance, which is where {@link
+ * IDatabase#getTypeRules()} is answered from as well.
+ *
+ * @return the DDL type, or null to defer to the next rule
+ */
+ default String getColumnType(
+ IVariables variables, IDatabase database, IValueMeta valueMeta, ColumnContext context) {
+ return null;
+ }
+
+ /**
+ * How values of this type move across JDBC on this database.
+ *
+ * <p>Chosen from the dialect and the value metadata rather than from column metadata, because by
+ * the time rows are moving there is no column metadata left. Anything the decision depends on — a
+ * driver quirk, a connection capability, the value's own precision — has to be answered here, so
+ * that the binding itself only has to do the work.
+ *
+ * @return the binding, or null to use the default JDBC handling for the Hop type
+ */
+ default IValueBinding getBinding(IDatabase database, IValueMeta valueMeta) {
+ return null;
+ }
+
+ /**
+ * Whether this rule can ever supply a binding.
+ *
+ * <p>Answered once per dialect so that the per value lookup can skip every rule that never binds,
+ * which is nearly all of them.
+ */
+ default boolean suppliesBindings() {
+ return false;
+ }
+}
diff --git a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java b/core/src/main/java/org/apache/hop/core/database/types/IDatabaseTypeRuleProvider.java
similarity index 55%
copy from plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
copy to core/src/main/java/org/apache/hop/core/database/types/IDatabaseTypeRuleProvider.java
index d138cf8a7f..a1cd96b142 100644
--- a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
+++ b/core/src/main/java/org/apache/hop/core/database/types/IDatabaseTypeRuleProvider.java
@@ -14,19 +14,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.hop.core.database.types;
-package org.apache.hop.databases.cockroachdb;
+import java.util.List;
-import org.apache.hop.core.database.DatabaseMetaPlugin;
-import org.apache.hop.core.database.IDatabase;
-import org.apache.hop.core.gui.plugin.GuiPlugin;
-import org.apache.hop.databases.postgresql.PostgreSqlDatabaseMeta;
+/** Supplies type rules from a {@link DatabaseTypeRulesPlugin}. */
+public interface IDatabaseTypeRuleProvider {
-/** Contains PostgreSQL specific information through static final members */
-@DatabaseMetaPlugin(
- type = "COCKROACHDB",
- typeDescription = "CockroachDB",
- image = "cockroachdb.svg",
- documentationUrl = "/database/databases/postgresql.html")
-@GuiPlugin(id = "GUI-CockroachDatabaseMeta")
-public class CockroachDatabaseMeta extends PostgreSqlDatabaseMeta implements IDatabase {}
+ /**
+ * @return the rules this plugin contributes, most specific first.
+ */
+ List<IDatabaseTypeRule> getTypeRules();
+}
diff --git a/core/src/main/java/org/apache/hop/core/database/types/IValueBinding.java b/core/src/main/java/org/apache/hop/core/database/types/IValueBinding.java
new file mode 100644
index 0000000000..1056f0d6ac
--- /dev/null
+++ b/core/src/main/java/org/apache/hop/core/database/types/IValueBinding.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.core.database.types;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import org.apache.hop.core.database.IDatabase;
+import org.apache.hop.core.exception.HopValueException;
+import org.apache.hop.core.row.IValueMeta;
+
+/**
+ * How a value of one column type crosses the JDBC boundary.
+ *
+ * <p>A binding belongs to whichever plugin owns the driver, because that is the only classloader in
+ * which driver classes resolve. An external plugin that tries to unwrap, say, {@code
+ * oracle.sql.STRUCT} from its own classloader gets "STRUCT cannot be cast to STRUCT"; the same code
+ * inside the Oracle plugin, or in a plugin that joins its classLoaderGroup, works.
+ *
+ * <p>Implementations should exchange driver-neutral values (byte arrays, strings, numbers) so that
+ * the value type on the other side needs no knowledge of any particular driver.
+ */
+public interface IValueBinding {
+
+ /**
+ * Reads the value of a column this binding's rule claimed.
+ *
+ * @param valueMeta the value metadata describing the column
+ * @param database the dialect being read from
+ * @param resultSet the result set to read from
+ * @param index the 1-based column index
+ * @return the value, or null
+ */
+ Object read(IDatabase database, IValueMeta valueMeta, ResultSet resultSet, int index)
+ throws SQLException;
+
+ /**
+ * Writes a value into a prepared statement.
+ *
+ * @param valueMeta the value metadata describing the column
+ * @param database the dialect being written to
+ * @param preparedStatement the statement to write into
+ * @param index the 1-based parameter index
+ * @param value the value to write, possibly null
+ */
+ void write(
+ IDatabase database,
+ IValueMeta valueMeta,
+ PreparedStatement preparedStatement,
+ int index,
+ Object value)
+ throws SQLException, HopValueException;
+}
diff --git a/core/src/main/java/org/apache/hop/core/database/types/JdbcDateValues.java b/core/src/main/java/org/apache/hop/core/database/types/JdbcDateValues.java
new file mode 100644
index 0000000000..a60aac784a
--- /dev/null
+++ b/core/src/main/java/org/apache/hop/core/database/types/JdbcDateValues.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.core.database.types;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.sql.Types;
+import java.util.Calendar;
+import org.apache.hop.core.database.IDatabase;
+import org.apache.hop.core.exception.HopValueException;
+import org.apache.hop.core.row.IValueMeta;
+
+/**
+ * Writing a date or timestamp through JDBC.
+ *
+ * <p>Held in one place because dialects differ in only one respect: some JDBC drivers have never
+ * implemented the {@link Calendar} overloads of setDate and setTimestamp, and a dialect whose
+ * driver is one of those should not have to restate the rest of the handling to say so.
+ */
+public final class JdbcDateValues {
+
+ private JdbcDateValues() {
+ // Utility class.
+ }
+
+ /**
+ * Writes a date value.
+ *
+ * @param calendarOverloadsSupported whether the driver implements the Calendar overloads of
+ * setDate and setTimestamp. When it does not, a configured time zone cannot be honoured.
+ */
+ public static void write(
+ IDatabase database,
+ IValueMeta valueMeta,
+ PreparedStatement preparedStatement,
+ int index,
+ Object value,
+ boolean calendarOverloadsSupported)
+ throws SQLException, HopValueException {
+
+ boolean asDate =
+ valueMeta.getPrecision() == 1 || !database.isSupportsTimeStampToDateConversion();
+
+ if (valueMeta.isNull(value)) {
+ preparedStatement.setNull(index, asDate ? Types.DATE : Types.TIMESTAMP);
+ return;
+ }
+
+ Calendar calendar =
+ calendarOverloadsSupported && valueMeta.getDateFormatTimeZone() != null
+ ? Calendar.getInstance(valueMeta.getDateFormatTimeZone())
+ : null;
+
+ if (asDate) {
+ java.sql.Date date = new java.sql.Date(valueMeta.getInteger(value));
+ if (calendar == null) {
+ preparedStatement.setDate(index, date);
+ } else {
+ preparedStatement.setDate(index, date, calendar);
+ }
+ return;
+ }
+
+ // Preserve nanosecond precision when the value already carries it.
+ Timestamp timestamp =
+ value instanceof Timestamp existing ? existing : new Timestamp(valueMeta.getInteger(value));
+ if (calendar == null) {
+ preparedStatement.setTimestamp(index, timestamp);
+ } else {
+ preparedStatement.setTimestamp(index, timestamp, calendar);
+ }
+ }
+
+ /** A binding for drivers that have not implemented the Calendar overloads. */
+ public static final IValueBinding WITHOUT_CALENDAR_OVERLOADS =
+ new IValueBinding() {
+ @Override
+ public Object read(
+ IDatabase database, IValueMeta valueMeta, java.sql.ResultSet resultSet, int index) {
+ throw new UnsupportedOperationException("This binding only writes values");
+ }
+
+ @Override
+ public void write(
+ IDatabase database,
+ IValueMeta valueMeta,
+ PreparedStatement preparedStatement,
+ int index,
+ Object value)
+ throws SQLException, HopValueException {
+ JdbcDateValues.write(database, valueMeta, preparedStatement, index, value, false);
+ }
+ };
+}
diff --git a/core/src/main/java/org/apache/hop/core/database/types/LegacyVariantBridge.java b/core/src/main/java/org/apache/hop/core/database/types/LegacyVariantBridge.java
new file mode 100644
index 0000000000..e52e7c7c12
--- /dev/null
+++ b/core/src/main/java/org/apache/hop/core/database/types/LegacyVariantBridge.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.core.database.types;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Predicate;
+import org.apache.hop.core.database.DatabasePluginType;
+import org.apache.hop.core.database.IDatabase;
+import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.core.plugins.IPlugin;
+import org.apache.hop.core.plugins.PluginRegistry;
+
+/**
+ * Keeps the deprecated {@code isXVariant()} flags working now that the rules they used to select
+ * are declared by the dialects.
+ *
+ * <p>A flag such as {@code isMySqlVariant()} means "treat me like MySQL", and some dialects say so
+ * without extending {@code MySqlDatabaseMeta} — Hive and SingleStore in Hop, and any number of
+ * plugins Hop does not ship. Those dialects declare no rules of their own, so this hands them the
+ * shared rules the flag was asking for.
+ *
+ * <p>It resolves to {@link ColumnTypeRules} directly rather than looking up the dialect the flag
+ * names. Going through the plugin registry would make compatibility depend on that plugin being
+ * installed, and fail silently when it is not.
+ *
+ * <p>Core is left holding only the mapping from each deprecated flag to the rules it implies, which
+ * is all the flag ever meant. When the flags go, this goes with them.
+ */
+public final class LegacyVariantBridge {
+
+ /** A deprecated flag whose rules are shared, because more than one dialect claims it. */
+ private record SharedFlag(Predicate<IDatabase> flag, List<IDatabaseTypeRule> rules) {}
+
+ /**
+ * isMySqlVariant() is the only flag with claimants other than the dialect it names: Generic, Hive
+ * and SingleStore all say they are MySQL-like without extending MySqlDatabaseMeta. Its rules are
+ * therefore shared rather than private to the MySQL plugin.
+ */
+ private static final List<SharedFlag> SHARED_FLAGS =
+ List.of(new SharedFlag(IDatabase::isMySqlVariant, ColumnTypeRules.MYSQL_COMPATIBLE));
+
+ /** A deprecated flag whose rules belong to the one dialect that declares them. */
+ private record DialectFlag(String dialectType, Predicate<IDatabase> flag) {}
+
+ /**
+ * These flags are answered only by the dialect they name, and by the dialects that extend it and
+ * so inherit its rules outright. Nothing in Hop needs them bridged. They are still bridged for
+ * the sake of a dialect from outside Hop that claims one, which is what the flag was for.
+ */
+ private static final List<DialectFlag> DIALECT_FLAGS =
+ List.of(
+ new DialectFlag("POSTGRESQL", IDatabase::isPostgresVariant),
+ new DialectFlag("ORACLE", IDatabase::isOracleVariant),
+ new DialectFlag("SQLITE", IDatabase::isSqliteVariant),
+ new DialectFlag("TERADATA", IDatabase::isTeradataVariant));
+
+ private static final Map<String, List<IDatabaseTypeRule>> dialectRuleCache =
+ new ConcurrentHashMap<>();
+
+ private LegacyVariantBridge() {
+ // Utility class.
+ }
+
+ /**
+ * The rules a dialect inherits purely by answering a deprecated flag.
+ *
+ * @param database the dialect being resolved
+ * @param ownDialectTypes its own plugin type and its ancestors', so that a dialect which already
+ * is the one a flag names is not handed its own rules a second time
+ */
+ public static List<IDatabaseTypeRule> forDialect(
+ IDatabase database, List<String> ownDialectTypes) {
+ List<IDatabaseTypeRule> rules = new ArrayList<>(2);
+ for (SharedFlag shared : SHARED_FLAGS) {
+ if (shared.flag().test(database)) {
+ rules.addAll(shared.rules());
+ }
+ }
+ for (DialectFlag variant : DIALECT_FLAGS) {
+ if (ownDialectTypes.contains(variant.dialectType()) || !variant.flag().test(database)) {
+ continue;
+ }
+ rules.addAll(rulesOf(variant.dialectType()));
+ }
+ return rules;
+ }
+
+ /**
+ * The rules declared by the dialect registered under this plugin id.
+ *
+ * <p>Reaching for a plugin makes this depend on that plugin being installed, so when it is not we
+ * say so rather than quietly doing nothing. Only a dialect from outside Hop can get here: the
+ * ones Hop ships either are that dialect or extend it.
+ */
+ private static List<IDatabaseTypeRule> rulesOf(String dialectType) {
+ return dialectRuleCache.computeIfAbsent(
+ dialectType,
+ type -> {
+ try {
+ IPlugin plugin = PluginRegistry.getInstance().getPlugin(DatabasePluginType.class, type);
+ if (plugin == null) {
+ LogChannel.GENERAL.logBasic(
+ "A database connection asks to be treated as "
+ + type
+ + ", but that database plugin is not installed, so its column type rules"
+ + " cannot be applied.");
+ return List.of();
+ }
+ Object dialect = PluginRegistry.getInstance().loadClass(plugin);
+ return dialect instanceof IDatabase resolved
+ ? List.copyOf(resolved.getTypeRules())
+ : List.<IDatabaseTypeRule>of();
+ } catch (Exception e) {
+ LogChannel.GENERAL.logError(
+ "Unable to read the column type rules of the " + type + " dialect", e);
+ return List.of();
+ }
+ });
+ }
+
+ /** Drops the cached dialect rules. Plugin registration changes between tests need this. */
+ public static void clearCache() {
+ dialectRuleCache.clear();
+ }
+}
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
new file mode 100644
index 0000000000..9548e70033
--- /dev/null
+++ b/core/src/main/java/org/apache/hop/core/database/types/StandardJdbcTypeMapper.java
@@ -0,0 +1,321 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.core.database.types;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import org.apache.hop.core.database.DatabaseMeta;
+import org.apache.hop.core.exception.HopDatabaseException;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.value.ValueMetaFactory;
+import org.apache.hop.core.variables.IVariables;
+
+/**
+ * The baseline mapping from a JDBC column to Hop value metadata.
+ *
+ * <p>Hop used to carry three copies of these rules — one in {@code Database}, one in {@code
+ * ValueMetaBase.getValueFromSqlType} and one in {@code ValueMetaBase.getMetadataPreview} — which
+ * had drifted apart in four places. This class is the single surviving copy, and it keeps the
+ * semantics of the {@code Database} version because that is the path production used and the only
+ * one of the three whose numeric handling round-trips (see JdbcTypeMappingCharacterizationTest).
+ *
+ * <p>The dialect-specific branches in here are the ones the type-mapping rework will move out to
+ * the dialects themselves. Until then, adding a new one means adding a vendor name to core, which
+ * is exactly the problem; prefer {@code IDatabase.customizeValueFromSqlType} in the meantime.
+ */
+public final class StandardJdbcTypeMapper {
+
+ private static final String CONST_FALSE = "false";
+
+ private StandardJdbcTypeMapper() {
+ // Utility class.
+ }
+
+ /**
+ * Maps a column onto Hop value metadata.
+ *
+ * @return the value metadata, or null when the SQL type is not one of the standard mappings, so
+ * that specialized value meta plugins (UUID, INET, JSON, ...) get their turn.
+ */
+ public static IValueMeta getValueMeta(
+ IVariables variables,
+ DatabaseMeta databaseMeta,
+ DatabaseColumn column,
+ boolean ignoreLength,
+ boolean lazyConversion)
+ throws HopDatabaseException {
+
+ int sqlType = column.getSqlType();
+ int valtype = IValueMeta.TYPE_NONE;
+ int length = -1;
+ int precision = -1;
+ boolean isClob = false;
+
+ switch (sqlType) {
+ case Types.CHAR,
+ Types.VARCHAR,
+ Types.NVARCHAR,
+ Types.LONGVARCHAR,
+ Types.NCHAR,
+ Types.LONGNVARCHAR:
+ valtype = IValueMeta.TYPE_STRING;
+ if (!ignoreLength) {
+ length = column.getDisplaySize();
+ }
+ break;
+
+ case Types.CLOB, Types.NCLOB:
+ valtype = IValueMeta.TYPE_STRING;
+ length = DatabaseMeta.CLOB_LENGTH;
+ isClob = true;
+ break;
+
+ case Types.BIGINT:
+ // An unsigned BIGINT overflows a Java Long, so it has to widen to BigNumber.
+ if (column.isSigned()) {
+ valtype = IValueMeta.TYPE_INTEGER;
+ precision = 0; // Max 9.223.372.036.854.775.807
+ length = 15;
+ } else {
+ valtype = IValueMeta.TYPE_BIGNUMBER;
+ precision = 0; // Max 18.446.744.073.709.551.615
+ length = 16;
+ }
+ break;
+
+ case Types.INTEGER:
+ valtype = IValueMeta.TYPE_INTEGER;
+ precision = 0; // Max 2.147.483.647
+ length = 9;
+ break;
+
+ case Types.SMALLINT:
+ valtype = IValueMeta.TYPE_INTEGER;
+ precision = 0; // Max 32.767
+ length = 4;
+ break;
+
+ case Types.TINYINT:
+ valtype = IValueMeta.TYPE_INTEGER;
+ precision = 0; // Max 127
+ length = 2;
+ break;
+
+ case Types.DECIMAL, Types.DOUBLE, Types.FLOAT, Types.REAL, Types.NUMERIC:
+ Numeric numeric = mapDecimal(databaseMeta, column, sqlType);
+ valtype = numeric.valtype();
+ length = numeric.length();
+ precision = numeric.precision();
+ break;
+
+ case Types.TIMESTAMP:
+ if (databaseMeta.supportsTimestampDataType()) {
+ valtype = IValueMeta.TYPE_TIMESTAMP;
+ length = column.getScale();
+ } else {
+ valtype = IValueMeta.TYPE_DATE;
+ }
+ break;
+
+ case Types.DATE, Types.TIME:
+ // MySQL's YEAR and Teradata's date marker used to be decided here. They are dialect rules
+ // now; see VariantTypeRules.
+ valtype = IValueMeta.TYPE_DATE;
+ break;
+
+ case Types.BOOLEAN, Types.BIT:
+ valtype = IValueMeta.TYPE_BOOLEAN;
+ break;
+
+ case Types.BINARY, Types.BLOB, Types.VARBINARY, Types.LONGVARBINARY:
+ // Oracle RAW, MySQL variable length binary and SQLite's dynamic typing used to be decided
+ // here. They are dialect rules now; see VariantTypeRules.
+ valtype = IValueMeta.TYPE_BINARY;
+ if (displaySizeIsTwiceThePrecision(databaseMeta, column)) {
+ // The length of a "CHAR(X) FOR BIT DATA" column.
+ length = column.getPrecision();
+ } else {
+ length = -1;
+ }
+ precision = -1;
+ break;
+
+ default:
+ return null;
+ }
+
+ return build(
+ databaseMeta, column, valtype, length, precision, isClob, ignoreLength, lazyConversion);
+ }
+
+ /**
+ * The mapping used when no standard rule and no value meta plugin claims the column: treat it as
+ * a string. Callers that must always produce a value (rather than deferring) use this.
+ */
+ public static IValueMeta getFallbackValueMeta(
+ DatabaseMeta databaseMeta,
+ DatabaseColumn column,
+ boolean ignoreLength,
+ boolean lazyConversion)
+ throws HopDatabaseException {
+ return build(
+ databaseMeta,
+ column,
+ IValueMeta.TYPE_STRING,
+ -1,
+ column.getScale(),
+ false,
+ ignoreLength,
+ lazyConversion);
+ }
+
+ /** The Hop type, length and precision decided for one numeric column. */
+ private record Numeric(int valtype, int length, int precision) {}
+
+ /** DECIMAL / NUMERIC / DOUBLE / FLOAT / REAL, which carry all size-dependent decisions. */
+ /**
+ * DECIMAL / NUMERIC / DOUBLE / FLOAT / REAL.
+ *
+ * <p>The dialect-specific adjustments that used to sit in here — Postgres double precision and
+ * undefined numerics, MySQL's overstated scale, Oracle's 38 digit numbers and undefined sizes —
+ * are dialect rules now; see ColumnTypeRules. Each of them fully determined the outcome, so they
+ * express cleanly as rules even though they read here like adjustments part way through.
+ */
+ private static Numeric mapDecimal(DatabaseMeta databaseMeta, DatabaseColumn column, int sqlType) {
+ int valtype = IValueMeta.TYPE_NUMBER;
+ int length = numericLength(column);
+ int precision = numericScale(column);
+
+ if (isApproximateNumeric(sqlType)) {
+ if (length > 15 || precision > 15) {
+ valtype = IValueMeta.TYPE_BIGNUMBER;
+ }
+ } else {
+ if (precision == 0) {
+ if (length <= 18 && length > 0) {
+ // A Long holds up to 18 significant digits.
+ valtype = IValueMeta.TYPE_INTEGER;
+ } else if (length > 18) {
+ valtype = IValueMeta.TYPE_BIGNUMBER;
+ }
+ } else if (length > 15 || precision > 15) {
+ valtype = IValueMeta.TYPE_BIGNUMBER;
+ }
+ }
+
+ return new Numeric(valtype, length, precision);
+ }
+
+ /** The tail every branch shares: build the value meta, attach original metadata, customize. */
+ private static IValueMeta build(
+ DatabaseMeta databaseMeta,
+ DatabaseColumn column,
+ int valtype,
+ int length,
+ int precision,
+ boolean isClob,
+ boolean ignoreLength,
+ boolean lazyConversion)
+ throws HopDatabaseException {
+ try {
+ IValueMeta valueMeta = ValueMetaFactory.createValueMeta(column.getName(), valtype);
+ valueMeta.setLength(length);
+ valueMeta.setPrecision(precision);
+ valueMeta.setLargeTextField(isClob);
+
+ setOriginalColumnMetadata(valueMeta, column, ignoreLength);
+
+ if (lazyConversion && valtype == IValueMeta.TYPE_STRING) {
+ valueMeta.setStorageType(IValueMeta.STORAGE_TYPE_BINARY_STRING);
+ IValueMeta storageMetaData =
+ ValueMetaFactory.cloneValueMeta(valueMeta, IValueMeta.TYPE_STRING);
+ storageMetaData.setStorageType(IValueMeta.STORAGE_TYPE_NORMAL);
+ valueMeta.setStorageMetadata(storageMetaData);
+ }
+
+ // Only the result set metadata path can offer this hook, which is what it took before.
+ if (column.getResultSetMetaData() != null) {
+ IValueMeta customized =
+ databaseMeta
+ .getIDatabase()
+ .customizeValueFromSqlType(
+ valueMeta, column.getResultSetMetaData(), column.getColumnIndex());
+ if (customized != null) {
+ return customized;
+ }
+ }
+ return valueMeta;
+ } catch (SQLException | RuntimeException | org.apache.hop.core.exception.HopPluginException e) {
+ throw new HopDatabaseException(
+ "Error determining value metadata from SQL resultset metadata", e);
+ }
+ }
+
+ /** True for the approximate numeric types, whose reported scale is not meaningful. */
+ public static boolean isApproximateNumeric(int sqlType) {
+ return sqlType == Types.DOUBLE || sqlType == Types.FLOAT || sqlType == Types.REAL;
+ }
+
+ /**
+ * The Hop length of a numeric column: the digits before the decimal.
+ *
+ * <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.
+ */
+ public static int numericLength(DatabaseColumn column) {
+ int length = column.getPrecision();
+ int scale = column.getScale();
+ if (length > 0 && length > scale) {
+ length -= scale;
+ }
+ 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();
+ if (scale >= 126) {
+ return -1;
+ }
+ // A scale of zero is meaningless on an approximate type.
+ return isApproximateNumeric(column.getSqlType()) && scale == 0 ? -1 : scale;
+ }
+
+ /**
+ * True when the column has the "CHAR(X) FOR BIT DATA" shape: some dialects report a binary
+ * column's display size as twice its precision.
+ *
+ * <p>Public because this check outranks every dialect rule for binary columns, and rules are
+ * consulted before the standard mapping, so a dialect rule has to defer to it explicitly.
+ */
+ public static boolean displaySizeIsTwiceThePrecision(
+ DatabaseMeta databaseMeta, DatabaseColumn column) {
+ return databaseMeta.isDisplaySizeTwiceThePrecision()
+ && (2 * column.getPrecision()) == column.getDisplaySize();
+ }
+
+ /** Records what the database actually reported, for transforms that need the raw truth. */
+ public static void setOriginalColumnMetadata(
+ IValueMeta valueMeta, DatabaseColumn column, boolean ignoreLength) {
+ valueMeta.setComments(column.getComment());
+ valueMeta.setOriginalColumnType(column.getSqlType());
+ valueMeta.setOriginalColumnTypeName(column.getNativeTypeName());
+ valueMeta.setOriginalPrecision(ignoreLength ? -1 : column.getPrecision());
+ valueMeta.setOriginalScale(column.getScale());
+ valueMeta.setOriginalSigned(column.isSigned());
+ }
+}
diff --git a/core/src/main/java/org/apache/hop/core/row/IValueMeta.java b/core/src/main/java/org/apache/hop/core/row/IValueMeta.java
index 8abe0cd278..c0715ff4b5 100644
--- a/core/src/main/java/org/apache/hop/core/row/IValueMeta.java
+++ b/core/src/main/java/org/apache/hop/core/row/IValueMeta.java
@@ -1283,7 +1283,12 @@ public interface IValueMeta extends Cloneable {
* @param lazyConversion use lazy conversion
* @return The value metadata if this value should handle the SQL type at the specified index.
* @throws HopDatabaseException In case something went wrong.
+ * @deprecated Superseded by {@link org.apache.hop.core.database.types.StandardJdbcTypeMapper},
+ * which carries the single copy of these rules. This is one of three implementations that had
+ * drifted apart; callers will be migrated to the mapper and this method removed in a later
+ * release.
*/
+ @Deprecated(since = "2.20")
IValueMeta getValueFromSqlType(
IVariables variables,
DatabaseMeta databaseMeta,
@@ -1305,7 +1310,11 @@ public interface IValueMeta extends Cloneable {
* @param variables
* @param databaseMeta the database metadata to reference capabilities and so on.
* @param rs A ResultSet from getColumns, positioned correctly on a column to read.
+ * @deprecated Superseded by {@link org.apache.hop.core.database.types.StandardJdbcTypeMapper}.
+ * This mapping had no callers left in Hop and had drifted from the one the engine actually
+ * uses; use the mapper instead.
*/
+ @Deprecated(since = "2.20")
IValueMeta getMetadataPreview(IVariables variables, DatabaseMeta databaseMeta, ResultSet rs)
throws HopDatabaseException;
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 823d60ca34..53df16d1d8 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
@@ -53,7 +53,6 @@ import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Arrays;
-import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.Locale;
@@ -65,6 +64,7 @@ import lombok.Setter;
import org.apache.hop.core.Const;
import org.apache.hop.core.database.DatabaseMeta;
import org.apache.hop.core.database.IDatabase;
+import org.apache.hop.core.database.types.JdbcDateValues;
import org.apache.hop.core.exception.HopDatabaseException;
import org.apache.hop.core.exception.HopEofException;
import org.apache.hop.core.exception.HopException;
@@ -5114,6 +5114,13 @@ public class ValueMetaBase implements IValueMeta {
this.ignoreWhitespace = ignoreWhitespace;
}
+ /**
+ * @deprecated Superseded by {@link org.apache.hop.core.database.types.StandardJdbcTypeMapper},
+ * which carries the single copy of these rules. This is one of three implementations that had
+ * drifted apart; callers will be migrated to the mapper and this method removed in a later
+ * release.
+ */
+ @Deprecated(since = "2.20")
@Override
public IValueMeta getValueFromSqlType(
IVariables variables,
@@ -5138,6 +5145,10 @@ public class ValueMetaBase implements IValueMeta {
// This JDBC Driver doesn't support the isSigned method
// nothing more we can do here by catch the exception.
}
+ // This whole mapping is superseded by StandardJdbcTypeMapper together with the rules each
+ // dialect declares; the vendor checks below name where each branch went. It is kept,
+ // unwired and unchanged, as the record of what Hop did before those rules existed, and
+ // JdbcTypeMappingCharacterizationTest compares the replacement against it.
switch (type) {
case Types.CHAR, Types.VARCHAR, Types.NVARCHAR, Types.LONGVARCHAR:
// Character Large Object
@@ -5202,6 +5213,8 @@ public class ValueMetaBase implements IValueMeta {
}
// If we're dealing with PostgreSQL and double precision types
+ // Superseded by PostgreSqlDatabaseMeta, which declares this reading of a double
+ // precision column.
if (databaseMeta.getIDatabase().isPostgresVariant()
&& type == Types.DOUBLE
&& precision >= 16
@@ -5212,6 +5225,8 @@ public class ValueMetaBase implements IValueMeta {
// MySQL: max resolution is double precision floating point (double)
// The (12,31) that is given back is not correct
+ // Superseded by ColumnTypeRules.OVERSCALED_APPROXIMATE_AS_UNSIZED_NUMBER, which
+ // MySqlDatabaseMeta declares.
if (databaseMeta.getIDatabase().isMySqlVariant() && precision >= length) {
precision = -1;
length = -1;
@@ -5237,6 +5252,8 @@ public class ValueMetaBase implements IValueMeta {
}
}
+ // Superseded by PostgreSqlDatabaseMeta, which declares this reading of an
+ // undefined numeric.
if (databaseMeta.getIDatabase().isPostgresVariant()
&& type == Types.NUMERIC
&& length == 0
@@ -5247,6 +5264,8 @@ public class ValueMetaBase implements IValueMeta {
precision = -1;
}
+ // Superseded by OracleDatabaseMeta, which declares both readings and asks its own
+ // strict big number option rather than the interface every dialect implements.
if (databaseMeta.getIDatabase().isOracleVariant()) {
if (precision == 0 && length == 38) {
valtype =
@@ -5275,6 +5294,7 @@ public class ValueMetaBase implements IValueMeta {
case Types.DATE, Types.TIME:
valtype = IValueMeta.TYPE_DATE;
//
+ // Superseded by ColumnTypeRules.YEAR_AS_INTEGER, which MySqlDatabaseMeta declares.
if (databaseMeta.getIDatabase().isMySqlVariant()) {
String property =
databaseMeta.getConnectionProperties(variables).getProperty("yearIsDateType");
@@ -5287,6 +5307,7 @@ public class ValueMetaBase implements IValueMeta {
length = 4;
}
}
+ // Superseded by TeradataDatabaseMeta, which declares the precision of one marker.
if (databaseMeta.getIDatabase().isTeradataVariant()) {
precision = 1;
}
@@ -5303,16 +5324,19 @@ public class ValueMetaBase implements IValueMeta {
&& (2 * rm.getPrecision(index)) == rm.getColumnDisplaySize(index)) {
// set the length for "CHAR(X) FOR BIT DATA"
length = rm.getPrecision(index);
+ // Superseded by OracleDatabaseMeta, which declares RAW and LONG RAW as strings.
} else if ((databaseMeta.getIDatabase().isOracleVariant())
&& (type == Types.VARBINARY || type == Types.LONGVARBINARY)) {
// set the length for Oracle "RAW" or "LONGRAW" data types
valtype = IValueMeta.TYPE_STRING;
length = rm.getColumnDisplaySize(index);
+ // Superseded by ColumnTypeRules.UNSIZED_VARIABLE_BINARY.
} else if (databaseMeta.isMySqlVariant()
&& (type == Types.VARBINARY || type == Types.LONGVARBINARY)) {
// don't call 'length = rm.getColumnDisplaySize(index);'
length = -1; // keep the length to -1, e.g. for string functions (e.g.
// CONCAT)
+ // Superseded by SqliteDatabaseMeta, which declares binary as text.
} else if (databaseMeta.getIDatabase().isSqliteVariant()) {
valtype = IValueMeta.TYPE_STRING;
} else {
@@ -5398,6 +5422,12 @@ public class ValueMetaBase implements IValueMeta {
v.setOriginalSigned(originalSigned);
}
+ /**
+ * @deprecated Superseded by {@link org.apache.hop.core.database.types.StandardJdbcTypeMapper}.
+ * This mapping had no callers left in Hop and had drifted from the one the engine actually
+ * uses; use the mapper instead.
+ */
+ @Deprecated(since = "2.20")
@Override
public IValueMeta getMetadataPreview(
IVariables variables, DatabaseMeta databaseMeta, ResultSet rs) throws HopDatabaseException {
@@ -5417,6 +5447,10 @@ public class ValueMetaBase implements IValueMeta {
int valtype = IValueMeta.TYPE_NONE;
boolean isClob = false;
+ // This whole mapping is superseded by StandardJdbcTypeMapper together with the rules each
+ // dialect declares; the vendor checks below name where each branch went. It is kept,
+ // unwired and unchanged, as the record of what Hop did before those rules existed, and
+ // JdbcTypeMappingCharacterizationTest compares the replacement against it.
switch (originalColumnType) {
case Types.CHAR, Types.VARCHAR, Types.NVARCHAR, Types.LONGVARCHAR:
// Character Large Object
@@ -5476,6 +5510,8 @@ public class ValueMetaBase implements IValueMeta {
}
// If we're dealing with PostgreSQL and double precision types
+ // Superseded by PostgreSqlDatabaseMeta, which declares this reading of a double
+ // precision column.
if (databaseMeta.getIDatabase().isPostgresVariant()
&& originalColumnType == Types.DOUBLE
&& precision >= 16
@@ -5486,6 +5522,8 @@ public class ValueMetaBase implements IValueMeta {
// MySQL: max resolution is double precision floating point (double)
// The (12,31) that is given back is not correct
+ // Superseded by ColumnTypeRules.OVERSCALED_APPROXIMATE_AS_UNSIZED_NUMBER, which
+ // MySqlDatabaseMeta declares.
if (databaseMeta.isMySqlVariant()) {
if (precision >= length) {
precision = -1;
@@ -5517,6 +5555,8 @@ public class ValueMetaBase implements IValueMeta {
}
}
+ // Superseded by PostgreSqlDatabaseMeta, which declares this reading of an
+ // undefined numeric.
if (databaseMeta.getIDatabase().isPostgresVariant()
&& originalColumnType == Types.NUMERIC
&& length == 0
@@ -5527,6 +5567,8 @@ public class ValueMetaBase implements IValueMeta {
precision = -1;
}
+ // Superseded by OracleDatabaseMeta, which declares both readings and asks its own
+ // strict big number option rather than the interface every dialect implements.
if (databaseMeta.getIDatabase().isOracleVariant()) {
if (precision == 0 && length == 38) {
valtype =
@@ -5555,6 +5597,7 @@ public class ValueMetaBase implements IValueMeta {
case Types.TIME, Types.DATE:
valtype = IValueMeta.TYPE_DATE;
//
+ // Superseded by ColumnTypeRules.YEAR_AS_INTEGER, which MySqlDatabaseMeta declares.
if (databaseMeta.isMySqlVariant()) {
String property =
databaseMeta.getConnectionProperties(variables).getProperty("yearIsDateType");
@@ -5567,6 +5610,7 @@ public class ValueMetaBase implements IValueMeta {
break;
}
}
+ // Superseded by TeradataDatabaseMeta, which declares the precision of one marker.
if (databaseMeta.getIDatabase().isTeradataVariant()) {
precision = 1;
}
@@ -5580,24 +5624,28 @@ public class ValueMetaBase implements IValueMeta {
valtype = IValueMeta.TYPE_BINARY;
IDatabase db = databaseMeta.getIDatabase();
+ // isOracle is never read: the check below derives it again. Dead with this copy.
boolean isOracle = db.isOracleVariant();
if (databaseMeta.isDisplaySizeTwiceThePrecision()
&& (2 * originalPrecision) == originalColumnDisplaySize) {
// set the length for "CHAR(X) FOR BIT DATA"
length = originalPrecision;
+ // Superseded by OracleDatabaseMeta, which declares RAW and LONG RAW as strings.
} else if ((databaseMeta.getIDatabase().isOracleVariant())
&& (originalColumnType == Types.VARBINARY
|| originalColumnType == Types.LONGVARBINARY)) {
// set the length for Oracle "RAW" or "LONGRAW" data types
valtype = IValueMeta.TYPE_STRING;
length = originalColumnDisplaySize;
+ // Superseded by ColumnTypeRules.UNSIZED_VARIABLE_BINARY.
} else if (databaseMeta.isMySqlVariant()
&& (originalColumnType == Types.VARBINARY
|| originalColumnType == Types.LONGVARBINARY)) {
// don't call 'length = rm.getColumnDisplaySize(index);'
length = -1; // keep the length to -1, e.g. for string functions (e.g.
// CONCAT)
+ // Superseded by SqliteDatabaseMeta, which declares binary as text.
} else if (databaseMeta.getIDatabase().isSqliteVariant()) {
valtype = IValueMeta.TYPE_STRING;
} else {
@@ -5684,10 +5732,6 @@ public class ValueMetaBase implements IValueMeta {
if (getPrecision() != 1 && iDatabase.isSupportsTimeStampToDateConversion()) {
data = resultSet.getTimestamp(index + 1);
break; // Timestamp extends java.util.Date
- } else if (iDatabase.isNetezzaVariant()) {
- // workaround for IBM Netezza jdbc 'special' implementation
- data = getNetezzaDateValueWorkaround(iDatabase, resultSet, index + 1);
- break;
} else {
data = resultSet.getDate(index + 1);
break;
@@ -5706,18 +5750,6 @@ public class ValueMetaBase implements IValueMeta {
}
}
- private Object getNetezzaDateValueWorkaround(IDatabase iDatabase, ResultSet resultSet, int index)
- throws SQLException {
- Object data = null;
- int type = resultSet.getMetaData().getColumnType(index);
- if (type == Types.TIME) {
- data = resultSet.getTime(index);
- } else {
- data = resultSet.getDate(index);
- }
- return data;
- }
-
@Override
public void setPreparedStatementValue(
DatabaseMeta databaseMeta, PreparedStatement preparedStatement, int index, Object data)
@@ -5788,70 +5820,8 @@ public class ValueMetaBase implements IValueMeta {
}
break;
case IValueMeta.TYPE_DATE:
- if (!isNull(data)) {
- // Environment variable to disable timezone setting for the database updates
- // When it is set, timezone will not be taken into account and the value will be
- // converted
- // into the local java timezone
- if (getPrecision() == 1 || !databaseMeta.supportsTimeStampToDateConversion()) {
- // Convert to DATE!
- long dat = getInteger(data); // converts using Date.getTime()
- java.sql.Date ddate = new java.sql.Date(dat);
- if (databaseMeta.getIDatabase().isDuckDbVariant()) {
- // As of DuckDB JDBC 0.10.0
- // setDate(int parameterIndex, Date x, Calendar cal)
- // is not yet implemented
- preparedStatement.setDate(index, ddate);
- } else {
- if (this.getDateFormatTimeZone() == null) {
- preparedStatement.setDate(index, ddate);
- } else {
- preparedStatement.setDate(
- index, ddate, Calendar.getInstance(this.getDateFormatTimeZone()));
- }
- }
- } else {
- if (data instanceof Timestamp timestamp) {
- // Preserve ns precision!
- //
- if (databaseMeta.getIDatabase().isDuckDbVariant()) {
- // As of DuckDB JDBC 0.10.0
- // setTimestamp(int parameterIndex, Timestamp x, Calendar cal)
- // is not yet implemented
- preparedStatement.setTimestamp(index, timestamp);
- } else {
- if (this.getDateFormatTimeZone() == null) {
- preparedStatement.setTimestamp(index, timestamp);
- } else {
- preparedStatement.setTimestamp(
- index, timestamp, Calendar.getInstance(this.getDateFormatTimeZone()));
- }
- }
- } else {
- long dat = getInteger(data); // converts using Date.getTime()
- Timestamp sdate = new Timestamp(dat);
- if (databaseMeta.getIDatabase().isDuckDbVariant()) {
- // As of DuckDB JDBC 0.10.0
- // setTimestamp(int parameterIndex, Timestamp x, Calendar cal)
- // is not yet implemented
- preparedStatement.setTimestamp(index, sdate);
- } else {
- if (this.getDateFormatTimeZone() == null) {
- preparedStatement.setTimestamp(index, sdate);
- } else {
- preparedStatement.setTimestamp(
- index, sdate, Calendar.getInstance(this.getDateFormatTimeZone()));
- }
- }
- }
- }
- } else {
- if (getPrecision() == 1 || !databaseMeta.supportsTimeStampToDateConversion()) {
- preparedStatement.setNull(index, Types.DATE);
- } else {
- preparedStatement.setNull(index, Types.TIMESTAMP);
- }
- }
+ JdbcDateValues.write(
+ databaseMeta.getIDatabase(), this, preparedStatement, index, data, true);
break;
case IValueMeta.TYPE_BOOLEAN:
if (databaseMeta.supportsBooleanDataType()) {
diff --git a/core/src/main/java/org/apache/hop/core/row/value/ValueMetaInternetAddress.java b/core/src/main/java/org/apache/hop/core/row/value/ValueMetaInternetAddress.java
index 8681a38cd9..846119bd76 100644
--- a/core/src/main/java/org/apache/hop/core/row/value/ValueMetaInternetAddress.java
+++ b/core/src/main/java/org/apache/hop/core/row/value/ValueMetaInternetAddress.java
@@ -446,6 +446,12 @@ public class ValueMetaInternetAddress extends ValueMetaDate {
}
}
+ /**
+ * @deprecated Superseded by {@link org.apache.hop.core.database.types.StandardJdbcTypeMapper}.
+ * This mapping had no callers left in Hop and had drifted from the one the engine actually
+ * uses; use the mapper instead.
+ */
+ @Deprecated(since = "2.20")
@Override
public IValueMeta getMetadataPreview(
IVariables variables, DatabaseMeta databaseMeta, ResultSet rs) throws HopDatabaseException {
@@ -469,6 +475,13 @@ public class ValueMetaInternetAddress extends ValueMetaDate {
return null;
}
+ /**
+ * @deprecated Superseded by {@link org.apache.hop.core.database.types.StandardJdbcTypeMapper},
+ * which carries the single copy of these rules. This is one of three implementations that had
+ * drifted apart; callers will be migrated to the mapper and this method removed in a later
+ * release.
+ */
+ @Deprecated(since = "2.20")
@Override
public IValueMeta getValueFromSqlType(
IVariables variables,
@@ -542,20 +555,9 @@ public class ValueMetaInternetAddress extends ValueMetaDate {
boolean addFieldName,
boolean addCr) {
- String retval = null;
- if (iDatabase.isPostgresVariant()) {
- if (addFieldName) {
- retval = getName() + " ";
- } else {
- retval = "";
- }
- retval += "INET";
- if (addCr) {
- retval += Const.CR;
- }
- }
-
- return retval;
+ // No neutral spelling for an internet address: the dialects that have a column type for it
+ // say so in their own type rules.
+ return null;
}
@Override
diff --git a/core/src/main/java/org/apache/hop/core/row/value/ValueMetaJson.java b/core/src/main/java/org/apache/hop/core/row/value/ValueMetaJson.java
index df95a9631f..615b4ce32b 100644
--- a/core/src/main/java/org/apache/hop/core/row/value/ValueMetaJson.java
+++ b/core/src/main/java/org/apache/hop/core/row/value/ValueMetaJson.java
@@ -263,14 +263,8 @@ public class ValueMetaJson extends ValueMetaBase {
return;
}
- // This handles both JSON and JSONB if Postgres.
- // other dbs don't accept type OTHER
- if (databaseMeta.getIDatabase().isPostgresVariant()) {
- preparedStatement.setObject(index, jn, Types.OTHER);
- return;
- }
-
- // generic fallback to String
+ // The neutral handling. A database that takes JSON as a typed object rather than a string
+ // says so with a binding of its own.
preparedStatement.setString(index, this.convertJsonToString(jn));
} catch (Exception e) {
throw new HopDatabaseException(
@@ -340,13 +334,9 @@ public class ValueMetaJson extends ValueMetaBase {
boolean useAutoIncrement,
boolean addFieldName,
boolean addCr) {
+ // The neutral spelling. A dialect that spells it differently, such as Postgres with JSONB,
+ // says so in its own type rules.
final String col = addFieldName ? getName() + " " : "";
- String def = "JSON";
-
- // Postgres advices non-legacy app to use JSONB instead of JSON
- if (iDatabase.isPostgresVariant()) {
- def = "JSONB";
- }
- return col + def + (addCr ? Const.CR : "");
+ return col + "JSON" + (addCr ? Const.CR : "");
}
}
diff --git a/core/src/main/java/org/apache/hop/core/row/value/ValueMetaTimestamp.java b/core/src/main/java/org/apache/hop/core/row/value/ValueMetaTimestamp.java
index cc1887611b..18e7b8a6c5 100644
--- a/core/src/main/java/org/apache/hop/core/row/value/ValueMetaTimestamp.java
+++ b/core/src/main/java/org/apache/hop/core/row/value/ValueMetaTimestamp.java
@@ -421,6 +421,12 @@ public class ValueMetaTimestamp extends ValueMetaDate {
return clone;
}
+ /**
+ * @deprecated Superseded by {@link org.apache.hop.core.database.types.StandardJdbcTypeMapper}.
+ * This mapping had no callers left in Hop and had drifted from the one the engine actually
+ * uses; use the mapper instead.
+ */
+ @Deprecated(since = "2.20")
@Override
public IValueMeta getMetadataPreview(
IVariables variables, DatabaseMeta databaseMeta, ResultSet rs) throws HopDatabaseException {
@@ -449,6 +455,13 @@ public class ValueMetaTimestamp extends ValueMetaDate {
return null;
}
+ /**
+ * @deprecated Superseded by {@link org.apache.hop.core.database.types.StandardJdbcTypeMapper},
+ * which carries the single copy of these rules. This is one of three implementations that had
+ * drifted apart; callers will be migrated to the mapper and this method removed in a later
+ * release.
+ */
+ @Deprecated(since = "2.20")
@Override
public IValueMeta getValueFromSqlType(
IVariables variables,
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 6cceafc489..2fcb33c1fe 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
@@ -383,7 +383,9 @@ class DatabaseTest {
doReturn(v).when(fields).getValueMeta(0);
boolean useAutoIncrement = true, semiColon = true;
- doReturn("double foo").when(meta).getFieldDefinition(v, tk, pk, useAutoIncrement);
+ doReturn("double foo")
+ .when(meta)
+ .getFieldDefinition(any(IVariables.class), eq(v), eq(tk), eq(pk), eq(useAutoIncrement));
doReturn(true).when(meta).requiresCreateTablePrimaryKeyAppend();
String statement =
db.getCreateTableStatement(tableName, fields, tk, useAutoIncrement, pk, semiColon);
diff --git a/core/src/test/java/org/apache/hop/core/database/JdbcTypeMappingCharacterizationTest.java b/core/src/test/java/org/apache/hop/core/database/JdbcTypeMappingCharacterizationTest.java
new file mode 100644
index 0000000000..a7294eabbd
--- /dev/null
+++ b/core/src/test/java/org/apache/hop/core/database/JdbcTypeMappingCharacterizationTest.java
@@ -0,0 +1,395 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.core.database;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import org.apache.hop.core.HopClientEnvironment;
+import org.apache.hop.core.database.types.DatabaseColumn;
+import org.apache.hop.core.database.types.DatabaseTypeMapper;
+import org.apache.hop.core.database.types.StandardJdbcTypeMapper;
+import org.apache.hop.core.logging.ILoggingObject;
+import org.apache.hop.core.logging.LogLevel;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.core.variables.Variables;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Characterizes the three JDBC-to-Hop type mappers that Hop currently carries, side by side, so the
+ * places where they disagree are enumerable instead of anecdotal.
+ *
+ * <ul>
+ * <li><b>DB</b> — {@code Database.getDataTypeFromKnownSqlType}, reached through the public {@code
+ * getQueryFieldsFromPreparedStatement}. This is the path production actually uses.
+ * <li><b>VMB</b> — {@code ValueMetaBase.getValueFromSqlType}, reached only when {@code
+ * HOP_DB_DDL_COMPATIBLE=true}.
+ * <li><b>PRV</b> — {@code ValueMetaBase.getMetadataPreview}, which has no production caller left.
+ * </ul>
+ *
+ * <p>Dialects are represented as mocked capability profiles. Only the ones core can answer are
+ * here: rules that belong to a single dialect now live in that dialect's plugin, and are checked
+ * there against the real dialect. MySQL remains because Generic, Hive and SingleStore claim to be
+ * MySQL-like without extending the MySQL dialect, so its rules are shared and resolved in core.
+ *
+ * <p>This started with 117 diverging cases across four root causes. All three now delegate to
+ * {@code StandardJdbcTypeMapper}, so the only {@code DIVERGE} lines left are the ones no shared
+ * implementation can remove: {@code DatabaseMetaData.getColumns()} does not report signedness, so
+ * the PRV path cannot tell an unsigned BIGINT from a signed one. Regenerate with {@code
+ * -Dhop.golden.update=true}.
+ */
+class JdbcTypeMappingCharacterizationTest {
+
+ private static final String UPDATE_PROPERTY = "hop.golden.update";
+ private static final String GOLDEN =
+ "/org/apache/hop/core/database/JdbcTypeMappingCharacterizationTest.txt";
+
+ private IVariables variables;
+
+ @BeforeAll
+ static void setUpClass() throws Exception {
+ HopClientEnvironment.init();
+ }
+
+ @Test
+ void mappersAgreeWhereTheGoldenSaysTheyDo() throws Exception {
+ variables = new Variables();
+ String actual = render();
+
+ if (Boolean.getBoolean(UPDATE_PROPERTY)) {
+ Path target = Paths.get("src", "test", "resources").resolve(GOLDEN.substring(1));
+ Files.createDirectories(target.getParent());
+ Files.writeString(target, actual, StandardCharsets.UTF_8);
+ System.out.println("Wrote golden file: " + target.toAbsolutePath());
+ return;
+ }
+
+ String expected;
+ try (InputStream in = getClass().getResourceAsStream(GOLDEN)) {
+ expected = in == null ? null : new String(in.readAllBytes(), StandardCharsets.UTF_8);
+ }
+ if (expected == null) {
+ fail(
+ "No golden at "
+ + GOLDEN
+ + ". Generate with -D"
+ + UPDATE_PROPERTY
+ + "=true.\n\n"
+ + actual);
+ }
+ assertEquals(
+ expected.trim(),
+ actual.trim(),
+ "JDBC type mapping changed. Regenerate with -D"
+ + UPDATE_PROPERTY
+ + "=true and justify"
+ + " every differing line; each one is a behaviour change for some database.");
+ }
+
+ private String render() throws Exception {
+ StringBuilder out = new StringBuilder();
+ out.append("# Side-by-side characterization of Hop's three JDBC -> Hop type mappers.\n");
+ out.append("# DB = Database.getDataTypeFromKnownSqlType (the live production path)\n");
+ out.append(
+ "# VMB = ValueMetaBase.getValueFromSqlType (only under HOP_DB_DDL_COMPATIBLE)\n");
+ out.append("# PRV = ValueMetaBase.getMetadataPreview (no production caller)\n");
+ out.append(
+ "# NEW = StandardJdbcTypeMapper (the replacement, not yet wired in)\n");
+ out.append(
+ "# NEW!=DB would mean the replacement has drifted from the live path. There are none.\n");
+ out.append(
+ "# All three now delegate to StandardJdbcTypeMapper, so DIVERGE lines are no longer\n");
+ out.append(
+ "# implementation differences. What remains is inherent: DatabaseMetaData.getColumns()\n");
+ out.append(
+ "# reports no signedness, so the PRV path cannot see an explicitly unsigned column.\n");
+
+ int diverging = 0;
+ for (Profile profile : profiles()) {
+ out.append("\n[").append(profile.name).append("]\n");
+ for (Column column : columns()) {
+ String db = describe(() -> viaDatabase(profile, column));
+ String vmb = describe(() -> viaValueMetaBase(profile, column));
+ String prv = describe(() -> viaMetadataPreview(profile, column));
+ String neu = describe(() -> viaNewMapper(profile, column));
+ boolean diverge = !(db.equals(vmb) && vmb.equals(prv));
+ if (diverge) {
+ diverging++;
+ }
+ out.append(
+ String.format(
+ "%-14s p=%4d s=%4d %-7s | DB=%-18s VMB=%-18s PRV=%-18s NEW=%-18s %s%s%n",
+ column.typeName,
+ column.precision,
+ column.scale,
+ column.signed ? "signed" : "unsigned",
+ db,
+ vmb,
+ prv,
+ neu,
+ diverge ? "DIVERGE" : "",
+ neu.equals(db) ? "" : " NEW!=DB"));
+ }
+ }
+ out.append("\n# diverging cases: ").append(diverging).append('\n');
+ return out.toString();
+ }
+
+ // ---------------------------------------------------------------- the three mappers
+
+ private IValueMeta viaDatabase(Profile profile, Column column) throws Exception {
+ DatabaseMeta meta = profile.databaseMeta();
+ Database database = new Database(logging(), variables, meta);
+
+ ResultSetMetaData rm = column.asResultSetMetaData();
+ PreparedStatement ps = mock(PreparedStatement.class);
+ when(ps.getMetaData()).thenReturn(rm);
+ Connection connection = mock(Connection.class);
+ when(connection.prepareStatement(anyString(), anyInt(), anyInt())).thenReturn(ps);
+ database.setConnection(connection);
+
+ IRowMeta rowMeta = database.getQueryFieldsFromPreparedStatement("select 1");
+ return rowMeta == null || rowMeta.isEmpty() ? null : rowMeta.getValueMeta(0);
+ }
+
+ private IValueMeta viaValueMetaBase(Profile profile, Column column) throws Exception {
+ return new ValueMetaString()
+ .getValueFromSqlType(
+ variables,
+ profile.databaseMeta(),
+ "COL",
+ column.asResultSetMetaData(),
+ 1,
+ false,
+ false);
+ }
+
+ /** The replacement mapper, kept alongside the three until callers are migrated. */
+ private IValueMeta viaNewMapper(Profile profile, Column column) throws Exception {
+ DatabaseMeta meta = profile.databaseMeta();
+ DatabaseColumn dc = DatabaseColumn.of(column.asResultSetMetaData(), 1, "COL");
+ // Through the full resolution path: contributed rules, the dialect's own, the legacy variant
+ // bridge, then the standard mapping.
+ IValueMeta v = DatabaseTypeMapper.getValueMeta(variables, meta, dc, false, false);
+ // Database falls back to the value meta plugin chain, which ends in a string, when the
+ // standard rules decline the column; mirror that so the two columns compare like for like.
+ return v == null ? StandardJdbcTypeMapper.getFallbackValueMeta(meta, dc, false, false) : v;
+ }
+
+ private IValueMeta viaMetadataPreview(Profile profile, Column column) throws Exception {
+ return new ValueMetaString()
+ .getMetadataPreview(variables, profile.databaseMeta(), column.asGetColumnsRow());
+ }
+
+ /** Renders a value meta as a stable "Type(length,precision)" string, or the failure it threw. */
+ private String describe(ThrowingSupplier supplier) {
+ try {
+ IValueMeta v = supplier.get();
+ if (v == null) {
+ return "<null>";
+ }
+ return v.getTypeDesc() + "(" + v.getLength() + "," + v.getPrecision() + ")";
+ } catch (Exception e) {
+ Throwable root = e;
+ while (root.getCause() != null) {
+ root = root.getCause();
+ }
+ return "!" + root.getClass().getSimpleName();
+ }
+ }
+
+ private interface ThrowingSupplier {
+ IValueMeta get() throws Exception;
+ }
+
+ private ILoggingObject logging() {
+ ILoggingObject log = mock(ILoggingObject.class);
+ when(log.getLogLevel()).thenReturn(LogLevel.NOTHING);
+ return log;
+ }
+
+ // ---------------------------------------------------------------- the matrix
+
+ /** One synthetic column, expressible through either JDBC metadata API. */
+ private static final class Column {
+ private final String typeName;
+ private final int sqlType;
+ private final int precision;
+ private final int scale;
+ private final boolean signed;
+
+ private Column(String typeName, int sqlType, int precision, int scale, boolean signed) {
+ this.typeName = typeName;
+ this.sqlType = sqlType;
+ this.precision = precision;
+ this.scale = scale;
+ this.signed = signed;
+ }
+
+ private ResultSetMetaData asResultSetMetaData() throws Exception {
+ ResultSetMetaData rm = mock(ResultSetMetaData.class);
+ when(rm.getColumnCount()).thenReturn(1);
+ when(rm.getColumnName(1)).thenReturn("COL");
+ when(rm.getColumnLabel(1)).thenReturn("COL");
+ when(rm.getColumnType(1)).thenReturn(sqlType);
+ when(rm.getColumnTypeName(1)).thenReturn(typeName);
+ when(rm.getPrecision(1)).thenReturn(precision);
+ when(rm.getScale(1)).thenReturn(scale);
+ when(rm.getColumnDisplaySize(1)).thenReturn(precision);
+ when(rm.isSigned(1)).thenReturn(signed);
+ return rm;
+ }
+
+ private ResultSet asGetColumnsRow() throws Exception {
+ ResultSet rs = mock(ResultSet.class);
+ when(rs.getString("COLUMN_NAME")).thenReturn("COL");
+ when(rs.getInt("DATA_TYPE")).thenReturn(sqlType);
+ when(rs.getObject("DECIMAL_DIGITS")).thenReturn(scale);
+ when(rs.getInt("DECIMAL_DIGITS")).thenReturn(scale);
+ when(rs.getInt("COLUMN_SIZE")).thenReturn(precision);
+ when(rs.getString("TYPE_NAME")).thenReturn(typeName);
+ when(rs.getString("REMARKS")).thenReturn("COL");
+ return rs;
+ }
+ }
+
+ private List<Column> columns() {
+ int[][] sizes = {{0, 0}, {38, 0}, {10, 2}, {20, 10}, {16, 16}, {126, 0}};
+ Object[][] sqlTypes = {
+ {"CHAR", Types.CHAR},
+ {"VARCHAR", Types.VARCHAR},
+ {"LONGVARCHAR", Types.LONGVARCHAR},
+ {"CLOB", Types.CLOB},
+ {"BIGINT", Types.BIGINT},
+ {"INTEGER", Types.INTEGER},
+ {"SMALLINT", Types.SMALLINT},
+ {"TINYINT", Types.TINYINT},
+ {"DECIMAL", Types.DECIMAL},
+ {"NUMERIC", Types.NUMERIC},
+ {"DOUBLE", Types.DOUBLE},
+ {"FLOAT", Types.FLOAT},
+ {"REAL", Types.REAL},
+ {"TIMESTAMP", Types.TIMESTAMP},
+ {"DATE", Types.DATE},
+ {"TIME", Types.TIME},
+ {"BOOLEAN", Types.BOOLEAN},
+ {"BIT", Types.BIT},
+ {"BINARY", Types.BINARY},
+ {"VARBINARY", Types.VARBINARY},
+ {"LONGVARBINARY", Types.LONGVARBINARY},
+ {"BLOB", Types.BLOB},
+ {"OTHER", Types.OTHER},
+ {"STRUCT", Types.STRUCT},
+ };
+
+ List<Column> list = new ArrayList<>();
+ for (Object[] t : sqlTypes) {
+ for (int[] size : sizes) {
+ list.add(new Column((String) t[0], (Integer) t[1], size[0], size[1], true));
+ }
+ }
+ // Unsigned BIGINT is handled differently by two of the three mappers.
+ list.add(new Column("BIGINT", Types.BIGINT, 20, 0, false));
+ // MySQL's YEAR special case keys off the column type name.
+ list.add(new Column("YEAR", Types.DATE, 4, 0, true));
+ return list;
+ }
+
+ /** A dialect reduced to the capability flags these mappers actually consult. */
+ private static final class Profile {
+ private final String name;
+ private boolean postgres;
+ private boolean mysql;
+ private boolean oracle;
+ private boolean strictBigNumber;
+ private boolean sqlite;
+ private boolean teradata;
+ private boolean netezza;
+ private boolean displaySizeTwiceThePrecision;
+ private boolean supportsTimestamp = true;
+
+ private Profile(String name) {
+ this.name = name;
+ }
+
+ private DatabaseMeta databaseMeta() {
+ IDatabase iDatabase = mock(IDatabase.class);
+ when(iDatabase.isPostgresVariant()).thenReturn(postgres);
+ when(iDatabase.isMySqlVariant()).thenReturn(mysql);
+ when(iDatabase.isOracleVariant()).thenReturn(oracle);
+ when(iDatabase.isStrictBigNumberInterpretation()).thenReturn(strictBigNumber);
+ when(iDatabase.isSqliteVariant()).thenReturn(sqlite);
+ when(iDatabase.isTeradataVariant()).thenReturn(teradata);
+ when(iDatabase.isNetezzaVariant()).thenReturn(netezza);
+
+ DatabaseMeta meta = mock(DatabaseMeta.class);
+ when(meta.getIDatabase()).thenReturn(iDatabase);
+ when(meta.getName()).thenReturn(name);
+ when(meta.isMySqlVariant()).thenReturn(mysql);
+ when(meta.supportsTimestampDataType()).thenReturn(supportsTimestamp);
+ when(meta.isDisplaySizeTwiceThePrecision()).thenReturn(displaySizeTwiceThePrecision);
+ when(meta.getConnectionProperties(org.mockito.ArgumentMatchers.any()))
+ .thenReturn(new Properties());
+ when(meta.stripCR(anyString())).thenReturn("select 1");
+ return meta;
+ }
+ }
+
+ private List<Profile> profiles() {
+ List<Profile> list = new ArrayList<>();
+ list.add(new Profile("PLAIN"));
+
+ Profile mysql = new Profile("MYSQL");
+ mysql.mysql = true;
+ list.add(mysql);
+
+ Profile netezza = new Profile("NETEZZA");
+ netezza.netezza = true;
+ list.add(netezza);
+
+ Profile twice = new Profile("DISPLAY_SIZE_TWICE_PRECISION");
+ twice.displaySizeTwiceThePrecision = true;
+ list.add(twice);
+
+ Profile noTimestamp = new Profile("NO_TIMESTAMP_SUPPORT");
+ noTimestamp.supportsTimestamp = false;
+ list.add(noTimestamp);
+
+ return list;
+ }
+}
diff --git a/core/src/test/java/org/apache/hop/core/database/types/DatabaseTypeRulesTest.java b/core/src/test/java/org/apache/hop/core/database/types/DatabaseTypeRulesTest.java
new file mode 100644
index 0000000000..764d42ccde
--- /dev/null
+++ b/core/src/test/java/org/apache/hop/core/database/types/DatabaseTypeRulesTest.java
@@ -0,0 +1,404 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.core.database.types;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import org.apache.hop.core.HopClientEnvironment;
+import org.apache.hop.core.database.DatabaseMeta;
+import org.apache.hop.core.database.DatabaseMetaPlugin;
+import org.apache.hop.core.database.IDatabase;
+import org.apache.hop.core.database.NoneDatabaseMeta;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.extension.IPluginMock;
+import org.apache.hop.core.plugins.PluginRegistry;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.value.ValueMetaBinary;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.core.variables.Variables;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/** Covers the type rule SPI: contribution, dialect matching, precedence and fallback. */
+class DatabaseTypeRulesTest {
+
+ private IVariables variables;
+ private final List<IPluginMock> registeredPlugins = new ArrayList<>();
+
+ @BeforeAll
+ static void setUpClass() throws HopException {
+ HopClientEnvironment.init();
+ }
+
+ @AfterEach
+ void tearDown() {
+ // The plugin registry is process-global, so a plugin registered here would otherwise be
+ // visible to every later test in this JVM.
+ registeredPlugins.forEach(
+ plugin ->
+ PluginRegistry.getInstance().removePlugin(DatabaseTypeRulesPluginType.class, plugin));
+ registeredPlugins.clear();
+ DatabaseTypeRuleRegistry.clearCache();
+ }
+
+ @BeforeEach
+ void setUp() {
+ variables = new Variables();
+ // Rules are cached per dialect class, and these tests change what is registered.
+ DatabaseTypeRuleRegistry.clearCache();
+ }
+
+ // ------------------------------------------------------------------ dialect identity
+
+ @DatabaseMetaPlugin(type = "TEST_BASE", typeDescription = "Test base dialect")
+ static class BaseTestDialect extends NoneDatabaseMeta {
+ @Override
+ public List<IDatabaseTypeRule> getTypeRules() {
+ return DatabaseTypes.rules()
+ .read(Types.OTHER)
+ .as(IValueMeta.TYPE_STRING, 42, 0)
+ .write(IValueMeta.TYPE_JSON)
+ .as("BASE_JSON")
+ .build();
+ }
+ }
+
+ @DatabaseMetaPlugin(type = "TEST_DERIVED", typeDescription = "Test derived dialect")
+ static class DerivedTestDialect extends BaseTestDialect {}
+
+ @Test
+ void dialectTypeChainFollowsTheClassHierarchy() {
+ // This is what replaces isPostgresVariant(): a derived dialect answers to its parent's name
+ // without anyone maintaining a list of Postgres-like databases.
+ assertEquals(
+ List.of("TEST_BASE", "NONE"),
+ DatabaseTypeRuleRegistry.getDialectTypes(new BaseTestDialect()));
+ assertEquals(
+ List.of("TEST_DERIVED", "TEST_BASE", "NONE"),
+ DatabaseTypeRuleRegistry.getDialectTypes(new DerivedTestDialect()));
+ }
+
+ @Test
+ void derivedDialectInheritsItsParentsRules() {
+ assertEquals(
+ "BASE_JSON",
+ DatabaseTypeMapper.getColumnType(
+ variables, metaFor(new DerivedTestDialect()), json(), createContext()));
+ }
+
+ // ------------------------------------------------------------------ read path
+
+ @Test
+ void dialectRuleClaimsTheColumnBeforeTheStandardMapping() throws Exception {
+ // Types.OTHER would fall through the standard mapping and return null.
+ DatabaseMeta meta = metaFor(new BaseTestDialect());
+ IValueMeta claimed =
+ DatabaseTypeMapper.getValueMeta(
+ variables, meta, column(Types.OTHER, "SOMETHING"), false, false);
+ assertNotNull(claimed);
+ assertTrue(claimed.isString());
+ assertEquals(42, claimed.getLength());
+ }
+
+ @Test
+ void standardMappingAppliesWhenNoRuleClaimsTheColumn() throws Exception {
+ DatabaseMeta meta = metaFor(new BaseTestDialect());
+ IValueMeta valueMeta =
+ DatabaseTypeMapper.getValueMeta(
+ variables, meta, column(Types.INTEGER, "INT4"), false, false);
+ assertNotNull(valueMeta);
+ assertTrue(valueMeta.isInteger());
+ assertEquals(9, valueMeta.getLength());
+ }
+
+ @Test
+ void unclaimedNonStandardTypeStillReturnsNullForTheValueMetaPlugins() throws Exception {
+ DatabaseMeta meta = metaFor(new PlainDialect());
+ assertNull(
+ DatabaseTypeMapper.getValueMeta(
+ variables, meta, column(Types.STRUCT, "SDO_GEOMETRY"), false, false));
+ }
+
+ @DatabaseMetaPlugin(type = "TEST_PLAIN", typeDescription = "Dialect with no rules")
+ static class PlainDialect extends NoneDatabaseMeta {}
+
+ // ------------------------------------------------------------------ builder semantics
+
+ @Test
+ void rulesMatchInDeclarationOrder() {
+ List<IDatabaseTypeRule> rules =
+ DatabaseTypes.rules()
+ .write(IValueMeta.TYPE_STRING)
+ .where(v -> v.getLength() <= 0)
+ .as("TEXT")
+ .write(IValueMeta.TYPE_STRING)
+ .as("VARCHAR")
+ .build();
+ DatabaseMeta meta = metaFor(new PlainDialect());
+
+ assertEquals("TEXT", first(rules, meta, new ValueMetaString("c")));
+ ValueMetaString sized = new ValueMetaString("c");
+ sized.setLength(10);
+ assertEquals("VARCHAR", first(rules, meta, sized));
+ }
+
+ @Test
+ void nativeTypeNameMatchingIsCaseInsensitiveAndSupportsPatterns() throws Exception {
+ List<IDatabaseTypeRule> byName =
+ DatabaseTypes.rules().readNative("jsonb").as(IValueMeta.TYPE_STRING, 1, 0).build();
+ List<IDatabaseTypeRule> byPattern =
+ DatabaseTypes.rules().readNativeMatching(".*INT.*").as(IValueMeta.TYPE_INTEGER).build();
+ DatabaseMeta meta = metaFor(new PlainDialect());
+
+ assertNotNull(byName.get(0).getValueMeta(variables, meta, column(Types.OTHER, "JSONB")));
+ assertNull(byName.get(0).getValueMeta(variables, meta, column(Types.OTHER, "JSON")));
+ assertNotNull(byPattern.get(0).getValueMeta(variables, meta, column(Types.INTEGER, "BIGINT")));
+ }
+
+ @Test
+ void aRuleCanSupplyABindingForTheColumnsItClaims() throws Exception {
+ IValueBinding binding = mock(IValueBinding.class);
+ IDatabase dialect =
+ new PlainDialect() {
+ @Override
+ public List<IDatabaseTypeRule> getTypeRules() {
+ return DatabaseTypes.rules()
+ .readNative("SDO_GEOMETRY")
+ .bind(binding)
+ .as(IValueMeta.TYPE_BINARY)
+ .build();
+ }
+ };
+ DatabaseTypeRuleRegistry.clearCache();
+ DatabaseMeta meta = metaFor(dialect);
+
+ // A binding is chosen from the dialect and the value, because by the time rows are moving
+ // there is no column metadata left.
+ assertEquals(
+ binding, DatabaseTypeMapper.getBinding(meta.getIDatabase(), new ValueMetaBinary("geom")));
+ assertNull(DatabaseTypeMapper.getBinding(meta.getIDatabase(), new ValueMetaString("name")));
+ }
+
+ // ------------------------------------------------------------------ external contribution
+
+ @DatabaseTypeRulesPlugin(
+ id = "test-external-rules",
+ dialects = {"TEST_BASE"})
+ public static class ExternalRules implements IDatabaseTypeRuleProvider {
+ @Override
+ public List<IDatabaseTypeRule> getTypeRules() {
+ return DatabaseTypes.rules().write(IValueMeta.TYPE_JSON).as("EXTERNAL_JSON").build();
+ }
+ }
+
+ @DatabaseTypeRulesPlugin(
+ id = "test-other-dialect-rules",
+ dialects = {"SOMETHING_ELSE"})
+ public static class RulesForAnotherDialect implements IDatabaseTypeRuleProvider {
+ @Override
+ public List<IDatabaseTypeRule> getTypeRules() {
+ return DatabaseTypes.rules().write(IValueMeta.TYPE_JSON).as("WRONG_DIALECT").build();
+ }
+ }
+
+ @DatabaseTypeRulesPlugin(
+ id = "test-missing-value-type-rules",
+ dialects = {"TEST_BASE"},
+ valueTypes = {"NoSuchValueType"})
+ public static class RulesNeedingAMissingValueType implements IDatabaseTypeRuleProvider {
+ @Override
+ public List<IDatabaseTypeRule> getTypeRules() {
+ return DatabaseTypes.rules().write(IValueMeta.TYPE_JSON).as("SHOULD_BE_SKIPPED").build();
+ }
+ }
+
+ @Test
+ void anExternalPluginCanAddRulesToADialectItDoesNotOwn() throws Exception {
+ register(new ExternalRules());
+ DatabaseTypeRuleRegistry.clearCache();
+
+ // The dialect's own rule says BASE_JSON; the contributed one is consulted first.
+ assertEquals(
+ "EXTERNAL_JSON",
+ DatabaseTypeMapper.getColumnType(
+ variables, metaFor(new BaseTestDialect()), json(), createContext()));
+ }
+
+ @Test
+ void contributedRulesOnlyApplyToTheDialectsTheyName() throws Exception {
+ register(new RulesForAnotherDialect());
+ DatabaseTypeRuleRegistry.clearCache();
+
+ assertEquals(
+ "BASE_JSON",
+ DatabaseTypeMapper.getColumnType(
+ variables, metaFor(new BaseTestDialect()), json(), createContext()));
+ }
+
+ @Test
+ void contributedRulesAreSkippedWhenTheirValueTypeIsNotInstalled() throws Exception {
+ register(new RulesNeedingAMissingValueType());
+ DatabaseTypeRuleRegistry.clearCache();
+
+ assertEquals(
+ "BASE_JSON",
+ DatabaseTypeMapper.getColumnType(
+ variables, metaFor(new BaseTestDialect()), json(), createContext()));
+ }
+
+ // ------------------------------------------------------------------ helpers
+
+ private void register(IDatabaseTypeRuleProvider provider) throws Exception {
+ IPluginMock plugin = mock(IPluginMock.class);
+ when(plugin.getIds())
+ .thenReturn(
+ new String[] {provider.getClass().getAnnotation(DatabaseTypeRulesPlugin.class).id()});
+ when(plugin.getName()).thenReturn("test rules");
+ when(plugin.getMainType()).thenReturn((Class) IDatabaseTypeRuleProvider.class);
+ when(plugin.loadClass(IDatabaseTypeRuleProvider.class)).thenReturn(provider);
+ PluginRegistry.getInstance().registerPlugin(DatabaseTypeRulesPluginType.class, plugin);
+ registeredPlugins.add(plugin);
+ }
+
+ private String first(List<IDatabaseTypeRule> rules, DatabaseMeta meta, IValueMeta valueMeta) {
+ for (IDatabaseTypeRule rule : rules) {
+ String type = rule.getColumnType(variables, meta.getIDatabase(), valueMeta, createContext());
+ if (type != null) {
+ return type;
+ }
+ }
+ return null;
+ }
+
+ private static ColumnContext createContext() {
+ return new ColumnContext(ColumnContext.Purpose.CREATE, null, null, false, false, false);
+ }
+
+ private static IValueMeta json() {
+ IValueMeta valueMeta = mock(IValueMeta.class);
+ when(valueMeta.getType()).thenReturn(IValueMeta.TYPE_JSON);
+ return valueMeta;
+ }
+
+ private static DatabaseMeta metaFor(IDatabase dialect) {
+ DatabaseMeta meta = mock(DatabaseMeta.class);
+ when(meta.getIDatabase()).thenReturn(dialect);
+ return meta;
+ }
+
+ private static DatabaseColumn column(int sqlType, String nativeTypeName) throws SQLException {
+ ResultSetMetaData rm = mock(ResultSetMetaData.class);
+ when(rm.getColumnName(1)).thenReturn("COL");
+ when(rm.getColumnLabel(1)).thenReturn("COL");
+ when(rm.getColumnType(1)).thenReturn(sqlType);
+ when(rm.getColumnTypeName(1)).thenReturn(nativeTypeName);
+ when(rm.getPrecision(1)).thenReturn(0);
+ when(rm.getScale(1)).thenReturn(0);
+ when(rm.getColumnDisplaySize(1)).thenReturn(0);
+ when(rm.isSigned(1)).thenReturn(true);
+ return DatabaseColumn.of(rm, 1);
+ }
+
+ // ------------------------------------------------------------------ legacy variant bridge
+
+ /** A dialect from outside Hop that has not migrated: it only answers the deprecated flags. */
+ static class UnmigratedExternalDialect extends NoneDatabaseMeta {
+ @Override
+ public boolean isMySqlVariant() {
+ return true;
+ }
+ }
+
+ @Test
+ void anExternalDialectThatOnlyAnswersTheVariantFlagStillGetsItsRules() throws Exception {
+ // The MySQL YEAR handling moved out of core's switch into a rule. A dialect that never heard
+ // of getTypeRules() has to keep behaving exactly as it did.
+ DatabaseMeta meta = mySqlStyleMeta(new UnmigratedExternalDialect(), "false");
+
+ IValueMeta valueMeta =
+ DatabaseTypeMapper.getValueMeta(variables, meta, column(Types.DATE, "YEAR"), false, false);
+
+ assertTrue(valueMeta.isInteger());
+ assertEquals(4, valueMeta.getLength());
+ assertEquals(0, valueMeta.getPrecision());
+ }
+
+ @Test
+ void theYearRuleDefersWhenTheDriverTreatsYearAsADate() throws Exception {
+ // yearIsDateType unset, so the column really is a date and the rule must not claim it.
+ DatabaseMeta meta = mySqlStyleMeta(new UnmigratedExternalDialect(), null);
+
+ IValueMeta valueMeta =
+ DatabaseTypeMapper.getValueMeta(variables, meta, column(Types.DATE, "YEAR"), false, false);
+
+ assertTrue(valueMeta.isDate());
+ }
+
+ @Test
+ void theYearRuleOnlyClaimsColumnsActuallyNamedYear() throws Exception {
+ DatabaseMeta meta = mySqlStyleMeta(new UnmigratedExternalDialect(), "false");
+
+ IValueMeta valueMeta =
+ DatabaseTypeMapper.getValueMeta(variables, meta, column(Types.DATE, "DATE"), false, false);
+
+ assertTrue(valueMeta.isDate());
+ }
+
+ /** A dialect that has migrated but still answers the flag, as it will during the deprecation. */
+ static class MigratedDialect extends UnmigratedExternalDialect {
+ @Override
+ public List<IDatabaseTypeRule> getTypeRules() {
+ return DatabaseTypes.rules().readNative("YEAR").as(IValueMeta.TYPE_STRING, 99, 0).build();
+ }
+ }
+
+ @Test
+ void aMigratedDialectsOwnRulesWinOverTheLegacyBridge() throws Exception {
+ DatabaseMeta meta = mySqlStyleMeta(new MigratedDialect(), "false");
+
+ IValueMeta valueMeta =
+ DatabaseTypeMapper.getValueMeta(variables, meta, column(Types.DATE, "YEAR"), false, false);
+
+ assertTrue(valueMeta.isString());
+ assertEquals(99, valueMeta.getLength());
+ }
+
+ private static DatabaseMeta mySqlStyleMeta(IDatabase dialect, String yearIsDateType) {
+ DatabaseMeta meta = mock(DatabaseMeta.class);
+ when(meta.getIDatabase()).thenReturn(dialect);
+ Properties properties = new Properties();
+ if (yearIsDateType != null) {
+ properties.setProperty("yearIsDateType", yearIsDateType);
+ }
+ when(meta.getConnectionProperties(org.mockito.ArgumentMatchers.any())).thenReturn(properties);
+ return meta;
+ }
+}
diff --git a/core/src/test/java/org/apache/hop/core/row/value/ValueMetaBaseSetPreparedStmntValueTest.java b/core/src/test/java/org/apache/hop/core/row/value/ValueMetaBaseSetPreparedStmntValueTest.java
index 92268c2ca8..dad6f8a3b1 100644
--- a/core/src/test/java/org/apache/hop/core/row/value/ValueMetaBaseSetPreparedStmntValueTest.java
+++ b/core/src/test/java/org/apache/hop/core/row/value/ValueMetaBaseSetPreparedStmntValueTest.java
@@ -53,6 +53,9 @@ class ValueMetaBaseSetPreparedStmntValueTest {
void setUp() {
dbMeta = mock(DatabaseMeta.class);
IDatabase iDb = mock(BaseDatabaseMeta.class);
+ // DatabaseMeta.supportsTimeStampToDateConversion() just forwards to the dialect, so stubbing
+ // only the one on DatabaseMeta leaves the two disagreeing.
+ when(iDb.isSupportsTimeStampToDateConversion()).thenReturn(true);
when(dbMeta.supportsTimeStampToDateConversion()).thenReturn(true);
when(dbMeta.getIDatabase()).thenReturn(iDb);
ps = mock(PreparedStatement.class);
diff --git a/core/src/test/java/org/apache/hop/core/row/value/ValueMetaJsonTest.java b/core/src/test/java/org/apache/hop/core/row/value/ValueMetaJsonTest.java
index 86e8047a74..be8410864c 100644
--- a/core/src/test/java/org/apache/hop/core/row/value/ValueMetaJsonTest.java
+++ b/core/src/test/java/org/apache/hop/core/row/value/ValueMetaJsonTest.java
@@ -237,16 +237,10 @@ class ValueMetaJsonTest {
@Test
void testDatabaseColumnTypeDefinition() {
+ // The value type gives the neutral spelling and knows about no database. A dialect that spells
+ // it differently says so in its own type rules: see PostgreSqlColumnDefinitionTest for JSONB.
ValueMetaJson vm = new ValueMetaJson("col");
- IDatabase pg = mock(IDatabase.class);
- when(pg.isPostgresVariant()).thenReturn(true);
IDatabase other = mock(IDatabase.class);
- when(other.isPostgresVariant()).thenReturn(false);
-
- assertEquals("JSONB", vm.getDatabaseColumnTypeDefinition(pg, null, null, false, false, false));
- assertEquals(
- "col JSONB" + Const.CR,
- vm.getDatabaseColumnTypeDefinition(pg, null, null, false, true, true));
assertEquals(
"JSON", vm.getDatabaseColumnTypeDefinition(other, null, null, false, false, false));
diff --git a/core/src/test/java/org/apache/hop/junit/database/BaseFieldDefinitionGoldenTest.java b/core/src/test/java/org/apache/hop/junit/database/BaseFieldDefinitionGoldenTest.java
new file mode 100644
index 0000000000..c87664ebcf
--- /dev/null
+++ b/core/src/test/java/org/apache/hop/junit/database/BaseFieldDefinitionGoldenTest.java
@@ -0,0 +1,313 @@
+/*
+ * 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.junit.database;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+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.row.IValueMeta;
+import org.apache.hop.core.row.value.ValueMetaBigNumber;
+import org.apache.hop.core.row.value.ValueMetaBinary;
+import org.apache.hop.core.row.value.ValueMetaBoolean;
+import org.apache.hop.core.row.value.ValueMetaDate;
+import org.apache.hop.core.row.value.ValueMetaInteger;
+import org.apache.hop.core.row.value.ValueMetaInternetAddress;
+import org.apache.hop.core.row.value.ValueMetaJson;
+import org.apache.hop.core.row.value.ValueMetaNumber;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.core.row.value.ValueMetaTimestamp;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Snapshots the DDL a database dialect generates for a fixed matrix of Hop value types, so that
+ * refactoring the type mapping cannot silently change generated DDL.
+ *
+ * <p>The golden files record what Hop does <em>today</em>, bugs included. They are a change
+ * detector, not a statement of correctness: a line that looks wrong (see the unsized INTEGER rows,
+ * issue #4174) is the bug held still so that fixing it shows up as a reviewable diff.
+ *
+ * <p>To regenerate after an intentional change, run with {@code -Dhop.golden.update=true} and read
+ * the resulting diff line by line.
+ *
+ * <p>Subclasses live in the database plugin they cover and only supply the dialect:
+ *
+ * <pre>
+ * class PostgreSqlFieldDefinitionGoldenTest extends BaseFieldDefinitionGoldenTest {
+ * @Override
+ * protected IDatabase createDatabase() {
+ * return new PostgreSqlDatabaseMeta();
+ * }
+ * }
+ * </pre>
+ */
+public abstract class BaseFieldDefinitionGoldenTest {
+
+ private static final String UPDATE_PROPERTY = "hop.golden.update";
+
+ /** The column name used throughout the matrix. */
+ private static final String COLUMN = "COL";
+
+ /** The table name used by the ALTER TABLE matrix. */
+ private static final String TABLE = "TBL";
+
+ /** Length/precision pairs exercised for every type. */
+ private static final int[][] SIZES = {
+ {-1, -1},
+ {0, 0},
+ {4, 0},
+ {9, 0},
+ {15, 0},
+ {18, 0},
+ {20, 0},
+ {10, 2},
+ {DatabaseMeta.CLOB_LENGTH, 0}
+ };
+
+ /** A shorter matrix for the ALTER statements: unsized, sized, and sized with a precision. */
+ private static final int[][] ALTER_SIZES = {
+ {-1, -1},
+ {15, 0},
+ {10, 2}
+ };
+
+ /**
+ * @return a fresh dialect instance to snapshot.
+ */
+ protected abstract IDatabase createDatabase();
+
+ /**
+ * @return the classpath resource holding the golden output. Defaults to a file named after the
+ * test class, next to it.
+ */
+ protected String getGoldenResource() {
+ return "/" + getClass().getName().replace('.', '/') + ".txt";
+ }
+
+ @BeforeAll
+ static void setUpGoldenTestClass() throws Exception {
+ HopClientEnvironment.init();
+ }
+
+ @Test
+ void fieldDefinitionsMatchGolden() throws IOException {
+ String actual = render();
+
+ if (Boolean.getBoolean(UPDATE_PROPERTY)) {
+ writeGolden(actual);
+ return;
+ }
+
+ String expected = readGolden();
+ if (expected == null) {
+ fail(
+ "No golden file found at "
+ + getGoldenResource()
+ + ". Generate it with -D"
+ + UPDATE_PROPERTY
+ + "=true and review every line before committing.\n\n"
+ + actual);
+ }
+ assertEquals(
+ expected.trim(),
+ actual.trim(),
+ "Generated DDL changed for "
+ + createDatabase().getClass().getSimpleName()
+ + ". If the change is intended, regenerate with -D"
+ + UPDATE_PROPERTY
+ + "=true and justify every differing line in the commit message.");
+ }
+
+ /** Builds the full matrix as stable, diffable text. */
+ private String render() {
+ IDatabase database = createDatabase();
+ // Through DatabaseMeta rather than the dialect directly: that is the path the engine takes,
+ // so it is the only one that records what a user's database actually receives. Asking the
+ // dialect on its own skips the dialect's type rules and the value types, which is how JSON
+ // and INET came to be recorded as UNKNOWN everywhere.
+ DatabaseMeta databaseMeta = new DatabaseMeta();
+ databaseMeta.setIDatabase(database);
+ StringBuilder out = new StringBuilder();
+ out.append("# ").append(database.getClass().getName()).append('\n');
+ out.append("# Generated by ")
+ .append(getClass().getSimpleName())
+ .append(". Do not hand-edit.\n");
+ out.append("# Records current behaviour including known bugs. See the class javadoc.\n");
+
+ out.append("\n[plain]\n");
+ for (TypeCase type : types()) {
+ for (int[] size : SIZES) {
+ out.append(line(databaseMeta, type, size[0], size[1], null, null, false));
+ }
+ }
+
+ // Technical/primary key and auto-increment only change the outcome for numeric types.
+ out.append("\n[keys]\n");
+ for (TypeCase type : numericTypes()) {
+ for (int[] size : SIZES) {
+ out.append(line(databaseMeta, type, size[0], size[1], COLUMN, null, false));
+ out.append(line(databaseMeta, type, size[0], size[1], null, COLUMN, false));
+ out.append(line(databaseMeta, type, size[0], size[1], COLUMN, null, true));
+ }
+ }
+
+ // ALTER TABLE has to spell a column the way CREATE TABLE did. It reaches the type through a
+ // different route, so it is snapshotted separately; a few sizes are enough, because what the
+ // type resolves to is already covered above and what is under test here is the route.
+ out.append("\n[alter]\n");
+ for (TypeCase type : types()) {
+ for (int[] size : ALTER_SIZES) {
+ out.append(alterLine(databaseMeta, type, size[0], size[1], true));
+ out.append(alterLine(databaseMeta, type, size[0], size[1], false));
+ }
+ }
+ return out.toString();
+ }
+
+ /** One ADD COLUMN or MODIFY COLUMN statement, flattened to a single line. */
+ private String alterLine(
+ DatabaseMeta databaseMeta, TypeCase type, int length, int precision, boolean add) {
+
+ IValueMeta v = type.create();
+ v.setLength(length);
+ v.setPrecision(precision);
+
+ String result;
+ try {
+ String statement =
+ add
+ ? databaseMeta.getAddColumnStatement(TABLE, v, null, false, null, false)
+ : databaseMeta.getModifyColumnStatement(TABLE, v, null, false, null, false);
+ result = statement == null ? "<null>" : statement.replaceAll("\\s+", " ").trim();
+ } catch (Exception e) {
+ result = "!! " + e.getClass().getSimpleName() + ": " + e.getMessage();
+ }
+
+ return String.format(
+ "%-12s len=%8d prec=%4d %-8s -> %s%n",
+ type.name, length, precision, add ? "add" : "modify", result);
+ }
+
+ private String line(
+ DatabaseMeta databaseMeta,
+ TypeCase type,
+ int length,
+ int precision,
+ String tk,
+ String pk,
+ boolean useAutoIncrement) {
+
+ // A fresh value meta every time: several dialects mutate it (CLOB_LENGTH clamping).
+ IValueMeta v = type.create();
+ v.setLength(length);
+ v.setPrecision(precision);
+
+ String result;
+ try {
+ // addFieldName=false, addCr=false so the golden holds the type only.
+ result = databaseMeta.getFieldDefinition(v, tk, pk, useAutoIncrement, false, false);
+ result = result == null ? "<null>" : result.trim();
+ } catch (Exception e) {
+ // Capture crashes too: some combinations throw today and that is worth freezing.
+ result = "!! " + e.getClass().getSimpleName() + ": " + e.getMessage();
+ }
+
+ String flag;
+ if (useAutoIncrement) {
+ flag = "autoinc";
+ } else if (tk != null) {
+ flag = "tk";
+ } else if (pk != null) {
+ flag = "pk";
+ } else {
+ flag = "-";
+ }
+
+ return String.format(
+ "%-12s len=%8d prec=%4d %-8s -> %s%n", type.name, length, precision, flag, result);
+ }
+
+ private List<TypeCase> types() {
+ List<TypeCase> list = new ArrayList<>();
+ list.add(new TypeCase("STRING", () -> new ValueMetaString(COLUMN)));
+ list.add(new TypeCase("INTEGER", () -> new ValueMetaInteger(COLUMN)));
+ list.add(new TypeCase("NUMBER", () -> new ValueMetaNumber(COLUMN)));
+ list.add(new TypeCase("BIGNUMBER", () -> new ValueMetaBigNumber(COLUMN)));
+ list.add(new TypeCase("DATE", () -> new ValueMetaDate(COLUMN)));
+ list.add(new TypeCase("TIMESTAMP", () -> new ValueMetaTimestamp(COLUMN)));
+ list.add(new TypeCase("BOOLEAN", () -> new ValueMetaBoolean(COLUMN)));
+ list.add(new TypeCase("BINARY", () -> new ValueMetaBinary(COLUMN)));
+ list.add(new TypeCase("INET", () -> new ValueMetaInternetAddress(COLUMN)));
+ list.add(new TypeCase("JSON", () -> new ValueMetaJson(COLUMN)));
+ return list;
+ }
+
+ private List<TypeCase> numericTypes() {
+ List<TypeCase> list = new ArrayList<>();
+ list.add(new TypeCase("INTEGER", () -> new ValueMetaInteger(COLUMN)));
+ list.add(new TypeCase("NUMBER", () -> new ValueMetaNumber(COLUMN)));
+ list.add(new TypeCase("BIGNUMBER", () -> new ValueMetaBigNumber(COLUMN)));
+ return list;
+ }
+
+ private String readGolden() throws IOException {
+ try (InputStream in = getClass().getResourceAsStream(getGoldenResource())) {
+ if (in == null) {
+ return null;
+ }
+ return new String(in.readAllBytes(), StandardCharsets.UTF_8);
+ }
+ }
+
+ /** Writes the golden into src/test/resources so the update run produces a reviewable diff. */
+ private void writeGolden(String content) throws IOException {
+ Path target =
+ Paths.get("src", "test", "resources")
+ .resolve(getGoldenResource().substring(1))
+ .toAbsolutePath();
+ Files.createDirectories(target.getParent());
+ Files.writeString(target, content, StandardCharsets.UTF_8);
+ System.out.println("Wrote golden file: " + target);
+ }
+
+ /** One value type in the matrix, created fresh per case. */
+ private static final class TypeCase {
+ private final String name;
+ private final Supplier<IValueMeta> factory;
+
+ private TypeCase(String name, Supplier<IValueMeta> factory) {
+ this.name = name;
+ this.factory = factory;
+ }
+
+ private IValueMeta create() {
+ return factory.get();
+ }
+ }
+}
diff --git a/core/src/test/java/org/apache/hop/junit/database/TypeRuleFixture.java b/core/src/test/java/org/apache/hop/junit/database/TypeRuleFixture.java
new file mode 100644
index 0000000000..8e199d3fe2
--- /dev/null
+++ b/core/src/test/java/org/apache/hop/junit/database/TypeRuleFixture.java
@@ -0,0 +1,92 @@
+/*
+ * 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.junit.database;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.Properties;
+import org.apache.hop.core.database.DatabaseMeta;
+import org.apache.hop.core.database.IDatabase;
+import org.apache.hop.core.database.types.DatabaseColumn;
+
+/**
+ * Builds the JDBC metadata a dialect's type rules are matched against, so that each database plugin
+ * can check its own rules against a real dialect instance rather than a mocked one.
+ */
+public final class TypeRuleFixture {
+
+ private TypeRuleFixture() {
+ // Utility class.
+ }
+
+ /** A connection on the given dialect, with no connection properties and no special shapes. */
+ public static DatabaseMeta meta(IDatabase dialect) {
+ return meta(dialect, new Properties(), false);
+ }
+
+ /** A connection on the given dialect. */
+ public static DatabaseMeta meta(
+ IDatabase dialect, Properties connectionProperties, boolean displaySizeTwiceThePrecision) {
+ DatabaseMeta meta = mock(DatabaseMeta.class);
+ when(meta.getIDatabase()).thenReturn(dialect);
+ when(meta.getConnectionProperties(any())).thenReturn(connectionProperties);
+ when(meta.isDisplaySizeTwiceThePrecision()).thenReturn(displaySizeTwiceThePrecision);
+ when(meta.supportsTimestampDataType()).thenReturn(true);
+ return meta;
+ }
+
+ /** Connection properties holding a single entry. */
+ public static Properties properties(String key, String value) {
+ Properties properties = new Properties();
+ properties.setProperty(key, value);
+ return properties;
+ }
+
+ /** A numeric column, described the way a driver would report it on a result set. */
+ public static DatabaseColumn numericColumn(
+ int sqlType, String nativeTypeName, int precision, int scale) throws SQLException {
+ ResultSetMetaData rm = mock(ResultSetMetaData.class);
+ when(rm.getColumnName(1)).thenReturn("COL");
+ when(rm.getColumnLabel(1)).thenReturn("COL");
+ when(rm.getColumnType(1)).thenReturn(sqlType);
+ when(rm.getColumnTypeName(1)).thenReturn(nativeTypeName);
+ when(rm.getPrecision(1)).thenReturn(precision);
+ when(rm.getScale(1)).thenReturn(scale);
+ when(rm.getColumnDisplaySize(1)).thenReturn(precision);
+ when(rm.isSigned(1)).thenReturn(true);
+ return DatabaseColumn.of(rm, 1);
+ }
+
+ /** A column, described the way a driver would report it on a result set. */
+ public static DatabaseColumn column(
+ int sqlType, String nativeTypeName, int precision, int displaySize) throws SQLException {
+ ResultSetMetaData rm = mock(ResultSetMetaData.class);
+ when(rm.getColumnName(1)).thenReturn("COL");
+ when(rm.getColumnLabel(1)).thenReturn("COL");
+ when(rm.getColumnType(1)).thenReturn(sqlType);
+ when(rm.getColumnTypeName(1)).thenReturn(nativeTypeName);
+ when(rm.getPrecision(1)).thenReturn(precision);
+ when(rm.getScale(1)).thenReturn(0);
+ when(rm.getColumnDisplaySize(1)).thenReturn(displaySize);
+ when(rm.isSigned(1)).thenReturn(true);
+ return DatabaseColumn.of(rm, 1);
+ }
+}
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
new file mode 100644
index 0000000000..654cb89e3c
--- /dev/null
+++ b/core/src/test/resources/org/apache/hop/core/database/JdbcTypeMappingCharacterizationTest.txt
@@ -0,0 +1,751 @@
+# Side-by-side characterization of Hop's three JDBC -> Hop type mappers.
+# DB = Database.getDataTypeFromKnownSqlType (the live production path)
+# VMB = ValueMetaBase.getValueFromSqlType (only under HOP_DB_DDL_COMPATIBLE)
+# PRV = ValueMetaBase.getMetadataPreview (no production caller)
+# NEW = StandardJdbcTypeMapper (the replacement, not yet wired in)
+# NEW!=DB would mean the replacement has drifted from the live path. There are none.
+# All three now delegate to StandardJdbcTypeMapper, so DIVERGE lines are no longer
+# implementation differences. What remains is inherent: DatabaseMetaData.getColumns()
+# reports no signedness, so the PRV path cannot see an explicitly unsigned column.
+
+[PLAIN]
+CHAR p= 0 s= 0 signed | DB=String(0,-1) VMB=String(0,-1) PRV=String(0,-1) NEW=String(0,-1)
+CHAR p= 38 s= 0 signed | DB=String(38,-1) VMB=String(38,-1) PRV=String(38,-1) NEW=String(38,-1)
+CHAR p= 10 s= 2 signed | DB=String(10,-1) VMB=String(10,-1) PRV=String(10,-1) NEW=String(10,-1)
+CHAR p= 20 s= 10 signed | DB=String(20,-1) VMB=String(20,-1) PRV=String(20,-1) NEW=String(20,-1)
+CHAR p= 16 s= 16 signed | DB=String(16,-1) VMB=String(16,-1) PRV=String(16,-1) NEW=String(16,-1)
+CHAR p= 126 s= 0 signed | DB=String(126,-1) VMB=String(126,-1) PRV=String(126,-1) NEW=String(126,-1)
+VARCHAR p= 0 s= 0 signed | DB=String(0,-1) VMB=String(0,-1) PRV=String(0,-1) NEW=String(0,-1)
+VARCHAR p= 38 s= 0 signed | DB=String(38,-1) VMB=String(38,-1) PRV=String(38,-1) NEW=String(38,-1)
+VARCHAR p= 10 s= 2 signed | DB=String(10,-1) VMB=String(10,-1) PRV=String(10,-1) NEW=String(10,-1)
+VARCHAR p= 20 s= 10 signed | DB=String(20,-1) VMB=String(20,-1) PRV=String(20,-1) NEW=String(20,-1)
+VARCHAR p= 16 s= 16 signed | DB=String(16,-1) VMB=String(16,-1) PRV=String(16,-1) NEW=String(16,-1)
+VARCHAR p= 126 s= 0 signed | DB=String(126,-1) VMB=String(126,-1) PRV=String(126,-1) NEW=String(126,-1)
+LONGVARCHAR p= 0 s= 0 signed | DB=String(0,-1) VMB=String(0,-1) PRV=String(0,-1) NEW=String(0,-1)
+LONGVARCHAR p= 38 s= 0 signed | DB=String(38,-1) VMB=String(38,-1) PRV=String(38,-1) NEW=String(38,-1)
+LONGVARCHAR p= 10 s= 2 signed | DB=String(10,-1) VMB=String(10,-1) PRV=String(10,-1) NEW=String(10,-1)
+LONGVARCHAR p= 20 s= 10 signed | DB=String(20,-1) VMB=String(20,-1) PRV=String(20,-1) NEW=String(20,-1)
+LONGVARCHAR p= 16 s= 16 signed | DB=String(16,-1) VMB=String(16,-1) PRV=String(16,-1) NEW=String(16,-1)
+LONGVARCHAR p= 126 s= 0 signed | DB=String(126,-1) VMB=String(126,-1) PRV=String(126,-1) NEW=String(126,-1)
+CLOB p= 0 s= 0 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 38 s= 0 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 10 s= 2 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 20 s= 10 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 16 s= 16 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 126 s= 0 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+BIGINT p= 0 s= 0 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 38 s= 0 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 10 s= 2 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 20 s= 10 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 16 s= 16 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 126 s= 0 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+INTEGER p= 0 s= 0 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 38 s= 0 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 10 s= 2 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 20 s= 10 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 16 s= 16 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 126 s= 0 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+SMALLINT p= 0 s= 0 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 38 s= 0 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 10 s= 2 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 20 s= 10 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 16 s= 16 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 126 s= 0 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+TINYINT p= 0 s= 0 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=Integer(2,0)
+TINYINT p= 38 s= 0 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=Integer(2,0)
+TINYINT p= 10 s= 2 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=Integer(2,0)
+TINYINT p= 20 s= 10 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=Integer(2,0)
+TINYINT p= 16 s= 16 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=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= 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= 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= 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= 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= 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)
+TIMESTAMP p= 38 s= 0 signed | DB=Timestamp(0,-1) VMB=Timestamp(0,-1) PRV=Timestamp(0,-1) NEW=Timestamp(0,-1)
+TIMESTAMP p= 10 s= 2 signed | DB=Timestamp(2,-1) VMB=Timestamp(2,-1) PRV=Timestamp(2,-1) NEW=Timestamp(2,-1)
+TIMESTAMP p= 20 s= 10 signed | DB=Timestamp(10,-1) VMB=Timestamp(10,-1) PRV=Timestamp(10,-1) NEW=Timestamp(10,-1)
+TIMESTAMP p= 16 s= 16 signed | DB=Timestamp(16,-1) VMB=Timestamp(16,-1) PRV=Timestamp(16,-1) NEW=Timestamp(16,-1)
+TIMESTAMP p= 126 s= 0 signed | DB=Timestamp(0,-1) VMB=Timestamp(0,-1) PRV=Timestamp(0,-1) NEW=Timestamp(0,-1)
+DATE p= 0 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 38 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 10 s= 2 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 20 s= 10 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 16 s= 16 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 126 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 0 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 38 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 10 s= 2 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 20 s= 10 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 16 s= 16 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 126 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+BOOLEAN p= 0 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 38 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 10 s= 2 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 20 s= 10 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 16 s= 16 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 126 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 0 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 38 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 10 s= 2 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 20 s= 10 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 16 s= 16 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 126 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BINARY p= 0 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 38 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 10 s= 2 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 20 s= 10 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 16 s= 16 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 126 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 0 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 38 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 10 s= 2 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 20 s= 10 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 16 s= 16 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 126 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 0 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 38 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 10 s= 2 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 20 s= 10 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 16 s= 16 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 126 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 0 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 38 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 10 s= 2 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 20 s= 10 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 16 s= 16 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 126 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+OTHER p= 0 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 38 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 10 s= 2 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 20 s= 10 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 16 s= 16 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 126 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 0 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 38 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 10 s= 2 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 20 s= 10 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 16 s= 16 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 126 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=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)
+
+[MYSQL]
+CHAR p= 0 s= 0 signed | DB=String(0,-1) VMB=String(0,-1) PRV=String(0,-1) NEW=String(0,-1)
+CHAR p= 38 s= 0 signed | DB=String(38,-1) VMB=String(38,-1) PRV=String(38,-1) NEW=String(38,-1)
+CHAR p= 10 s= 2 signed | DB=String(10,-1) VMB=String(10,-1) PRV=String(10,-1) NEW=String(10,-1)
+CHAR p= 20 s= 10 signed | DB=String(20,-1) VMB=String(20,-1) PRV=String(20,-1) NEW=String(20,-1)
+CHAR p= 16 s= 16 signed | DB=String(16,-1) VMB=String(16,-1) PRV=String(16,-1) NEW=String(16,-1)
+CHAR p= 126 s= 0 signed | DB=String(126,-1) VMB=String(126,-1) PRV=String(126,-1) NEW=String(126,-1)
+VARCHAR p= 0 s= 0 signed | DB=String(0,-1) VMB=String(0,-1) PRV=String(0,-1) NEW=String(0,-1)
+VARCHAR p= 38 s= 0 signed | DB=String(38,-1) VMB=String(38,-1) PRV=String(38,-1) NEW=String(38,-1)
+VARCHAR p= 10 s= 2 signed | DB=String(10,-1) VMB=String(10,-1) PRV=String(10,-1) NEW=String(10,-1)
+VARCHAR p= 20 s= 10 signed | DB=String(20,-1) VMB=String(20,-1) PRV=String(20,-1) NEW=String(20,-1)
+VARCHAR p= 16 s= 16 signed | DB=String(16,-1) VMB=String(16,-1) PRV=String(16,-1) NEW=String(16,-1)
+VARCHAR p= 126 s= 0 signed | DB=String(126,-1) VMB=String(126,-1) PRV=String(126,-1) NEW=String(126,-1)
+LONGVARCHAR p= 0 s= 0 signed | DB=String(0,-1) VMB=String(0,-1) PRV=String(0,-1) NEW=String(0,-1)
+LONGVARCHAR p= 38 s= 0 signed | DB=String(38,-1) VMB=String(38,-1) PRV=String(38,-1) NEW=String(38,-1)
+LONGVARCHAR p= 10 s= 2 signed | DB=String(10,-1) VMB=String(10,-1) PRV=String(10,-1) NEW=String(10,-1)
+LONGVARCHAR p= 20 s= 10 signed | DB=String(20,-1) VMB=String(20,-1) PRV=String(20,-1) NEW=String(20,-1)
+LONGVARCHAR p= 16 s= 16 signed | DB=String(16,-1) VMB=String(16,-1) PRV=String(16,-1) NEW=String(16,-1)
+LONGVARCHAR p= 126 s= 0 signed | DB=String(126,-1) VMB=String(126,-1) PRV=String(126,-1) NEW=String(126,-1)
+CLOB p= 0 s= 0 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 38 s= 0 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 10 s= 2 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 20 s= 10 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 16 s= 16 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 126 s= 0 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+BIGINT p= 0 s= 0 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 38 s= 0 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 10 s= 2 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 20 s= 10 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 16 s= 16 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 126 s= 0 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+INTEGER p= 0 s= 0 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 38 s= 0 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 10 s= 2 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 20 s= 10 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 16 s= 16 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 126 s= 0 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+SMALLINT p= 0 s= 0 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 38 s= 0 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 10 s= 2 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 20 s= 10 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 16 s= 16 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 126 s= 0 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+TINYINT p= 0 s= 0 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=Integer(2,0)
+TINYINT p= 38 s= 0 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=Integer(2,0)
+TINYINT p= 10 s= 2 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=Integer(2,0)
+TINYINT p= 20 s= 10 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=Integer(2,0)
+TINYINT p= 16 s= 16 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=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= 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= 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= 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= 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= 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)
+TIMESTAMP p= 0 s= 0 signed | DB=Timestamp(0,-1) VMB=Timestamp(0,-1) PRV=Timestamp(0,-1) NEW=Timestamp(0,-1)
+TIMESTAMP p= 38 s= 0 signed | DB=Timestamp(0,-1) VMB=Timestamp(0,-1) PRV=Timestamp(0,-1) NEW=Timestamp(0,-1)
+TIMESTAMP p= 10 s= 2 signed | DB=Timestamp(2,-1) VMB=Timestamp(2,-1) PRV=Timestamp(2,-1) NEW=Timestamp(2,-1)
+TIMESTAMP p= 20 s= 10 signed | DB=Timestamp(10,-1) VMB=Timestamp(10,-1) PRV=Timestamp(10,-1) NEW=Timestamp(10,-1)
+TIMESTAMP p= 16 s= 16 signed | DB=Timestamp(16,-1) VMB=Timestamp(16,-1) PRV=Timestamp(16,-1) NEW=Timestamp(16,-1)
+TIMESTAMP p= 126 s= 0 signed | DB=Timestamp(0,-1) VMB=Timestamp(0,-1) PRV=Timestamp(0,-1) NEW=Timestamp(0,-1)
+DATE p= 0 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 38 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 10 s= 2 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 20 s= 10 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 16 s= 16 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 126 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 0 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 38 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 10 s= 2 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 20 s= 10 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 16 s= 16 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 126 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+BOOLEAN p= 0 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 38 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 10 s= 2 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 20 s= 10 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 16 s= 16 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 126 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 0 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 38 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 10 s= 2 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 20 s= 10 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 16 s= 16 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 126 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BINARY p= 0 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 38 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 10 s= 2 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 20 s= 10 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 16 s= 16 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 126 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 0 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 38 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 10 s= 2 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 20 s= 10 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 16 s= 16 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 126 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 0 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 38 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 10 s= 2 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 20 s= 10 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 16 s= 16 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 126 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 0 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 38 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 10 s= 2 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 20 s= 10 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 16 s= 16 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 126 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+OTHER p= 0 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 38 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 10 s= 2 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 20 s= 10 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 16 s= 16 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 126 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 0 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 38 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 10 s= 2 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 20 s= 10 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 16 s= 16 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 126 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=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)
+
+[NETEZZA]
+CHAR p= 0 s= 0 signed | DB=String(0,-1) VMB=String(0,-1) PRV=String(0,-1) NEW=String(0,-1)
+CHAR p= 38 s= 0 signed | DB=String(38,-1) VMB=String(38,-1) PRV=String(38,-1) NEW=String(38,-1)
+CHAR p= 10 s= 2 signed | DB=String(10,-1) VMB=String(10,-1) PRV=String(10,-1) NEW=String(10,-1)
+CHAR p= 20 s= 10 signed | DB=String(20,-1) VMB=String(20,-1) PRV=String(20,-1) NEW=String(20,-1)
+CHAR p= 16 s= 16 signed | DB=String(16,-1) VMB=String(16,-1) PRV=String(16,-1) NEW=String(16,-1)
+CHAR p= 126 s= 0 signed | DB=String(126,-1) VMB=String(126,-1) PRV=String(126,-1) NEW=String(126,-1)
+VARCHAR p= 0 s= 0 signed | DB=String(0,-1) VMB=String(0,-1) PRV=String(0,-1) NEW=String(0,-1)
+VARCHAR p= 38 s= 0 signed | DB=String(38,-1) VMB=String(38,-1) PRV=String(38,-1) NEW=String(38,-1)
+VARCHAR p= 10 s= 2 signed | DB=String(10,-1) VMB=String(10,-1) PRV=String(10,-1) NEW=String(10,-1)
+VARCHAR p= 20 s= 10 signed | DB=String(20,-1) VMB=String(20,-1) PRV=String(20,-1) NEW=String(20,-1)
+VARCHAR p= 16 s= 16 signed | DB=String(16,-1) VMB=String(16,-1) PRV=String(16,-1) NEW=String(16,-1)
+VARCHAR p= 126 s= 0 signed | DB=String(126,-1) VMB=String(126,-1) PRV=String(126,-1) NEW=String(126,-1)
+LONGVARCHAR p= 0 s= 0 signed | DB=String(0,-1) VMB=String(0,-1) PRV=String(0,-1) NEW=String(0,-1)
+LONGVARCHAR p= 38 s= 0 signed | DB=String(38,-1) VMB=String(38,-1) PRV=String(38,-1) NEW=String(38,-1)
+LONGVARCHAR p= 10 s= 2 signed | DB=String(10,-1) VMB=String(10,-1) PRV=String(10,-1) NEW=String(10,-1)
+LONGVARCHAR p= 20 s= 10 signed | DB=String(20,-1) VMB=String(20,-1) PRV=String(20,-1) NEW=String(20,-1)
+LONGVARCHAR p= 16 s= 16 signed | DB=String(16,-1) VMB=String(16,-1) PRV=String(16,-1) NEW=String(16,-1)
+LONGVARCHAR p= 126 s= 0 signed | DB=String(126,-1) VMB=String(126,-1) PRV=String(126,-1) NEW=String(126,-1)
+CLOB p= 0 s= 0 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 38 s= 0 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 10 s= 2 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 20 s= 10 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 16 s= 16 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 126 s= 0 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+BIGINT p= 0 s= 0 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 38 s= 0 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 10 s= 2 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 20 s= 10 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 16 s= 16 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 126 s= 0 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+INTEGER p= 0 s= 0 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 38 s= 0 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 10 s= 2 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 20 s= 10 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 16 s= 16 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 126 s= 0 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+SMALLINT p= 0 s= 0 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 38 s= 0 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 10 s= 2 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 20 s= 10 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 16 s= 16 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 126 s= 0 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+TINYINT p= 0 s= 0 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=Integer(2,0)
+TINYINT p= 38 s= 0 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=Integer(2,0)
+TINYINT p= 10 s= 2 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=Integer(2,0)
+TINYINT p= 20 s= 10 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=Integer(2,0)
+TINYINT p= 16 s= 16 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=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= 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= 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= 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= 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= 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)
+TIMESTAMP p= 38 s= 0 signed | DB=Timestamp(0,-1) VMB=Timestamp(0,-1) PRV=Timestamp(0,-1) NEW=Timestamp(0,-1)
+TIMESTAMP p= 10 s= 2 signed | DB=Timestamp(2,-1) VMB=Timestamp(2,-1) PRV=Timestamp(2,-1) NEW=Timestamp(2,-1)
+TIMESTAMP p= 20 s= 10 signed | DB=Timestamp(10,-1) VMB=Timestamp(10,-1) PRV=Timestamp(10,-1) NEW=Timestamp(10,-1)
+TIMESTAMP p= 16 s= 16 signed | DB=Timestamp(16,-1) VMB=Timestamp(16,-1) PRV=Timestamp(16,-1) NEW=Timestamp(16,-1)
+TIMESTAMP p= 126 s= 0 signed | DB=Timestamp(0,-1) VMB=Timestamp(0,-1) PRV=Timestamp(0,-1) NEW=Timestamp(0,-1)
+DATE p= 0 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 38 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 10 s= 2 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 20 s= 10 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 16 s= 16 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 126 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 0 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 38 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 10 s= 2 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 20 s= 10 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 16 s= 16 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 126 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+BOOLEAN p= 0 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 38 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 10 s= 2 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 20 s= 10 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 16 s= 16 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 126 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 0 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 38 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 10 s= 2 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 20 s= 10 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 16 s= 16 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 126 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BINARY p= 0 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 38 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 10 s= 2 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 20 s= 10 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 16 s= 16 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 126 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 0 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 38 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 10 s= 2 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 20 s= 10 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 16 s= 16 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 126 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 0 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 38 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 10 s= 2 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 20 s= 10 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 16 s= 16 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 126 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 0 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 38 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 10 s= 2 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 20 s= 10 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 16 s= 16 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 126 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+OTHER p= 0 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 38 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 10 s= 2 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 20 s= 10 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 16 s= 16 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 126 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 0 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 38 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 10 s= 2 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 20 s= 10 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 16 s= 16 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 126 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=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)
+
+[DISPLAY_SIZE_TWICE_PRECISION]
+CHAR p= 0 s= 0 signed | DB=String(0,-1) VMB=String(0,-1) PRV=String(0,-1) NEW=String(0,-1)
+CHAR p= 38 s= 0 signed | DB=String(38,-1) VMB=String(38,-1) PRV=String(38,-1) NEW=String(38,-1)
+CHAR p= 10 s= 2 signed | DB=String(10,-1) VMB=String(10,-1) PRV=String(10,-1) NEW=String(10,-1)
+CHAR p= 20 s= 10 signed | DB=String(20,-1) VMB=String(20,-1) PRV=String(20,-1) NEW=String(20,-1)
+CHAR p= 16 s= 16 signed | DB=String(16,-1) VMB=String(16,-1) PRV=String(16,-1) NEW=String(16,-1)
+CHAR p= 126 s= 0 signed | DB=String(126,-1) VMB=String(126,-1) PRV=String(126,-1) NEW=String(126,-1)
+VARCHAR p= 0 s= 0 signed | DB=String(0,-1) VMB=String(0,-1) PRV=String(0,-1) NEW=String(0,-1)
+VARCHAR p= 38 s= 0 signed | DB=String(38,-1) VMB=String(38,-1) PRV=String(38,-1) NEW=String(38,-1)
+VARCHAR p= 10 s= 2 signed | DB=String(10,-1) VMB=String(10,-1) PRV=String(10,-1) NEW=String(10,-1)
+VARCHAR p= 20 s= 10 signed | DB=String(20,-1) VMB=String(20,-1) PRV=String(20,-1) NEW=String(20,-1)
+VARCHAR p= 16 s= 16 signed | DB=String(16,-1) VMB=String(16,-1) PRV=String(16,-1) NEW=String(16,-1)
+VARCHAR p= 126 s= 0 signed | DB=String(126,-1) VMB=String(126,-1) PRV=String(126,-1) NEW=String(126,-1)
+LONGVARCHAR p= 0 s= 0 signed | DB=String(0,-1) VMB=String(0,-1) PRV=String(0,-1) NEW=String(0,-1)
+LONGVARCHAR p= 38 s= 0 signed | DB=String(38,-1) VMB=String(38,-1) PRV=String(38,-1) NEW=String(38,-1)
+LONGVARCHAR p= 10 s= 2 signed | DB=String(10,-1) VMB=String(10,-1) PRV=String(10,-1) NEW=String(10,-1)
+LONGVARCHAR p= 20 s= 10 signed | DB=String(20,-1) VMB=String(20,-1) PRV=String(20,-1) NEW=String(20,-1)
+LONGVARCHAR p= 16 s= 16 signed | DB=String(16,-1) VMB=String(16,-1) PRV=String(16,-1) NEW=String(16,-1)
+LONGVARCHAR p= 126 s= 0 signed | DB=String(126,-1) VMB=String(126,-1) PRV=String(126,-1) NEW=String(126,-1)
+CLOB p= 0 s= 0 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 38 s= 0 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 10 s= 2 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 20 s= 10 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 16 s= 16 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 126 s= 0 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+BIGINT p= 0 s= 0 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 38 s= 0 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 10 s= 2 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 20 s= 10 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 16 s= 16 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 126 s= 0 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+INTEGER p= 0 s= 0 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 38 s= 0 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 10 s= 2 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 20 s= 10 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 16 s= 16 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 126 s= 0 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+SMALLINT p= 0 s= 0 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 38 s= 0 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 10 s= 2 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 20 s= 10 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 16 s= 16 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 126 s= 0 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+TINYINT p= 0 s= 0 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=Integer(2,0)
+TINYINT p= 38 s= 0 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=Integer(2,0)
+TINYINT p= 10 s= 2 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=Integer(2,0)
+TINYINT p= 20 s= 10 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=Integer(2,0)
+TINYINT p= 16 s= 16 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=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= 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= 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= 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= 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= 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)
+TIMESTAMP p= 38 s= 0 signed | DB=Timestamp(0,-1) VMB=Timestamp(0,-1) PRV=Timestamp(0,-1) NEW=Timestamp(0,-1)
+TIMESTAMP p= 10 s= 2 signed | DB=Timestamp(2,-1) VMB=Timestamp(2,-1) PRV=Timestamp(2,-1) NEW=Timestamp(2,-1)
+TIMESTAMP p= 20 s= 10 signed | DB=Timestamp(10,-1) VMB=Timestamp(10,-1) PRV=Timestamp(10,-1) NEW=Timestamp(10,-1)
+TIMESTAMP p= 16 s= 16 signed | DB=Timestamp(16,-1) VMB=Timestamp(16,-1) PRV=Timestamp(16,-1) NEW=Timestamp(16,-1)
+TIMESTAMP p= 126 s= 0 signed | DB=Timestamp(0,-1) VMB=Timestamp(0,-1) PRV=Timestamp(0,-1) NEW=Timestamp(0,-1)
+DATE p= 0 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 38 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 10 s= 2 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 20 s= 10 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 16 s= 16 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 126 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 0 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 38 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 10 s= 2 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 20 s= 10 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 16 s= 16 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 126 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+BOOLEAN p= 0 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 38 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 10 s= 2 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 20 s= 10 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 16 s= 16 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 126 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 0 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 38 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 10 s= 2 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 20 s= 10 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 16 s= 16 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 126 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BINARY p= 0 s= 0 signed | DB=Binary(0,0) VMB=Binary(0,0) PRV=Binary(0,0) NEW=Binary(0,0)
+BINARY p= 38 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 10 s= 2 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 20 s= 10 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 16 s= 16 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 126 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 0 s= 0 signed | DB=Binary(0,0) VMB=Binary(0,0) PRV=Binary(0,0) NEW=Binary(0,0)
+VARBINARY p= 38 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 10 s= 2 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 20 s= 10 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 16 s= 16 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 126 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 0 s= 0 signed | DB=Binary(0,0) VMB=Binary(0,0) PRV=Binary(0,0) NEW=Binary(0,0)
+LONGVARBINARY p= 38 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 10 s= 2 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 20 s= 10 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 16 s= 16 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 126 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 0 s= 0 signed | DB=Binary(0,0) VMB=Binary(0,0) PRV=Binary(0,0) NEW=Binary(0,0)
+BLOB p= 38 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 10 s= 2 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 20 s= 10 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 16 s= 16 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 126 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+OTHER p= 0 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 38 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 10 s= 2 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 20 s= 10 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 16 s= 16 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 126 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 0 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 38 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 10 s= 2 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 20 s= 10 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 16 s= 16 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 126 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=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)
+
+[NO_TIMESTAMP_SUPPORT]
+CHAR p= 0 s= 0 signed | DB=String(0,-1) VMB=String(0,-1) PRV=String(0,-1) NEW=String(0,-1)
+CHAR p= 38 s= 0 signed | DB=String(38,-1) VMB=String(38,-1) PRV=String(38,-1) NEW=String(38,-1)
+CHAR p= 10 s= 2 signed | DB=String(10,-1) VMB=String(10,-1) PRV=String(10,-1) NEW=String(10,-1)
+CHAR p= 20 s= 10 signed | DB=String(20,-1) VMB=String(20,-1) PRV=String(20,-1) NEW=String(20,-1)
+CHAR p= 16 s= 16 signed | DB=String(16,-1) VMB=String(16,-1) PRV=String(16,-1) NEW=String(16,-1)
+CHAR p= 126 s= 0 signed | DB=String(126,-1) VMB=String(126,-1) PRV=String(126,-1) NEW=String(126,-1)
+VARCHAR p= 0 s= 0 signed | DB=String(0,-1) VMB=String(0,-1) PRV=String(0,-1) NEW=String(0,-1)
+VARCHAR p= 38 s= 0 signed | DB=String(38,-1) VMB=String(38,-1) PRV=String(38,-1) NEW=String(38,-1)
+VARCHAR p= 10 s= 2 signed | DB=String(10,-1) VMB=String(10,-1) PRV=String(10,-1) NEW=String(10,-1)
+VARCHAR p= 20 s= 10 signed | DB=String(20,-1) VMB=String(20,-1) PRV=String(20,-1) NEW=String(20,-1)
+VARCHAR p= 16 s= 16 signed | DB=String(16,-1) VMB=String(16,-1) PRV=String(16,-1) NEW=String(16,-1)
+VARCHAR p= 126 s= 0 signed | DB=String(126,-1) VMB=String(126,-1) PRV=String(126,-1) NEW=String(126,-1)
+LONGVARCHAR p= 0 s= 0 signed | DB=String(0,-1) VMB=String(0,-1) PRV=String(0,-1) NEW=String(0,-1)
+LONGVARCHAR p= 38 s= 0 signed | DB=String(38,-1) VMB=String(38,-1) PRV=String(38,-1) NEW=String(38,-1)
+LONGVARCHAR p= 10 s= 2 signed | DB=String(10,-1) VMB=String(10,-1) PRV=String(10,-1) NEW=String(10,-1)
+LONGVARCHAR p= 20 s= 10 signed | DB=String(20,-1) VMB=String(20,-1) PRV=String(20,-1) NEW=String(20,-1)
+LONGVARCHAR p= 16 s= 16 signed | DB=String(16,-1) VMB=String(16,-1) PRV=String(16,-1) NEW=String(16,-1)
+LONGVARCHAR p= 126 s= 0 signed | DB=String(126,-1) VMB=String(126,-1) PRV=String(126,-1) NEW=String(126,-1)
+CLOB p= 0 s= 0 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 38 s= 0 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 10 s= 2 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 20 s= 10 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 16 s= 16 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+CLOB p= 126 s= 0 signed | DB=String(9999999,-1) VMB=String(9999999,-1) PRV=String(9999999,-1) NEW=String(9999999,-1)
+BIGINT p= 0 s= 0 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 38 s= 0 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 10 s= 2 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 20 s= 10 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 16 s= 16 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+BIGINT p= 126 s= 0 signed | DB=Integer(15,0) VMB=Integer(15,0) PRV=Integer(15,0) NEW=Integer(15,0)
+INTEGER p= 0 s= 0 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 38 s= 0 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 10 s= 2 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 20 s= 10 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 16 s= 16 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+INTEGER p= 126 s= 0 signed | DB=Integer(9,0) VMB=Integer(9,0) PRV=Integer(9,0) NEW=Integer(9,0)
+SMALLINT p= 0 s= 0 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 38 s= 0 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 10 s= 2 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 20 s= 10 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 16 s= 16 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+SMALLINT p= 126 s= 0 signed | DB=Integer(4,0) VMB=Integer(4,0) PRV=Integer(4,0) NEW=Integer(4,0)
+TINYINT p= 0 s= 0 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=Integer(2,0)
+TINYINT p= 38 s= 0 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=Integer(2,0)
+TINYINT p= 10 s= 2 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=Integer(2,0)
+TINYINT p= 20 s= 10 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=Integer(2,0)
+TINYINT p= 16 s= 16 signed | DB=Integer(2,0) VMB=Integer(2,0) PRV=Integer(2,0) NEW=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= 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= 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= 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= 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= 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
+TIMESTAMP p= 38 s= 0 signed | DB=Date(-1,-1) VMB=None(-1,-1) PRV=None(-1,-1) NEW=Date(-1,-1) DIVERGE
+TIMESTAMP p= 10 s= 2 signed | DB=Date(-1,-1) VMB=None(-1,-1) PRV=None(-1,-1) NEW=Date(-1,-1) DIVERGE
+TIMESTAMP p= 20 s= 10 signed | DB=Date(-1,-1) VMB=None(-1,-1) PRV=None(-1,-1) NEW=Date(-1,-1) DIVERGE
+TIMESTAMP p= 16 s= 16 signed | DB=Date(-1,-1) VMB=None(-1,-1) PRV=None(-1,-1) NEW=Date(-1,-1) DIVERGE
+TIMESTAMP p= 126 s= 0 signed | DB=Date(-1,-1) VMB=None(-1,-1) PRV=None(-1,-1) NEW=Date(-1,-1) DIVERGE
+DATE p= 0 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 38 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 10 s= 2 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 20 s= 10 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 16 s= 16 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+DATE p= 126 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 0 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 38 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 10 s= 2 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 20 s= 10 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 16 s= 16 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+TIME p= 126 s= 0 signed | DB=Date(-1,-1) VMB=Date(-1,-1) PRV=Date(-1,-1) NEW=Date(-1,-1)
+BOOLEAN p= 0 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 38 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 10 s= 2 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 20 s= 10 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 16 s= 16 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BOOLEAN p= 126 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 0 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 38 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 10 s= 2 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 20 s= 10 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 16 s= 16 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BIT p= 126 s= 0 signed | DB=Boolean(-1,-1) VMB=Boolean(-1,-1) PRV=Boolean(-1,-1) NEW=Boolean(-1,-1)
+BINARY p= 0 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 38 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 10 s= 2 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 20 s= 10 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 16 s= 16 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BINARY p= 126 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 0 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 38 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 10 s= 2 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 20 s= 10 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 16 s= 16 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+VARBINARY p= 126 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 0 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 38 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 10 s= 2 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 20 s= 10 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 16 s= 16 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+LONGVARBINARY p= 126 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 0 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 38 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 10 s= 2 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 20 s= 10 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 16 s= 16 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+BLOB p= 126 s= 0 signed | DB=Binary(-1,0) VMB=Binary(-1,0) PRV=Binary(-1,0) NEW=Binary(-1,0)
+OTHER p= 0 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 38 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 10 s= 2 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 20 s= 10 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 16 s= 16 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+OTHER p= 126 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 0 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 38 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 10 s= 2 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 20 s= 10 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 16 s= 16 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=String(-1,-1)
+STRUCT p= 126 s= 0 signed | DB=String(-1,-1) VMB=String(-1,-1) PRV=String(-1,-1) NEW=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
diff --git a/docs/hop-dev-manual/modules/ROOT/nav.adoc b/docs/hop-dev-manual/modules/ROOT/nav.adoc
index 9dc7cd1497..2e4a80d785 100644
--- a/docs/hop-dev-manual/modules/ROOT/nav.adoc
+++ b/docs/hop-dev-manual/modules/ROOT/nav.adoc
@@ -25,6 +25,14 @@ under the License.
* xref:integration-testing.adoc[Integration testing]
* xref:internationalisation.adoc[Internationalisation (i18n)]
* xref:plugin-development.adoc[Plugins Development]
+* xref:database/index.adoc[Database plugins]
+** xref:database/creating-a-dialect.adoc[Creating a dialect]
+** xref:database/column-types.adoc[Column types and type rules]
+** xref:database/value-bindings.adoc[Moving values across JDBC]
+** xref:database/extending-another-dialect.adoc[Extending a dialect you don't own]
+** xref:database/testing.adoc[Testing a database plugin]
+** xref:database/migrating-from-variants.adoc[Migrating from isXVariant()]
+* xref:value-types.adoc[Value types]
* xref:gui-plugins-toolbars.adoc[GUI plugins and toolbars]
* xref:marketplace-publish-plugin.adoc[Publishing a plugin to Nexus (marketplace)]
* xref:engine-compatibility.adoc[Engine compatibility]
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
new file mode 100644
index 0000000000..186a596713
--- /dev/null
+++ b/docs/hop-dev-manual/modules/ROOT/pages/database/column-types.adoc
@@ -0,0 +1,197 @@
+////
+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.
+////
+:description: How a database column becomes a Hop value and back again, and how a dialect declares the parts it does differently.
+[[Database-ColumnTypes]]
+= Column types and type rules
+
+Hop has its own small set of data types, and every database has a larger and different set.
+Something has to decide that a `NUMBER(38,0)` read from Oracle is a Hop Integer, and that a Hop Integer written to PostgreSQL is a `BIGINT`.
+
+That decision is made by the dialect, not by Hop.
+Hop provides the plain JDBC behaviour; a dialect declares the parts it does differently, as rules.
+
+== The Hop types
+
+[cols="1,1,3"]
+|===
+|Constant |Java type |Notes
+
+|`TYPE_STRING` |String |
+|`TYPE_INTEGER` |Long |Up to 18 significant digits.
+|`TYPE_NUMBER` |Double |
+|`TYPE_BIGNUMBER` |BigDecimal |For anything a double cannot hold exactly.
+|`TYPE_DATE` |java.util.Date |A precision of 1 marks "a date, not a timestamp".
+|`TYPE_TIMESTAMP` |java.sql.Timestamp |
+|`TYPE_BOOLEAN` |Boolean |
+|`TYPE_BINARY` |byte[] |
+|`TYPE_INET` |InetAddress |
+|`TYPE_JSON` |JsonNode |
+|`TYPE_UUID` |UUID |From the UUID value type plugin.
+|===
+
+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.
+
+`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.
+
+== A rule
+
+A rule is one `IDatabaseTypeRule`.
+It can answer three questions, and returns `null` from any it does not know, so a rule only has to describe what it actually does.
+
+[source,java]
+----
+IValueMeta getValueMeta(IVariables variables, DatabaseMeta databaseMeta, DatabaseColumn column);
+String getColumnType(IVariables variables, IDatabase database, IValueMeta valueMeta, ColumnContext context);
+IValueBinding getBinding(IDatabase database, IValueMeta valueMeta);
+----
+
+Reading a column::
+`getValueMeta` claims a column and says what Hop value it is.
+
+Writing a column definition::
+`getColumnType` gives the column type for a Hop value, without the column name. Hop adds the name and any line break.
+It is handed the dialect rather than the `DatabaseMeta` wrapper, because a dialect assembling an `ALTER TABLE` reaches this path from inside itself, where there is no wrapper. A connection option a type decision depends on lives on the dialect instance anyway, which is the same object `getTypeRules()` is answered from.
+
+Moving the values::
+`getBinding` is only for drivers that need the values themselves handled specially, and has its own page: xref:database/value-bindings.adoc[Moving values across JDBC].
+
+== Declaring rules
+
+A dialect returns its rules from `getTypeRules()`, built as a table rather than written as a switch.
+
+[source,java]
+----
+private static final List<IDatabaseTypeRule> TYPE_RULES =
+ DatabaseTypes.rules()
+ // A YEAR column is reported as a date, but is really a four digit integer when the
+ // driver has been told not to treat it as one.
+ .read(Types.DATE, Types.TIME)
+ .nativeName("YEAR")
+ .where(
+ (variables, databaseMeta, column) -> {
+ String property =
+ databaseMeta.getConnectionProperties(variables).getProperty("yearIsDateType");
+ return property != null && "false".equalsIgnoreCase(property);
+ })
+ .as(IValueMeta.TYPE_INTEGER, 4, 0)
+ // Postgres advises new applications to use JSONB rather than JSON.
+ .write(IValueMeta.TYPE_JSON)
+ .as("JSONB")
+ .build();
+
+@Override
+public List<IDatabaseTypeRule> getTypeRules() {
+ return TYPE_RULES;
+}
+----
+
+Rules are matched in declaration order and the first match wins.
+
+=== Matching a column to read
+
+`read(int... sqlTypes)`::
+Match on `java.sql.Types` constants.
+
+`readNative(String... names)`::
+Match on the database's own type name, ignoring case, such as `SDO_GEOMETRY` or `jsonb`.
+
+`readNativeMatching(String regex)`::
+Match the type name against a regular expression, for databases whose type names carry a size or an affinity.
+
+`nativeName(String...)`::
+Chains onto `read(...)` when both the JDBC type *and* the database's name for it have to match.
+
+`where(...)`::
+Narrows the match.
+The simple form takes the column; the fuller form also gets the variables and the `DatabaseMeta`, for rules that depend on a connection property or on another capability of the dialect.
+
+`as(...)`::
+Terminal.
+Either a Hop type on its own, a Hop type with a fixed length and precision, or a Hop type whose length and precision are derived from the column.
+
+=== Producing a column definition to write
+
+`write(int... hopTypes)`::
+The Hop types this rule spells.
+
+`where(Predicate<IValueMeta>)`::
+Narrows on the value, usually its length.
+
+`as(String)` or `as(Function<IValueMeta, String>)`::
+Terminal.
+Either a fixed type name, or one derived from the value for size dependent types.
+
+=== When the table is not enough
+
+`rule(IDatabaseTypeRule)` takes a hand written rule, and `include(List<IDatabaseTypeRule>)` appends another set, for a dialect that shares behaviour with one it does not extend.
+
+== Resolution order
+
+Reading a column, first match wins:
+
+. rules contributed by a `@DatabaseTypeRulesPlugin` targeting this dialect
+. the dialect's own `getTypeRules()`
+. the value type plugins
+. the standard JDBC mapping
+
+Writing a column definition:
+
+. the dialect's rules
+. the value type's own opinion
+. the dialect's `getFieldDefinition()`
+. the standard mapping
+
+Two things follow from that order.
+A dialect overrides the standard behaviour simply by declaring a rule, and a plugin from outside can override a dialect, which is deliberate.
+
+The write order also means a value type gives the *neutral* spelling and knows about no database at all.
+`ValueMetaJson` says `JSON`; PostgreSQL says `JSONB` in its own rules.
+
+== Rules are inherited
+
+A dialect that extends another inherits its rules in the ordinary Java way.
+`RedshiftDatabaseMeta extends PostgreSqlDatabaseMeta`, so Redshift reads columns the way Postgres does without restating anything, and can prepend its own rules by overriding `getTypeRules()`.
+
+This is also how rules contributed from outside find their target: a rule written for `POSTGRESQL` applies to every dialect that extends the PostgreSQL one.
+
+== Where to put a rule
+
+Put a rule in the dialect that needs it.
+
+The exception is a behaviour genuinely shared by dialects that do not extend one another.
+`ColumnTypeRules` in core holds those, named after what they do rather than after a vendor, because Generic, Hive and SingleStore all speak to a MySQL driver without extending `MySqlDatabaseMeta`.
+That list is short on purpose.
+If only one dialect needs a rule, it belongs in that dialect's plugin.
+
+== The standard mapping
+
+What survives when no rule claims a column is `StandardJdbcTypeMapper`, which does the plain JDBC thing and names no database.
+
+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
+* `numericScale(column)` — digits after the decimal
+* `displaySizeIsTwiceThePrecision(databaseMeta, column)` — the `CHAR(X) FOR BIT DATA` shape
+
+That last one matters if you write a rule for binary columns.
+The check outranks every dialect rule, but rules run *before* the standard mapping, so a binary rule has to defer to it explicitly or the precedence silently inverts.
diff --git a/docs/hop-dev-manual/modules/ROOT/pages/database/creating-a-dialect.adoc b/docs/hop-dev-manual/modules/ROOT/pages/database/creating-a-dialect.adoc
new file mode 100644
index 0000000000..3a8c4b1223
--- /dev/null
+++ b/docs/hop-dev-manual/modules/ROOT/pages/database/creating-a-dialect.adoc
@@ -0,0 +1,150 @@
+////
+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.
+////
+:description: How to add support for a database Hop does not know yet, by writing a dialect.
+[[Database-CreatingADialect]]
+= Creating a dialect
+
+A dialect tells Hop how one database differs from plain JDBC.
+It is a class implementing `IDatabase`, which in practice means extending `BaseDatabaseMeta` and overriding only what is actually different.
+
+== The smallest possible dialect
+
+[source,java]
+----
+@DatabaseMetaPlugin(
+ type = "ACME",
+ typeDescription = "ACME Database",
+ classLoaderGroup = "acme-db",
+ documentationUrl = "/database/databases/acme.html")
+@GuiPlugin(id = "GUI-AcmeDatabaseMeta")
+public class AcmeDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
+
+ @Override
+ public String getDriverClass() {
+ return "com.acme.jdbc.Driver";
+ }
+
+ @Override
+ public String getURL(String hostname, String port, String databaseName) {
+ return "jdbc:acme://" + hostname + ":" + port + "/" + databaseName;
+ }
+
+ @Override
+ public int getDefaultDatabasePort() {
+ return 5555;
+ }
+}
+----
+
+Everything else has a default in `BaseDatabaseMeta`.
+Start there and override as the differences show up, rather than working through the whole interface.
+
+=== The annotation
+
+`type`::
+The identifier for this dialect, uppercase by convention.
+It is stored in saved connections, so changing it later breaks existing projects.
+It is also how other plugins refer to this dialect without compiling against it, so pick something stable and obvious.
+
+`typeDescription`::
+What the user sees in the connection dialog.
+
+`classLoaderGroup`::
+Give every dialect one, named after the module, and give the *same* group to every other plugin class in the module.
+See <<Classloading>>.
+
+`documentationUrl`::
+Path to the page in the user manual, which the connection dialog links to.
+
+== Capabilities
+
+Most of `IDatabase` is questions about what the database supports.
+They are ordinary methods with sensible defaults, and the ones that matter most in practice are these.
+
+[cols="1,3"]
+|===
+|Method |What it controls
+
+|`isSupportsBooleanDataType()`
+|Whether a Hop boolean can be written as `BOOLEAN`, or has to become a one character column.
+
+|`isSupportsTimestampDataType()`
+|Whether timestamps survive as timestamps, or collapse to dates.
+
+|`isSupportsAutoInc()`
+|Whether the database can generate technical keys itself.
+
+|`isFetchSizeSupported()`
+|Whether the JDBC fetch size can be set at all.
+
+|`isSupportsErrorHandling()`
+|Set this false for databases that invalidate a statement, or the whole connection, when a statement fails. SQLite is the example in the tree.
+
+|`getLimitClause(int)`
+|The clause appended *after* the FROM clause to limit rows, such as `" LIMIT 10"`. It is a suffix; databases that limit rows with a prefix such as `TOP` cannot express it here.
+|===
+
+Prefer overriding a capability over overriding behaviour.
+A capability is a fact about the database that Hop can act on in several places; overridden behaviour only fixes the one place you overrode.
+
+== SQL differences
+
+Where the SQL itself differs, override the statement the difference belongs to: `getTruncateTableStatement`, `getAddColumnStatement`, `getModifyColumnStatement`, `getSqlListOfSchemas`, and so on.
+
+Column types are not done this way.
+They have their own mechanism, described in xref:database/column-types.adoc[Column types and type rules], and a new dialect should use that rather than overriding `getFieldDefinition`.
+
+When one of those statements has to name a column type, call `getColumnDefinition(v, tk, pk, useAutoIncrement, addFieldName, addCr, purpose)` rather than `getFieldDefinition(...)`.
+It asks the type rules first, so a column is spelled in an `ALTER TABLE` exactly the way it is in the `CREATE TABLE`; calling `getFieldDefinition` directly skips the rules and the two drift apart.
+Pass `ColumnContext.Purpose.ADD_COLUMN` or `MODIFY_COLUMN` to say which statement is being built.
+
+`addFieldName` means only whether the returned definition is prefixed with the column name.
+A dialect whose `ALTER` syntax puts the name somewhere else writes that name into the statement itself and asks for the definition with `addFieldName` false, as Firebird, Interbase and SAPDB do:
+
+[source,java]
+----
+return "ALTER TABLE " + tableName + " ALTER COLUMN " + v.getName() + " TYPE "
+ + getColumnDefinition(v, tk, pk, useAutoinc, false, false, ColumnContext.Purpose.MODIFY_COLUMN);
+----
+
+[[Classloading]]
+== Classloading and the JDBC driver
+
+Hop loads each plugin in its own classloader so that two plugins can use incompatible versions of the same library.
+Plugins that declare the same `classLoaderGroup` share one classloader instead.
+
+This matters for two reasons.
+
+Within your own module, the dialect loads the JDBC driver, and any bulk loader you ship needs the same driver classes.
+If the dialect declares a group and the bulk loader does not, they end up in different classloaders and the bulk loader cannot see the driver.
+Give every plugin class in the module the same group.
+
+Across modules, a plugin from somewhere else can join your group by naming it, which is how somebody can add support for a database specific type without a change to your plugin.
+Choose a group name that reads as public API, because it is.
+
+== Registering the driver
+
+Hop does not bundle most JDBC drivers.
+Declare the dependency as `provided` in the module's `pom.xml`, so the plugin builds against it without shipping it, and document which jar the user has to drop into the plugin folder.
+
+== Checklist
+
+* The dialect extends `BaseDatabaseMeta` and carries `@DatabaseMetaPlugin`.
+* Every plugin class in the module carries the same `classLoaderGroup`.
+* Column types are declared as rules, not as an overridden `getFieldDefinition`.
+* There is a golden test for the generated DDL, see xref:database/testing.adoc[Testing a database plugin].
+* There is a page in the user manual, and `documentationUrl` points at it.
diff --git a/docs/hop-dev-manual/modules/ROOT/pages/database/extending-another-dialect.adoc b/docs/hop-dev-manual/modules/ROOT/pages/database/extending-another-dialect.adoc
new file mode 100644
index 0000000000..0ff33ad4cc
--- /dev/null
+++ b/docs/hop-dev-manual/modules/ROOT/pages/database/extending-another-dialect.adoc
@@ -0,0 +1,121 @@
+////
+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.
+////
+:description: How a plugin can add support for a database specific type without any change to Hop or to the database plugin it extends.
+[[Database-ExtendingAnotherDialect]]
+= Extending a dialect you don't own
+
+Some types belong to one database and are worth real support: Oracle's `SDO_GEOMETRY`, PostGIS geometry, a vendor's vector or interval type.
+
+You can add these from your own plugin.
+Neither Hop nor the database plugin you are extending has to change.
+
+== The pieces
+
+Supporting a database specific type usually needs three things, and they do not all live in the same place.
+
+The Hop type::
+A value type plugin, so the value can travel between transforms, be compared, and be serialised.
+This part is not database specific.
+See xref:value-types.adoc[Value types].
+
+The mapping::
+A rule saying that this database's column of that name is this Hop type, and how it is spelled when writing.
+This is database specific and is what this page is about.
+
+The binding::
+How the value crosses JDBC, if the driver needs anything unusual.
+See xref:database/value-bindings.adoc[Moving values across JDBC].
+
+== Contributing rules
+
+[source,java]
+----
+@DatabaseTypeRulesPlugin(
+ id = "acme-oracle-geometry",
+ dialects = {"ORACLE"},
+ valueTypes = {"Geometry"},
+ classLoaderGroup = "oracle-db")
+public class OracleGeometryTypeRules implements IDatabaseTypeRuleProvider {
+
+ @Override
+ public List<IDatabaseTypeRule> getTypeRules() {
+ return DatabaseTypes.rules()
+ .readNative("SDO_GEOMETRY").as(GEOMETRY)
+ .write(GEOMETRY).as("SDO_GEOMETRY")
+ .bind(GEOMETRY, new OracleGeometryBinding())
+ .build();
+ }
+}
+----
+
+`dialects`::
+Which dialects these rules apply to, named by the `type` of their `@DatabaseMetaPlugin` — `ORACLE`, `POSTGRESQL`, and so on.
+They are named as strings on purpose: you must not have to compile against the plugin you are extending.
+Leaving this empty applies the rules to every dialect.
+
+`valueTypes`::
+Value type names these rules need.
+If one is not installed the whole plugin is skipped silently, rather than failing, so you can ship optional support for a type the user may not have.
+
+`classLoaderGroup`::
+Join the dialect's group when your binding touches the driver's classes.
+See <<DriverClasses>>.
+
+== Targeting follows the class hierarchy
+
+A rule written for `POSTGRESQL` also applies to Redshift, Greenplum, CrateDB and CockroachDB, because their dialects extend the PostgreSQL one.
+
+Matching walks the dialect's superclasses and collects each `@DatabaseMetaPlugin` type it finds, so Redshift answers to `REDSHIFT` and to `POSTGRESQL`.
+You target the family by naming the one they extend.
+
+== Contributed rules are consulted first
+
+The order is: contributed rules, then the dialect's own, then the value types, then the standard JDBC mapping.
+
+So a plugin can add a type a dialect never had, and can also *correct* one a dialect gets wrong, without patching it.
+That second one is a sharp tool.
+If you use it, say so loudly in your plugin's documentation, because the behaviour a user gets will no longer match Hop's own.
+
+[[DriverClasses]]
+== Reaching the driver's classes
+
+This is the part that catches people out.
+
+Oracle hands back an `SDO_GEOMETRY` as an `oracle.sql.STRUCT`.
+That class is loaded by the classloader that loaded the Oracle driver, which is the Oracle plugin's.
+Your plugin has its own, so unwrapping the object from there fails with a message that looks impossible:
+
+----
+class oracle.sql.STRUCT cannot be cast to class oracle.sql.STRUCT
+----
+
+Two different classloaders, two different classes, same name.
+
+The fix is to join the group, by declaring the same `classLoaderGroup` as the plugin whose driver you need.
+Hop's database plugins name their group after the module: `oracle-db`, `postgres-db`, `mysql-db`, and so on.
+
+If you can avoid touching driver classes at all, do.
+A rule that claims a column by its type name and reads it with `getBytes` or `getString` needs no group and no coordination with anybody.
+PostGIS geometry arrives as well known binary and can be handled that way.
+
+== A checklist for a type plugin
+
+* The value type is a separate plugin, so the type is usable whatever the database.
+* The rules name their dialects by plugin type string, not by class.
+* `valueTypes` lists what you depend on, so an incomplete install degrades quietly.
+* You only join a `classLoaderGroup` if you genuinely need the driver's classes.
+* Values exchanged with the rest of Hop are driver neutral.
diff --git a/docs/hop-dev-manual/modules/ROOT/pages/database/index.adoc b/docs/hop-dev-manual/modules/ROOT/pages/database/index.adoc
new file mode 100644
index 0000000000..0e337d8b77
--- /dev/null
+++ b/docs/hop-dev-manual/modules/ROOT/pages/database/index.adoc
@@ -0,0 +1,74 @@
+////
+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.
+////
+:description: A database plugin teaches Hop to speak to one database. This is what such a plugin is made of and how the pieces fit together.
+[[Database-Index]]
+= Database plugins
+
+A database plugin teaches Hop to speak to one database.
+Hop ships around fifty of them, and writing another is one of the more common reasons to extend Hop.
+
+This section is about writing one.
+For connecting to a database that already has a plugin, see the xref:hop-user-manual:database/databases/index.adoc[database pages in the user manual] instead.
+
+== What a database plugin is made of
+
+A database plugin is rarely a single class.
+In Hop's own plugins you will usually find some combination of these.
+
+The dialect::
+An implementation of `IDatabase`, almost always by extending `BaseDatabaseMeta`, carrying the `@DatabaseMetaPlugin` annotation.
+It answers everything Hop needs to know about the database: how to build a URL, which driver class to load, what its SQL looks like, and how its column types map onto Hop's.
+This is the only mandatory part.
+See xref:database/creating-a-dialect.adoc[Creating a dialect].
+
+The JDBC driver::
+Hop does not ship most drivers, for licensing reasons.
+The plugin declares the dependency and the driver jar is dropped into the plugin's folder by whoever installs it.
+
+Column type rules::
+How a column of this database becomes a Hop value, and how a Hop value is written back as a column definition.
+See xref:database/column-types.adoc[Column types and type rules].
+
+Value bindings::
+Only needed when a driver has a quirk in how it hands over the values themselves, rather than in how the types are named.
+See xref:database/value-bindings.adoc[Moving values across JDBC].
+
+Transforms and actions::
+Many database plugins also ship a bulk loader, because loading a million rows through JDBC is rarely the fastest route.
+These are ordinary transform plugins that happen to live in the same module.
+
+== How the pieces find each other
+
+Everything is discovered by annotation.
+`@DatabaseMetaPlugin` on the dialect makes it appear in the connection dialog; `@Transform` on a bulk loader makes it appear in the transform list.
+
+The one piece of wiring worth understanding early is `classLoaderGroup`.
+Plugins in the same group share one classloader, which is how a bulk loader in the same module can use classes from the JDBC driver the dialect loaded.
+Every class in a database plugin should carry the same group, or one of them ends up in a classloader of its own and cannot see the driver.
+
+That mechanism is also what lets a plugin from somewhere else reach a driver's classes, which is what makes it possible to support a database specific type without changing Hop.
+See xref:database/extending-another-dialect.adoc[Extending a dialect you don't own].
+
+== Where to start
+
+If you are adding a database Hop does not support yet, read xref:database/creating-a-dialect.adoc[Creating a dialect] first, then xref:database/column-types.adoc[Column types and type rules].
+
+If you are adding a type to a database Hop already supports, go straight to xref:database/extending-another-dialect.adoc[Extending a dialect you don't own].
+
+If you maintain a plugin written before the type rules existed, see xref:database/migrating-from-variants.adoc[Migrating from isXVariant()].
+
+Whatever you are doing, read xref:database/testing.adoc[Testing a database plugin] before you change anything that generates SQL.
diff --git a/docs/hop-dev-manual/modules/ROOT/pages/database/migrating-from-variants.adoc b/docs/hop-dev-manual/modules/ROOT/pages/database/migrating-from-variants.adoc
new file mode 100644
index 0000000000..95b2001488
--- /dev/null
+++ b/docs/hop-dev-manual/modules/ROOT/pages/database/migrating-from-variants.adoc
@@ -0,0 +1,87 @@
+////
+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.
+////
+:description: The isXVariant() methods are deprecated. This is what replaced them and how to move a plugin across.
+[[Database-MigratingFromVariants]]
+= Migrating from isXVariant()
+
+`IDatabase` used to carry fourteen methods of the form `isMySqlVariant()`, `isPostgresVariant()`, `isOracleVariant()`.
+They are deprecated.
+This page is for anyone maintaining a database plugin written against them.
+
+== What they were for
+
+Core asked the connection which vendor it resembled, and switched on the answer:
+
+[source,java]
+----
+if (databaseMeta.getIDatabase().isOracleVariant()) {
+ // read this column the way Oracle does
+}
+----
+
+The problem was not the style, it was the direction.
+The knowledge lived in Hop, so a dialect could not describe itself, only be described.
+A database plugin Hop did not ship could never be fully correct however carefully it was written, because the list of vendors was compiled into core.
+
+== What replaced them
+
+A dialect declares what it does, through xref:database/column-types.adoc[type rules]:
+
+[source,java]
+----
+@Override
+public List<IDatabaseTypeRule> getTypeRules() {
+ return DatabaseTypes.rules()
+ .read(Types.VARBINARY, Types.LONGVARBINARY)
+ .as(IValueMeta.TYPE_STRING, DatabaseColumn::getDisplaySize, column -> -1)
+ .build();
+}
+----
+
+Where the flags asked "are you like Oracle?", inheritance now answers structurally.
+A dialect extending another inherits its rules, so Redshift reads columns the way PostgreSQL does without anybody maintaining a list of PostgreSQL-like databases.
+
+== Nothing breaks while you migrate
+
+The flags still work.
+A dialect that answers one is handed the rules that flag asks for, so an unmigrated plugin behaves exactly as it did.
+
+The bridge is consulted *after* a dialect's own rules, so migrating is an override rather than a conflict: you can declare your rules and keep answering the flag through the deprecation period.
+
+One limit is worth knowing.
+For every flag except `isMySqlVariant`, the rules are fetched from the dialect the flag names, so that dialect's plugin has to be installed.
+When it is not, Hop logs that it could not apply them rather than failing quietly.
+`isMySqlVariant` is resolved directly and has no such dependency, because Generic, Hive and SingleStore all claim it without extending the MySQL dialect.
+
+== Moving a plugin across
+
+. Work out what your dialect actually did differently. Anything guarded by a variant flag in core, plus anything you overrode in `getFieldDefinition`.
+. Write those as rules and return them from `getTypeRules()`.
+. Add a golden test if the plugin does not have one, *before* changing anything, so you can prove the move changed nothing. See xref:database/testing.adoc[Testing a database plugin].
+. Stop answering the flag.
+
+If your dialect claims to be like another database without extending it, either extend it, or declare the rules you need.
+Reusable rules shared by more than one dialect live in `ColumnTypeRules`.
+
+== Related deprecations
+
+`isStrictBigNumberInterpretation()` has gone the same way.
+It was an Oracle option sitting on the interface every dialect implements; Oracle now reads it from its own connection when it builds its rules.
+
+`IValueMeta.getValueFromSqlType()` and `IValueMeta.getMetadataPreview()` are also deprecated.
+They were older copies of the column mapping that had drifted from the one the engine used.
+Nothing should call them; `StandardJdbcTypeMapper` is the single implementation.
diff --git a/docs/hop-dev-manual/modules/ROOT/pages/database/testing.adoc b/docs/hop-dev-manual/modules/ROOT/pages/database/testing.adoc
new file mode 100644
index 0000000000..7520fd321e
--- /dev/null
+++ b/docs/hop-dev-manual/modules/ROOT/pages/database/testing.adoc
@@ -0,0 +1,113 @@
+////
+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.
+////
+:description: How database plugins are tested in Hop, and what a golden file diff means when you see one in review.
+[[Database-Testing]]
+= Testing a database plugin
+
+Most of what a dialect does is turn one thing into another: a JDBC column into a Hop value, a Hop value into a piece of SQL.
+That is straightforward to test without a database, and Hop does.
+
+== Golden files for generated DDL
+
+Every dialect has a golden file recording the column definition it generates for each Hop type at each size.
+They live next to the plugin's other tests and are generated, not written.
+
+[source,java]
+----
+class AcmeFieldDefinitionGoldenTest extends BaseFieldDefinitionGoldenTest {
+ @Override
+ protected IDatabase createDatabase() {
+ return new AcmeDatabaseMeta();
+ }
+}
+----
+
+That is the whole test.
+The base class walks every Hop type across a range of lengths and precisions, with and without keys and auto-increment, and compares the result against the recorded file.
+
+Generate or refresh it with:
+
+[source,bash]
+----
+mvn test -pl plugins/databases/acme -Dhop.golden.update=true
+----
+
+=== What a golden diff means
+
+A changed golden file is a change to the SQL Hop generates for a user's database.
+It is never noise.
+
+Read every changed line and be able to say why it changed.
+If you cannot, the change is not ready.
+Regenerating a golden to make a build go green is the one thing these files exist to prevent.
+
+Golden files also record behaviour that is *wrong* but current.
+Freezing a known bug is deliberate: it means the fix shows up later as a small reviewable diff instead of being lost in a refactor.
+A comment in the test explains which ones are which.
+
+== Testing type rules
+
+Rules are tested against the real dialect, using a fixture that builds the JDBC metadata for you.
+
+[source,java]
+----
+IValueMeta valueMeta =
+ DatabaseTypeMapper.getValueMeta(
+ new Variables(),
+ meta(new AcmeDatabaseMeta()),
+ column(Types.VARBINARY, "RAW", 8, 20),
+ false,
+ false);
+
+assertTrue(valueMeta.isString());
+----
+
+`TypeRuleFixture` provides `meta(...)`, `column(...)` and `numericColumn(...)`.
+Note the difference between the last two: `column(...)` takes a display size and leaves the scale at zero, while `numericColumn(...)` takes a scale.
+Numeric rules almost always want the second.
+
+Test the rule against the dialect, not against the rule object.
+Going through `DatabaseTypeMapper` exercises the resolution order too, which is where mistakes actually happen.
+
+== Testing value bindings
+
+Test through the engine, not through the value type.
+
+Hop asks for a binding in the dialect's `getValueFromResultSet` when reading, and in `Database.setValue` when writing.
+A test that calls `valueMeta.setPreparedStatementValue(...)` directly bypasses the point where the binding is chosen, and will pass whether or not your binding works.
+
+[source,java]
+----
+database.setValue(preparedStatement, valueMeta, value, 1);
+verify(preparedStatement).setObject(eq(1), any(), eq(Types.OTHER));
+----
+
+== Integration tests
+
+`integration-tests/database` runs pipelines against real databases in Docker.
+Use these for what cannot be checked without a server: that the SQL actually executes, that types survive a round trip, that a bulk loader loads.
+
+Unit tests are the right place for everything that is a pure transformation, because they run in milliseconds and do not need a container.
+
+== A trap worth knowing
+
+`DatabaseMeta` forwards many questions to the dialect.
+`databaseMeta.supportsTimeStampToDateConversion()` returns `iDatabase.isSupportsTimeStampToDateConversion()`, and there are several pairs like it.
+
+If a test mocks one and not the other, the two disagree in a way they never can in production, and the test then asserts something that is not true of the running system.
+This has bitten Hop's own tests more than once.
+Stub the dialect, which is the source of truth, or use a real dialect instance.
diff --git a/docs/hop-dev-manual/modules/ROOT/pages/database/value-bindings.adoc b/docs/hop-dev-manual/modules/ROOT/pages/database/value-bindings.adoc
new file mode 100644
index 0000000000..9d084e5ebe
--- /dev/null
+++ b/docs/hop-dev-manual/modules/ROOT/pages/database/value-bindings.adoc
@@ -0,0 +1,109 @@
+////
+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.
+////
+:description: When a driver needs the values themselves handled specially, rather than the types named differently.
+[[Database-ValueBindings]]
+= Moving values across JDBC
+
+Most of the time, once Hop knows a column is a date, reading it is `getTimestamp` and writing it is `setTimestamp`.
+Some drivers are not that obliging.
+
+A binding is for those cases.
+It is not for naming types differently, which is what xref:database/column-types.adoc[type rules] are for.
+Reach for a binding only when the driver misbehaves over the value itself.
+
+The three in Hop's own plugins are a fair sample of what qualifies:
+
+* the Netezza driver returns nothing usable from `getDate` on a `TIME` column
+* the DuckDB driver has never implemented the `Calendar` overloads of `setDate` and `setTimestamp`
+* PostgreSQL takes JSON as a typed object, where other databases reject `Types.OTHER`
+
+== The interface
+
+[source,java]
+----
+public interface IValueBinding {
+ Object read(IDatabase database, IValueMeta valueMeta, ResultSet resultSet, int index)
+ throws SQLException;
+
+ void write(IDatabase database, IValueMeta valueMeta, PreparedStatement preparedStatement,
+ int index, Object value) throws SQLException, HopValueException;
+}
+----
+
+The index is the JDBC one, counting from 1.
+
+A binding that only ever writes can throw `UnsupportedOperationException` from `read`, and the other way round.
+Hop only calls the direction the rule was declared for.
+
+== Declaring one
+
+[source,java]
+----
+DatabaseTypes.rules()
+ .bind(IValueMeta.TYPE_DATE, MY_DATE_BINDING)
+ .build();
+----
+
+Or, when it should only apply in some cases:
+
+[source,java]
+----
+DatabaseTypes.rules()
+ .bind(
+ IValueMeta.TYPE_DATE,
+ (database, valueMeta) ->
+ valueMeta.getPrecision() == 1 || !database.isSupportsTimeStampToDateConversion(),
+ NETEZZA_DATE_BINDING)
+ .build();
+----
+
+== Choosing happens before the work
+
+A binding is chosen from the dialect and the value metadata, never from column metadata.
+By the time rows are moving there is no column metadata left, only the value meta the row carries.
+
+So everything the choice depends on is answered when the binding is *chosen*: a driver quirk, a connection capability, the value's own precision.
+The binding itself only does the work.
+That is why the Netezza example above tests `isSupportsTimeStampToDateConversion` in the condition rather than inside `read`.
+
+== Bindings and the classloader
+
+A binding is the one place in the type system that is likely to touch the JDBC driver's own classes, and driver classes only resolve in the classloader that loaded the driver.
+
+If the binding lives in the same plugin as the dialect, that is automatic.
+If it lives somewhere else, the plugin has to join the dialect's `classLoaderGroup`, or unwrapping a driver object fails with the confusing `class com.acme.Thing cannot be cast to class com.acme.Thing`.
+See xref:database/extending-another-dialect.adoc[Extending a dialect you don't own].
+
+Prefer exchanging driver neutral values — byte arrays, strings, numbers — so that whatever consumes the value needs no knowledge of any driver.
+
+== Where Hop asks
+
+Hop asks for a binding at two places, and only two:
+
+* reading, in the dialect's own `getValueFromResultSet`
+* writing, in `Database.setValue`
+
+Both are the points the engine goes through, which is deliberate.
+A value type that handles its own reading or writing still gets its database's binding, because the question is asked before the value type is reached.
+
+== Cost
+
+Binding lookup runs for every value of every row.
+The rules that can supply one are collected once per dialect and cached, and nearly every dialect has none, so the lookup is a map read and an empty list check.
+
+A rule that can supply a binding must say so by returning true from `suppliesBindings()`; the builder does this for you.
+A rule that never binds is skipped entirely.
diff --git a/docs/hop-dev-manual/modules/ROOT/pages/value-types.adoc b/docs/hop-dev-manual/modules/ROOT/pages/value-types.adoc
new file mode 100644
index 0000000000..8d662644e3
--- /dev/null
+++ b/docs/hop-dev-manual/modules/ROOT/pages/value-types.adoc
@@ -0,0 +1,89 @@
+////
+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.
+////
+:description: Hop's data types are a plugin type. This is how to add one.
+[[ValueTypes]]
+= Value types
+
+A value type is what Hop knows about one kind of data: how to compare it, convert it, serialise it across a hop, and write it to a file.
+The built-in set is small — string, integer, number, big number, date, timestamp, boolean, binary — and it is extensible.
+
+`UUID` and `JSON` are value types.
+A geometry type, a vector type or a money type would be too.
+
+== When you need one
+
+You need a value type when a value has to *travel*.
+
+A row crosses transforms, gets serialised, is compared and sorted, and may be written to a file that has nothing to do with a database.
+Anything that has to survive all of that is a value type.
+
+You do not need one to read an unusual column from a single database if the value can reasonably be carried as a string or as bytes.
+Reach for the simpler option first.
+
+== Writing one
+
+Extend `ValueMetaBase` and carry the annotation:
+
+[source,java]
+----
+@ValueMetaPlugin(
+ id = "1042",
+ name = "Geometry",
+ description = "Geospatial geometry")
+public class ValueMetaGeometry extends ValueMetaBase {
+
+ public static final int TYPE_GEOMETRY = 1042;
+
+ public ValueMetaGeometry() {
+ super(null, TYPE_GEOMETRY);
+ }
+
+ @Override
+ public Class<?> getNativeDataTypeClass() {
+ return Geometry.class;
+ }
+
+ @Override
+ public Object convertData(IValueMeta meta2, Object data2) throws HopValueException {
+ ...
+ }
+}
+----
+
+At minimum you will implement conversion to and from the types Hop already knows, a native class, and binary serialisation.
+`ValueMetaUuid` in `plugins/valuetypes/uuid` is a complete and fairly small example to read.
+
+== Type ids
+
+[WARNING]
+====
+Type ids are plain integers and there is no allocation registry.
+The built-in types use low numbers and the shipped plugins use `20` for Avro and `32` for UUID.
+
+Two plugins that pick the same id will collide, and the failure will not be obvious.
+Until Hop has a convention, pick something distinctive and high, and say in your documentation which id you took.
+====
+
+== Value types and databases
+
+Keep the two apart.
+
+A value type should give the *neutral* answer and know about no database at all.
+`ValueMetaJson` says the column type is `JSON`; that PostgreSQL prefers `JSONB` is declared by PostgreSQL, in its own rules.
+
+If your type needs different treatment on a particular database, that belongs in a xref:database/extending-another-dialect.adoc[type rules plugin] targeting that dialect, not in the value type.
+This keeps the type usable on databases you have never heard of, and keeps you out of the business of maintaining a list of vendors.
diff --git a/plugins/databases/access/src/main/java/org/apache/hop/databases/access/AccessDatabaseMeta.java b/plugins/databases/access/src/main/java/org/apache/hop/databases/access/AccessDatabaseMeta.java
index 36f42fffb5..aa6110aa86 100644
--- a/plugins/databases/access/src/main/java/org/apache/hop/databases/access/AccessDatabaseMeta.java
+++ b/plugins/databases/access/src/main/java/org/apache/hop/databases/access/AccessDatabaseMeta.java
@@ -24,6 +24,7 @@ import org.apache.hop.core.database.DatabaseMeta;
import org.apache.hop.core.database.DatabaseMetaPlugin;
import org.apache.hop.core.database.DriverDownload;
import org.apache.hop.core.database.IDatabase;
+import org.apache.hop.core.database.types.ColumnContext;
import org.apache.hop.core.gui.plugin.GuiPlugin;
import org.apache.hop.core.row.IValueMeta;
@@ -113,7 +114,7 @@ public class AccessDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
return "ALTER TABLE "
+ tableName
+ " ADD "
- + getFieldDefinition(v, tk, pk, useAutoinc, true, false);
+ + getColumnDefinition(v, tk, pk, useAutoinc, true, false, ColumnContext.Purpose.ADD_COLUMN);
}
@Override
@@ -124,7 +125,8 @@ public class AccessDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
+ " ALTER COLUMN "
+ v.getName()
+ " SET "
- + getFieldDefinition(v, tk, pk, useAutoinc, false, false);
+ + getColumnDefinition(
+ v, tk, pk, useAutoinc, false, false, ColumnContext.Purpose.MODIFY_COLUMN);
}
@Override
diff --git a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java b/plugins/databases/access/src/test/java/org/apache/hop/databases/access/AccessFieldDefinitionGoldenTest.java
similarity index 58%
copy from plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
copy to plugins/databases/access/src/test/java/org/apache/hop/databases/access/AccessFieldDefinitionGoldenTest.java
index d138cf8a7f..ce36a391ac 100644
--- a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
+++ b/plugins/databases/access/src/test/java/org/apache/hop/databases/access/AccessFieldDefinitionGoldenTest.java
@@ -14,19 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.hop.databases.access;
-package org.apache.hop.databases.cockroachdb;
-
-import org.apache.hop.core.database.DatabaseMetaPlugin;
import org.apache.hop.core.database.IDatabase;
-import org.apache.hop.core.gui.plugin.GuiPlugin;
-import org.apache.hop.databases.postgresql.PostgreSqlDatabaseMeta;
+import org.apache.hop.junit.database.BaseFieldDefinitionGoldenTest;
-/** Contains PostgreSQL specific information through static final members */
-@DatabaseMetaPlugin(
- type = "COCKROACHDB",
- typeDescription = "CockroachDB",
- image = "cockroachdb.svg",
- documentationUrl = "/database/databases/postgresql.html")
-@GuiPlugin(id = "GUI-CockroachDatabaseMeta")
-public class CockroachDatabaseMeta extends PostgreSqlDatabaseMeta implements IDatabase {}
+class AccessFieldDefinitionGoldenTest extends BaseFieldDefinitionGoldenTest {
+ @Override
+ protected IDatabase createDatabase() {
+ return new AccessDatabaseMeta();
+ }
+}
diff --git a/plugins/databases/access/src/test/resources/org/apache/hop/databases/access/AccessFieldDefinitionGoldenTest.txt b/plugins/databases/access/src/test/resources/org/apache/hop/databases/access/AccessFieldDefinitionGoldenTest.txt
new file mode 100644
index 0000000000..faaab2b5d7
--- /dev/null
+++ b/plugins/databases/access/src/test/resources/org/apache/hop/databases/access/AccessFieldDefinitionGoldenTest.txt
@@ -0,0 +1,240 @@
+# org.apache.hop.databases.access.AccessDatabaseMeta
+# Generated by AccessFieldDefinitionGoldenTest. Do not hand-edit.
+# Records current behaviour including known bugs. See the class javadoc.
+
+[plain]
+STRING len= -1 prec= -1 - -> TEXT
+STRING len= 0 prec= 0 - -> TEXT
+STRING len= 4 prec= 0 - -> TEXT
+STRING len= 9 prec= 0 - -> TEXT
+STRING len= 15 prec= 0 - -> TEXT
+STRING len= 18 prec= 0 - -> TEXT
+STRING len= 20 prec= 0 - -> TEXT
+STRING len= 10 prec= 2 - -> TEXT
+STRING len= 9999999 prec= 0 - -> MEMO
+INTEGER len= -1 prec= -1 - -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= 0 prec= 0 - -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= 4 prec= 0 - -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= 9 prec= 0 - -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= 15 prec= 0 - -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= 18 prec= 0 - -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= 20 prec= 0 - -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= 10 prec= 2 - -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= 9999999 prec= 0 - -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+NUMBER len= -1 prec= -1 - -> DOUBLE
+NUMBER len= 0 prec= 0 - -> DOUBLE
+NUMBER len= 4 prec= 0 - -> DOUBLE
+NUMBER len= 9 prec= 0 - -> DOUBLE
+NUMBER len= 15 prec= 0 - -> DOUBLE
+NUMBER len= 18 prec= 0 - -> DOUBLE
+NUMBER len= 20 prec= 0 - -> DOUBLE
+NUMBER len= 10 prec= 2 - -> DOUBLE
+NUMBER len= 9999999 prec= 0 - -> DOUBLE
+BIGNUMBER len= -1 prec= -1 - -> NUMERIC
+BIGNUMBER len= 0 prec= 0 - -> NUMERIC
+BIGNUMBER len= 4 prec= 0 - -> NUMERIC
+BIGNUMBER len= 9 prec= 0 - -> NUMERIC
+BIGNUMBER len= 15 prec= 0 - -> NUMERIC
+BIGNUMBER len= 18 prec= 0 - -> NUMERIC
+BIGNUMBER len= 20 prec= 0 - -> NUMERIC
+BIGNUMBER len= 10 prec= 2 - -> NUMERIC
+BIGNUMBER len= 9999999 prec= 0 - -> NUMERIC
+DATE len= -1 prec= -1 - -> DATETIME
+DATE len= 0 prec= 0 - -> DATETIME
+DATE len= 4 prec= 0 - -> DATETIME
+DATE len= 9 prec= 0 - -> DATETIME
+DATE len= 15 prec= 0 - -> DATETIME
+DATE len= 18 prec= 0 - -> DATETIME
+DATE len= 20 prec= 0 - -> DATETIME
+DATE len= 10 prec= 2 - -> 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= 15 prec= 0 - -> DATETIME
+TIMESTAMP len= 18 prec= 0 - -> DATETIME
+TIMESTAMP len= 20 prec= 0 - -> DATETIME
+TIMESTAMP len= 10 prec= 2 - -> DATETIME
+TIMESTAMP len= 9999999 prec= 0 - -> DATETIME
+BOOLEAN len= -1 prec= -1 - -> YESNO
+BOOLEAN len= 0 prec= 0 - -> YESNO
+BOOLEAN len= 4 prec= 0 - -> YESNO
+BOOLEAN len= 9 prec= 0 - -> YESNO
+BOOLEAN len= 15 prec= 0 - -> YESNO
+BOOLEAN len= 18 prec= 0 - -> YESNO
+BOOLEAN len= 20 prec= 0 - -> YESNO
+BOOLEAN len= 10 prec= 2 - -> YESNO
+BOOLEAN len= 9999999 prec= 0 - -> YESNO
+BINARY len= -1 prec= -1 - -> UNKNOWN
+BINARY len= 0 prec= 0 - -> UNKNOWN
+BINARY len= 4 prec= 0 - -> UNKNOWN
+BINARY len= 9 prec= 0 - -> UNKNOWN
+BINARY len= 15 prec= 0 - -> UNKNOWN
+BINARY len= 18 prec= 0 - -> UNKNOWN
+BINARY len= 20 prec= 0 - -> UNKNOWN
+BINARY len= 10 prec= 2 - -> UNKNOWN
+BINARY len= 9999999 prec= 0 - -> UNKNOWN
+INET len= -1 prec= -1 - -> UNKNOWN
+INET len= 0 prec= 0 - -> UNKNOWN
+INET len= 4 prec= 0 - -> UNKNOWN
+INET len= 9 prec= 0 - -> UNKNOWN
+INET len= 15 prec= 0 - -> UNKNOWN
+INET len= 18 prec= 0 - -> UNKNOWN
+INET len= 20 prec= 0 - -> UNKNOWN
+INET len= 10 prec= 2 - -> UNKNOWN
+INET len= 9999999 prec= 0 - -> UNKNOWN
+JSON len= -1 prec= -1 - -> JSON
+JSON len= 0 prec= 0 - -> JSON
+JSON len= 4 prec= 0 - -> JSON
+JSON len= 9 prec= 0 - -> JSON
+JSON len= 15 prec= 0 - -> JSON
+JSON len= 18 prec= 0 - -> JSON
+JSON len= 20 prec= 0 - -> JSON
+JSON len= 10 prec= 2 - -> JSON
+JSON len= 9999999 prec= 0 - -> JSON
+
+[keys]
+INTEGER len= -1 prec= -1 tk -> AUTOINCREMENT
+INTEGER len= -1 prec= -1 pk -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= -1 prec= -1 autoinc -> AUTOINCREMENT
+INTEGER len= 0 prec= 0 tk -> AUTOINCREMENT
+INTEGER len= 0 prec= 0 pk -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= 0 prec= 0 autoinc -> AUTOINCREMENT
+INTEGER len= 4 prec= 0 tk -> AUTOINCREMENT
+INTEGER len= 4 prec= 0 pk -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= 4 prec= 0 autoinc -> AUTOINCREMENT
+INTEGER len= 9 prec= 0 tk -> AUTOINCREMENT
+INTEGER len= 9 prec= 0 pk -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= 9 prec= 0 autoinc -> AUTOINCREMENT
+INTEGER len= 15 prec= 0 tk -> AUTOINCREMENT
+INTEGER len= 15 prec= 0 pk -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= 15 prec= 0 autoinc -> AUTOINCREMENT
+INTEGER len= 18 prec= 0 tk -> AUTOINCREMENT
+INTEGER len= 18 prec= 0 pk -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= 18 prec= 0 autoinc -> AUTOINCREMENT
+INTEGER len= 20 prec= 0 tk -> AUTOINCREMENT
+INTEGER len= 20 prec= 0 pk -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= 20 prec= 0 autoinc -> AUTOINCREMENT
+INTEGER len= 10 prec= 2 tk -> AUTOINCREMENT
+INTEGER len= 10 prec= 2 pk -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= 10 prec= 2 autoinc -> AUTOINCREMENT
+INTEGER len= 9999999 prec= 0 tk -> AUTOINCREMENT
+INTEGER len= 9999999 prec= 0 pk -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= 9999999 prec= 0 autoinc -> AUTOINCREMENT
+NUMBER len= -1 prec= -1 tk -> DOUBLE
+NUMBER len= -1 prec= -1 pk -> DOUBLE
+NUMBER len= -1 prec= -1 autoinc -> DOUBLE
+NUMBER len= 0 prec= 0 tk -> DOUBLE
+NUMBER len= 0 prec= 0 pk -> DOUBLE
+NUMBER len= 0 prec= 0 autoinc -> DOUBLE
+NUMBER len= 4 prec= 0 tk -> DOUBLE
+NUMBER len= 4 prec= 0 pk -> DOUBLE
+NUMBER len= 4 prec= 0 autoinc -> DOUBLE
+NUMBER len= 9 prec= 0 tk -> DOUBLE
+NUMBER len= 9 prec= 0 pk -> DOUBLE
+NUMBER len= 9 prec= 0 autoinc -> DOUBLE
+NUMBER len= 15 prec= 0 tk -> DOUBLE
+NUMBER len= 15 prec= 0 pk -> DOUBLE
+NUMBER len= 15 prec= 0 autoinc -> DOUBLE
+NUMBER len= 18 prec= 0 tk -> DOUBLE
+NUMBER len= 18 prec= 0 pk -> DOUBLE
+NUMBER len= 18 prec= 0 autoinc -> DOUBLE
+NUMBER len= 20 prec= 0 tk -> DOUBLE
+NUMBER len= 20 prec= 0 pk -> DOUBLE
+NUMBER len= 20 prec= 0 autoinc -> DOUBLE
+NUMBER len= 10 prec= 2 tk -> DOUBLE
+NUMBER len= 10 prec= 2 pk -> DOUBLE
+NUMBER len= 10 prec= 2 autoinc -> DOUBLE
+NUMBER len= 9999999 prec= 0 tk -> DOUBLE
+NUMBER len= 9999999 prec= 0 pk -> DOUBLE
+NUMBER len= 9999999 prec= 0 autoinc -> DOUBLE
+BIGNUMBER len= -1 prec= -1 tk -> NUMERIC
+BIGNUMBER len= -1 prec= -1 pk -> NUMERIC
+BIGNUMBER len= -1 prec= -1 autoinc -> NUMERIC
+BIGNUMBER len= 0 prec= 0 tk -> NUMERIC
+BIGNUMBER len= 0 prec= 0 pk -> NUMERIC
+BIGNUMBER len= 0 prec= 0 autoinc -> NUMERIC
+BIGNUMBER len= 4 prec= 0 tk -> NUMERIC
+BIGNUMBER len= 4 prec= 0 pk -> NUMERIC
+BIGNUMBER len= 4 prec= 0 autoinc -> NUMERIC
+BIGNUMBER len= 9 prec= 0 tk -> NUMERIC
+BIGNUMBER len= 9 prec= 0 pk -> NUMERIC
+BIGNUMBER len= 9 prec= 0 autoinc -> NUMERIC
+BIGNUMBER len= 15 prec= 0 tk -> NUMERIC
+BIGNUMBER len= 15 prec= 0 pk -> NUMERIC
+BIGNUMBER len= 15 prec= 0 autoinc -> NUMERIC
+BIGNUMBER len= 18 prec= 0 tk -> NUMERIC
+BIGNUMBER len= 18 prec= 0 pk -> NUMERIC
+BIGNUMBER len= 18 prec= 0 autoinc -> NUMERIC
+BIGNUMBER len= 20 prec= 0 tk -> NUMERIC
+BIGNUMBER len= 20 prec= 0 pk -> NUMERIC
+BIGNUMBER len= 20 prec= 0 autoinc -> NUMERIC
+BIGNUMBER len= 10 prec= 2 tk -> NUMERIC
+BIGNUMBER len= 10 prec= 2 pk -> NUMERIC
+BIGNUMBER len= 10 prec= 2 autoinc -> NUMERIC
+BIGNUMBER len= 9999999 prec= 0 tk -> NUMERIC
+BIGNUMBER len= 9999999 prec= 0 pk -> NUMERIC
+BIGNUMBER len= 9999999 prec= 0 autoinc -> NUMERIC
+
+[alter]
+STRING len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL TEXT
+STRING len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL SET TEXT
+STRING len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL TEXT
+STRING len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL SET TEXT
+STRING len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL TEXT
+STRING len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL SET TEXT
+INTEGER len= -1 prec= -1 add -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= -1 prec= -1 modify -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= 15 prec= 0 add -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= 15 prec= 0 modify -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= 10 prec= 2 add -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+INTEGER len= 10 prec= 2 modify -> !! NullPointerException: Cannot invoke "String.equals(Object)" because "tk" is null
+NUMBER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL DOUBLE
+NUMBER len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL SET DOUBLE
+NUMBER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL DOUBLE
+NUMBER len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL SET DOUBLE
+NUMBER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL DOUBLE
+NUMBER len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL SET DOUBLE
+BIGNUMBER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL NUMERIC
+BIGNUMBER len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL SET NUMERIC
+BIGNUMBER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL NUMERIC
+BIGNUMBER len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL SET NUMERIC
+BIGNUMBER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL NUMERIC
+BIGNUMBER len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL SET NUMERIC
+DATE len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL DATETIME
+DATE len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL SET 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 SET DATETIME
+DATE len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL DATETIME
+DATE len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL SET 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 SET 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 SET 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 SET DATETIME
+BOOLEAN len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL YESNO
+BOOLEAN len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL SET YESNO
+BOOLEAN len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL YESNO
+BOOLEAN len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL SET YESNO
+BOOLEAN len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL YESNO
+BOOLEAN len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL SET YESNO
+BINARY len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL UNKNOWN
+BINARY len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL SET UNKNOWN
+BINARY len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL UNKNOWN
+BINARY len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL SET UNKNOWN
+BINARY len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL UNKNOWN
+BINARY len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL SET UNKNOWN
+INET len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL UNKNOWN
+INET len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL SET UNKNOWN
+INET len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL UNKNOWN
+INET len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL SET UNKNOWN
+INET len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL UNKNOWN
+INET len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL SET UNKNOWN
+JSON len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL JSON
+JSON len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL SET JSON
+JSON len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL JSON
+JSON len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL SET JSON
+JSON len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL JSON
+JSON len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL SET JSON
diff --git a/plugins/databases/as400/src/main/java/org/apache/hop/databases/as400/AS400DatabaseMeta.java b/plugins/databases/as400/src/main/java/org/apache/hop/databases/as400/AS400DatabaseMeta.java
index f7819b2ed7..40bb6ae1a9 100644
--- a/plugins/databases/as400/src/main/java/org/apache/hop/databases/as400/AS400DatabaseMeta.java
+++ b/plugins/databases/as400/src/main/java/org/apache/hop/databases/as400/AS400DatabaseMeta.java
@@ -23,6 +23,7 @@ import org.apache.hop.core.database.DatabaseMeta;
import org.apache.hop.core.database.DatabaseMetaPlugin;
import org.apache.hop.core.database.DriverDownload;
import org.apache.hop.core.database.IDatabase;
+import org.apache.hop.core.database.types.ColumnContext;
import org.apache.hop.core.gui.plugin.GuiPlugin;
import org.apache.hop.core.row.IValueMeta;
@@ -31,7 +32,8 @@ import org.apache.hop.core.row.IValueMeta;
type = "AS/400",
typeDescription = "AS/400",
image = "db2.svg",
- documentationUrl = "/database/databases/as400.html")
+ documentationUrl = "/database/databases/as400.html",
+ classLoaderGroup = "as400-db")
@GuiPlugin(id = "GUI-AS400DatabaseMeta")
public class AS400DatabaseMeta extends BaseDatabaseMeta implements IDatabase {
@Override
@@ -99,7 +101,7 @@ public class AS400DatabaseMeta extends BaseDatabaseMeta implements IDatabase {
return "ALTER TABLE "
+ tableName
+ " ADD "
- + getFieldDefinition(v, tk, pk, useAutoinc, true, false);
+ + getColumnDefinition(v, tk, pk, useAutoinc, true, false, ColumnContext.Purpose.ADD_COLUMN);
}
/**
@@ -121,7 +123,8 @@ public class AS400DatabaseMeta extends BaseDatabaseMeta implements IDatabase {
+ " ALTER COLUMN "
+ v.getName()
+ " SET "
- + getFieldDefinition(v, tk, pk, useAutoinc, false, false);
+ + getColumnDefinition(
+ v, tk, pk, useAutoinc, false, false, ColumnContext.Purpose.MODIFY_COLUMN);
}
@Override
diff --git a/plugins/databases/as400/src/main/java/org/apache/hop/workflow/actions/as400command/ActionAs400Command.java b/plugins/databases/as400/src/main/java/org/apache/hop/workflow/actions/as400command/ActionAs400Command.java
index 982c90dca2..d93322ca06 100644
--- a/plugins/databases/as400/src/main/java/org/apache/hop/workflow/actions/as400command/ActionAs400Command.java
+++ b/plugins/databases/as400/src/main/java/org/apache/hop/workflow/actions/as400command/ActionAs400Command.java
@@ -48,7 +48,8 @@ import org.apache.hop.workflow.action.validator.AndValidator;
categoryDescription = "i18n:org.apache.hop.workflow:ActionCategory.Category.Utility",
keywords = "i18n::ActionAs400Command.keyword",
documentationUrl = "/workflow/actions/as400command.html",
- isIncludeJdbcDrivers = true)
+ isIncludeJdbcDrivers = true,
+ classLoaderGroup = "as400-db")
public class ActionAs400Command extends ActionBase implements Cloneable, IAction {
private static final Class<?> PKG = ActionAs400Command.class;
diff --git a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java b/plugins/databases/as400/src/test/java/org/apache/hop/databases/as400/AS400FieldDefinitionGoldenTest.java
similarity index 58%
copy from plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
copy to plugins/databases/as400/src/test/java/org/apache/hop/databases/as400/AS400FieldDefinitionGoldenTest.java
index d138cf8a7f..9dc1f5e568 100644
--- a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
+++ b/plugins/databases/as400/src/test/java/org/apache/hop/databases/as400/AS400FieldDefinitionGoldenTest.java
@@ -14,19 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.hop.databases.as400;
-package org.apache.hop.databases.cockroachdb;
-
-import org.apache.hop.core.database.DatabaseMetaPlugin;
import org.apache.hop.core.database.IDatabase;
-import org.apache.hop.core.gui.plugin.GuiPlugin;
-import org.apache.hop.databases.postgresql.PostgreSqlDatabaseMeta;
+import org.apache.hop.junit.database.BaseFieldDefinitionGoldenTest;
-/** Contains PostgreSQL specific information through static final members */
-@DatabaseMetaPlugin(
- type = "COCKROACHDB",
- typeDescription = "CockroachDB",
- image = "cockroachdb.svg",
- documentationUrl = "/database/databases/postgresql.html")
-@GuiPlugin(id = "GUI-CockroachDatabaseMeta")
-public class CockroachDatabaseMeta extends PostgreSqlDatabaseMeta implements IDatabase {}
+class AS400FieldDefinitionGoldenTest extends BaseFieldDefinitionGoldenTest {
+ @Override
+ protected IDatabase createDatabase() {
+ return new AS400DatabaseMeta();
+ }
+}
diff --git a/plugins/databases/as400/src/test/resources/org/apache/hop/databases/as400/AS400FieldDefinitionGoldenTest.txt b/plugins/databases/as400/src/test/resources/org/apache/hop/databases/as400/AS400FieldDefinitionGoldenTest.txt
new file mode 100644
index 0000000000..ce413b0107
--- /dev/null
+++ b/plugins/databases/as400/src/test/resources/org/apache/hop/databases/as400/AS400FieldDefinitionGoldenTest.txt
@@ -0,0 +1,240 @@
+# org.apache.hop.databases.as400.AS400DatabaseMeta
+# Generated by AS400FieldDefinitionGoldenTest. Do not hand-edit.
+# Records current behaviour including known bugs. See the class javadoc.
+
+[plain]
+STRING len= -1 prec= -1 - -> VARCHAR()
+STRING len= 0 prec= 0 - -> VARCHAR()
+STRING len= 4 prec= 0 - -> VARCHAR(4)
+STRING len= 9 prec= 0 - -> VARCHAR(9)
+STRING len= 15 prec= 0 - -> VARCHAR(15)
+STRING len= 18 prec= 0 - -> VARCHAR(18)
+STRING len= 20 prec= 0 - -> VARCHAR(20)
+STRING len= 10 prec= 2 - -> VARCHAR(10)
+STRING len= 9999999 prec= 0 - -> CLOB
+INTEGER len= -1 prec= -1 - -> INT
+INTEGER len= 0 prec= 0 - -> INT
+INTEGER len= 4 prec= 0 - -> INT
+INTEGER len= 9 prec= 0 - -> INT
+INTEGER len= 15 prec= 0 - -> DECIMAL(15)
+INTEGER len= 18 prec= 0 - -> DECIMAL(18)
+INTEGER len= 20 prec= 0 - -> DECIMAL(20)
+INTEGER len= 10 prec= 2 - -> DECIMAL(10)
+INTEGER len= 9999999 prec= 0 - -> DECIMAL(9999999)
+NUMBER len= -1 prec= -1 - -> DOUBLE
+NUMBER len= 0 prec= 0 - -> DOUBLE
+NUMBER len= 4 prec= 0 - -> DOUBLE
+NUMBER len= 9 prec= 0 - -> DOUBLE
+NUMBER len= 15 prec= 0 - -> DOUBLE
+NUMBER len= 18 prec= 0 - -> DOUBLE
+NUMBER len= 20 prec= 0 - -> DOUBLE
+NUMBER len= 10 prec= 2 - -> DOUBLE
+NUMBER len= 9999999 prec= 0 - -> DOUBLE
+BIGNUMBER len= -1 prec= -1 - -> DECIMAL(16,16)
+BIGNUMBER len= 0 prec= 0 - -> DECIMAL(16,16)
+BIGNUMBER len= 4 prec= 0 - -> DECIMAL(4,16)
+BIGNUMBER len= 9 prec= 0 - -> DECIMAL(9,16)
+BIGNUMBER len= 15 prec= 0 - -> DECIMAL(15,16)
+BIGNUMBER len= 18 prec= 0 - -> DECIMAL(18,16)
+BIGNUMBER len= 20 prec= 0 - -> DECIMAL(20,16)
+BIGNUMBER len= 10 prec= 2 - -> DECIMAL(10,2)
+BIGNUMBER len= 9999999 prec= 0 - -> DECIMAL(9999999,16)
+DATE len= -1 prec= -1 - -> TIMESTAMP
+DATE len= 0 prec= 0 - -> TIMESTAMP
+DATE len= 4 prec= 0 - -> TIMESTAMP
+DATE len= 9 prec= 0 - -> TIMESTAMP
+DATE len= 15 prec= 0 - -> TIMESTAMP
+DATE len= 18 prec= 0 - -> TIMESTAMP
+DATE len= 20 prec= 0 - -> TIMESTAMP
+DATE len= 10 prec= 2 - -> TIMESTAMP
+DATE len= 9999999 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= -1 prec= -1 - -> TIMESTAMP
+TIMESTAMP len= 0 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 4 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 9 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 15 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 18 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 20 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 10 prec= 2 - -> TIMESTAMP
+TIMESTAMP len= 9999999 prec= 0 - -> TIMESTAMP
+BOOLEAN len= -1 prec= -1 - -> CHAR(1)
+BOOLEAN len= 0 prec= 0 - -> CHAR(1)
+BOOLEAN len= 4 prec= 0 - -> CHAR(1)
+BOOLEAN len= 9 prec= 0 - -> CHAR(1)
+BOOLEAN len= 15 prec= 0 - -> CHAR(1)
+BOOLEAN len= 18 prec= 0 - -> CHAR(1)
+BOOLEAN len= 20 prec= 0 - -> CHAR(1)
+BOOLEAN len= 10 prec= 2 - -> CHAR(1)
+BOOLEAN len= 9999999 prec= 0 - -> CHAR(1)
+BINARY len= -1 prec= -1 - -> UNKNOWN
+BINARY len= 0 prec= 0 - -> UNKNOWN
+BINARY len= 4 prec= 0 - -> UNKNOWN
+BINARY len= 9 prec= 0 - -> UNKNOWN
+BINARY len= 15 prec= 0 - -> UNKNOWN
+BINARY len= 18 prec= 0 - -> UNKNOWN
+BINARY len= 20 prec= 0 - -> UNKNOWN
+BINARY len= 10 prec= 2 - -> UNKNOWN
+BINARY len= 9999999 prec= 0 - -> UNKNOWN
+INET len= -1 prec= -1 - -> UNKNOWN
+INET len= 0 prec= 0 - -> UNKNOWN
+INET len= 4 prec= 0 - -> UNKNOWN
+INET len= 9 prec= 0 - -> UNKNOWN
+INET len= 15 prec= 0 - -> UNKNOWN
+INET len= 18 prec= 0 - -> UNKNOWN
+INET len= 20 prec= 0 - -> UNKNOWN
+INET len= 10 prec= 2 - -> UNKNOWN
+INET len= 9999999 prec= 0 - -> UNKNOWN
+JSON len= -1 prec= -1 - -> JSON
+JSON len= 0 prec= 0 - -> JSON
+JSON len= 4 prec= 0 - -> JSON
+JSON len= 9 prec= 0 - -> JSON
+JSON len= 15 prec= 0 - -> JSON
+JSON len= 18 prec= 0 - -> JSON
+JSON len= 20 prec= 0 - -> JSON
+JSON len= 10 prec= 2 - -> JSON
+JSON len= 9999999 prec= 0 - -> JSON
+
+[keys]
+INTEGER len= -1 prec= -1 tk -> INT
+INTEGER len= -1 prec= -1 pk -> INT
+INTEGER len= -1 prec= -1 autoinc -> INT
+INTEGER len= 0 prec= 0 tk -> INT
+INTEGER len= 0 prec= 0 pk -> INT
+INTEGER len= 0 prec= 0 autoinc -> INT
+INTEGER len= 4 prec= 0 tk -> INT
+INTEGER len= 4 prec= 0 pk -> INT
+INTEGER len= 4 prec= 0 autoinc -> INT
+INTEGER len= 9 prec= 0 tk -> INT
+INTEGER len= 9 prec= 0 pk -> INT
+INTEGER len= 9 prec= 0 autoinc -> INT
+INTEGER len= 15 prec= 0 tk -> DECIMAL(15)
+INTEGER len= 15 prec= 0 pk -> DECIMAL(15)
+INTEGER len= 15 prec= 0 autoinc -> DECIMAL(15)
+INTEGER len= 18 prec= 0 tk -> DECIMAL(18)
+INTEGER len= 18 prec= 0 pk -> DECIMAL(18)
+INTEGER len= 18 prec= 0 autoinc -> DECIMAL(18)
+INTEGER len= 20 prec= 0 tk -> DECIMAL(20)
+INTEGER len= 20 prec= 0 pk -> DECIMAL(20)
+INTEGER len= 20 prec= 0 autoinc -> DECIMAL(20)
+INTEGER len= 10 prec= 2 tk -> DECIMAL(10)
+INTEGER len= 10 prec= 2 pk -> DECIMAL(10)
+INTEGER len= 10 prec= 2 autoinc -> DECIMAL(10)
+INTEGER len= 9999999 prec= 0 tk -> DECIMAL(9999999)
+INTEGER len= 9999999 prec= 0 pk -> DECIMAL(9999999)
+INTEGER len= 9999999 prec= 0 autoinc -> DECIMAL(9999999)
+NUMBER len= -1 prec= -1 tk -> DOUBLE
+NUMBER len= -1 prec= -1 pk -> DOUBLE
+NUMBER len= -1 prec= -1 autoinc -> DOUBLE
+NUMBER len= 0 prec= 0 tk -> DOUBLE
+NUMBER len= 0 prec= 0 pk -> DOUBLE
+NUMBER len= 0 prec= 0 autoinc -> DOUBLE
+NUMBER len= 4 prec= 0 tk -> DOUBLE
+NUMBER len= 4 prec= 0 pk -> DOUBLE
+NUMBER len= 4 prec= 0 autoinc -> DOUBLE
+NUMBER len= 9 prec= 0 tk -> DOUBLE
+NUMBER len= 9 prec= 0 pk -> DOUBLE
+NUMBER len= 9 prec= 0 autoinc -> DOUBLE
+NUMBER len= 15 prec= 0 tk -> DOUBLE
+NUMBER len= 15 prec= 0 pk -> DOUBLE
+NUMBER len= 15 prec= 0 autoinc -> DOUBLE
+NUMBER len= 18 prec= 0 tk -> DOUBLE
+NUMBER len= 18 prec= 0 pk -> DOUBLE
+NUMBER len= 18 prec= 0 autoinc -> DOUBLE
+NUMBER len= 20 prec= 0 tk -> DOUBLE
+NUMBER len= 20 prec= 0 pk -> DOUBLE
+NUMBER len= 20 prec= 0 autoinc -> DOUBLE
+NUMBER len= 10 prec= 2 tk -> DOUBLE
+NUMBER len= 10 prec= 2 pk -> DOUBLE
+NUMBER len= 10 prec= 2 autoinc -> DOUBLE
+NUMBER len= 9999999 prec= 0 tk -> DOUBLE
+NUMBER len= 9999999 prec= 0 pk -> DOUBLE
+NUMBER len= 9999999 prec= 0 autoinc -> DOUBLE
+BIGNUMBER len= -1 prec= -1 tk -> DECIMAL(16,16)
+BIGNUMBER len= -1 prec= -1 pk -> DECIMAL(16,16)
+BIGNUMBER len= -1 prec= -1 autoinc -> DECIMAL(16,16)
+BIGNUMBER len= 0 prec= 0 tk -> DECIMAL(16,16)
+BIGNUMBER len= 0 prec= 0 pk -> DECIMAL(16,16)
+BIGNUMBER len= 0 prec= 0 autoinc -> DECIMAL(16,16)
+BIGNUMBER len= 4 prec= 0 tk -> DECIMAL(4,16)
+BIGNUMBER len= 4 prec= 0 pk -> DECIMAL(4,16)
+BIGNUMBER len= 4 prec= 0 autoinc -> DECIMAL(4,16)
+BIGNUMBER len= 9 prec= 0 tk -> DECIMAL(9,16)
+BIGNUMBER len= 9 prec= 0 pk -> DECIMAL(9,16)
+BIGNUMBER len= 9 prec= 0 autoinc -> DECIMAL(9,16)
+BIGNUMBER len= 15 prec= 0 tk -> DECIMAL(15,16)
+BIGNUMBER len= 15 prec= 0 pk -> DECIMAL(15,16)
+BIGNUMBER len= 15 prec= 0 autoinc -> DECIMAL(15,16)
+BIGNUMBER len= 18 prec= 0 tk -> DECIMAL(18,16)
+BIGNUMBER len= 18 prec= 0 pk -> DECIMAL(18,16)
+BIGNUMBER len= 18 prec= 0 autoinc -> DECIMAL(18,16)
+BIGNUMBER len= 20 prec= 0 tk -> DECIMAL(20,16)
+BIGNUMBER len= 20 prec= 0 pk -> DECIMAL(20,16)
+BIGNUMBER len= 20 prec= 0 autoinc -> DECIMAL(20,16)
+BIGNUMBER len= 10 prec= 2 tk -> DECIMAL(10,2)
+BIGNUMBER len= 10 prec= 2 pk -> DECIMAL(10,2)
+BIGNUMBER len= 10 prec= 2 autoinc -> DECIMAL(10,2)
+BIGNUMBER len= 9999999 prec= 0 tk -> DECIMAL(9999999,16)
+BIGNUMBER len= 9999999 prec= 0 pk -> DECIMAL(9999999,16)
+BIGNUMBER len= 9999999 prec= 0 autoinc -> DECIMAL(9999999,16)
+
+[alter]
+STRING len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL VARCHAR()
+STRING len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL SET VARCHAR()
+STRING len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL VARCHAR(15)
+STRING len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL SET VARCHAR(15)
+STRING len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL VARCHAR(10)
+STRING len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL SET VARCHAR(10)
+INTEGER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL INT
+INTEGER len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL SET INT
+INTEGER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL DECIMAL(15)
+INTEGER len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL SET DECIMAL(15)
+INTEGER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL DECIMAL(10)
+INTEGER len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL SET DECIMAL(10)
+NUMBER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL DOUBLE
+NUMBER len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL SET DOUBLE
+NUMBER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL DOUBLE
+NUMBER len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL SET DOUBLE
+NUMBER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL DOUBLE
+NUMBER len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL SET DOUBLE
+BIGNUMBER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL DECIMAL(16,16)
+BIGNUMBER len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL SET DECIMAL(16,16)
+BIGNUMBER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL DECIMAL(15,16)
+BIGNUMBER len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL SET DECIMAL(15,16)
+BIGNUMBER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL DECIMAL(10,2)
+BIGNUMBER len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL SET DECIMAL(10,2)
+DATE len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL TIMESTAMP
+DATE len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL SET TIMESTAMP
+DATE len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL TIMESTAMP
+DATE len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL SET TIMESTAMP
+DATE len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL TIMESTAMP
+DATE len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL SET TIMESTAMP
+TIMESTAMP len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL TIMESTAMP
+TIMESTAMP len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL SET TIMESTAMP
+TIMESTAMP len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL TIMESTAMP
+TIMESTAMP len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL SET TIMESTAMP
+TIMESTAMP len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL TIMESTAMP
+TIMESTAMP len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL SET TIMESTAMP
+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 SET CHAR(1)
+BOOLEAN len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL CHAR(1)
+BOOLEAN len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL SET CHAR(1)
+BOOLEAN len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL CHAR(1)
+BOOLEAN len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL SET CHAR(1)
+BINARY len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL UNKNOWN
+BINARY len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL SET UNKNOWN
+BINARY len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL UNKNOWN
+BINARY len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL SET UNKNOWN
+BINARY len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL UNKNOWN
+BINARY len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL SET UNKNOWN
+INET len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL UNKNOWN
+INET len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL SET UNKNOWN
+INET len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL UNKNOWN
+INET len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL SET UNKNOWN
+INET len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL UNKNOWN
+INET len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL SET UNKNOWN
+JSON len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL JSON
+JSON len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL SET JSON
+JSON len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL JSON
+JSON len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL SET JSON
+JSON len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL JSON
+JSON len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL SET JSON
diff --git a/plugins/databases/cache/src/main/java/org/apache/hop/databases/cache/CacheDatabaseMeta.java b/plugins/databases/cache/src/main/java/org/apache/hop/databases/cache/CacheDatabaseMeta.java
index 341547ab90..f0f4824662 100644
--- a/plugins/databases/cache/src/main/java/org/apache/hop/databases/cache/CacheDatabaseMeta.java
+++ b/plugins/databases/cache/src/main/java/org/apache/hop/databases/cache/CacheDatabaseMeta.java
@@ -22,6 +22,7 @@ import org.apache.hop.core.database.BaseDatabaseMeta;
import org.apache.hop.core.database.DatabaseMeta;
import org.apache.hop.core.database.DatabaseMetaPlugin;
import org.apache.hop.core.database.IDatabase;
+import org.apache.hop.core.database.types.ColumnContext;
import org.apache.hop.core.gui.plugin.GuiPlugin;
import org.apache.hop.core.row.IValueMeta;
@@ -30,7 +31,8 @@ import org.apache.hop.core.row.IValueMeta;
type = "CACHE",
typeDescription = "InterSystems Cache",
image = "intersystems.svg",
- documentationUrl = "/database/databases/cache.html")
+ documentationUrl = "/database/databases/cache.html",
+ classLoaderGroup = "cache-db")
@GuiPlugin(id = "GUI-CacheDatabaseMeta")
public class CacheDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
@@ -94,7 +96,7 @@ public class CacheDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
return CONST_ALTER_TABLE
+ tableName
+ " ADD COLUMN ( "
- + getFieldDefinition(v, tk, pk, useAutoinc, true, false)
+ + getColumnDefinition(v, tk, pk, useAutoinc, true, false, ColumnContext.Purpose.ADD_COLUMN)
+ " ) ";
}
@@ -132,7 +134,8 @@ public class CacheDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
return CONST_ALTER_TABLE
+ tableName
+ " ALTER COLUMN "
- + getFieldDefinition(v, tk, pk, useAutoinc, true, false);
+ + getColumnDefinition(
+ v, tk, pk, useAutoinc, true, false, ColumnContext.Purpose.MODIFY_COLUMN);
}
@Override
diff --git a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java b/plugins/databases/cache/src/test/java/org/apache/hop/databases/cache/CacheFieldDefinitionGoldenTest.java
similarity index 58%
copy from plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
copy to plugins/databases/cache/src/test/java/org/apache/hop/databases/cache/CacheFieldDefinitionGoldenTest.java
index d138cf8a7f..b0aa4ff4f0 100644
--- a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
+++ b/plugins/databases/cache/src/test/java/org/apache/hop/databases/cache/CacheFieldDefinitionGoldenTest.java
@@ -14,19 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.hop.databases.cache;
-package org.apache.hop.databases.cockroachdb;
-
-import org.apache.hop.core.database.DatabaseMetaPlugin;
import org.apache.hop.core.database.IDatabase;
-import org.apache.hop.core.gui.plugin.GuiPlugin;
-import org.apache.hop.databases.postgresql.PostgreSqlDatabaseMeta;
+import org.apache.hop.junit.database.BaseFieldDefinitionGoldenTest;
-/** Contains PostgreSQL specific information through static final members */
-@DatabaseMetaPlugin(
- type = "COCKROACHDB",
- typeDescription = "CockroachDB",
- image = "cockroachdb.svg",
- documentationUrl = "/database/databases/postgresql.html")
-@GuiPlugin(id = "GUI-CockroachDatabaseMeta")
-public class CockroachDatabaseMeta extends PostgreSqlDatabaseMeta implements IDatabase {}
+class CacheFieldDefinitionGoldenTest extends BaseFieldDefinitionGoldenTest {
+ @Override
+ protected IDatabase createDatabase() {
+ return new CacheDatabaseMeta();
+ }
+}
diff --git a/plugins/databases/cache/src/test/resources/org/apache/hop/databases/cache/CacheFieldDefinitionGoldenTest.txt b/plugins/databases/cache/src/test/resources/org/apache/hop/databases/cache/CacheFieldDefinitionGoldenTest.txt
new file mode 100644
index 0000000000..bf9d7dd153
--- /dev/null
+++ b/plugins/databases/cache/src/test/resources/org/apache/hop/databases/cache/CacheFieldDefinitionGoldenTest.txt
@@ -0,0 +1,240 @@
+# org.apache.hop.databases.cache.CacheDatabaseMeta
+# Generated by CacheFieldDefinitionGoldenTest. Do not hand-edit.
+# Records current behaviour including known bugs. See the class javadoc.
+
+[plain]
+STRING len= -1 prec= -1 - -> VARCHAR
+STRING len= 0 prec= 0 - -> VARCHAR
+STRING len= 4 prec= 0 - -> VARCHAR(4)
+STRING len= 9 prec= 0 - -> VARCHAR(9)
+STRING len= 15 prec= 0 - -> VARCHAR(15)
+STRING len= 18 prec= 0 - -> VARCHAR(18)
+STRING len= 20 prec= 0 - -> VARCHAR(20)
+STRING len= 10 prec= 2 - -> VARCHAR(10)
+STRING len= 9999999 prec= 0 - -> VARCHAR(9999999)
+INTEGER len= -1 prec= -1 - -> INT
+INTEGER len= 0 prec= 0 - -> INT
+INTEGER len= 4 prec= 0 - -> INT
+INTEGER len= 9 prec= 0 - -> INT
+INTEGER len= 15 prec= 0 - -> DECIMAL(15)
+INTEGER len= 18 prec= 0 - -> DECIMAL(18)
+INTEGER len= 20 prec= 0 - -> DECIMAL(20)
+INTEGER len= 10 prec= 2 - -> DECIMAL(10)
+INTEGER len= 9999999 prec= 0 - -> DECIMAL(9999999)
+NUMBER len= -1 prec= -1 - -> DOUBLE
+NUMBER len= 0 prec= 0 - -> DOUBLE
+NUMBER len= 4 prec= 0 - -> DOUBLE
+NUMBER len= 9 prec= 0 - -> DOUBLE
+NUMBER len= 15 prec= 0 - -> DOUBLE
+NUMBER len= 18 prec= 0 - -> DOUBLE
+NUMBER len= 20 prec= 0 - -> DOUBLE
+NUMBER len= 10 prec= 2 - -> DOUBLE
+NUMBER len= 9999999 prec= 0 - -> DOUBLE
+BIGNUMBER len= -1 prec= -1 - -> DECIMAL(16,16)
+BIGNUMBER len= 0 prec= 0 - -> DECIMAL(16,16)
+BIGNUMBER len= 4 prec= 0 - -> DECIMAL(4,16)
+BIGNUMBER len= 9 prec= 0 - -> DECIMAL(9,16)
+BIGNUMBER len= 15 prec= 0 - -> DECIMAL(15,16)
+BIGNUMBER len= 18 prec= 0 - -> DECIMAL(18,16)
+BIGNUMBER len= 20 prec= 0 - -> DECIMAL(20,16)
+BIGNUMBER len= 10 prec= 2 - -> DECIMAL(10,2)
+BIGNUMBER len= 9999999 prec= 0 - -> DECIMAL(9999999,16)
+DATE len= -1 prec= -1 - -> TIMESTAMP
+DATE len= 0 prec= 0 - -> TIMESTAMP
+DATE len= 4 prec= 0 - -> TIMESTAMP
+DATE len= 9 prec= 0 - -> TIMESTAMP
+DATE len= 15 prec= 0 - -> TIMESTAMP
+DATE len= 18 prec= 0 - -> TIMESTAMP
+DATE len= 20 prec= 0 - -> TIMESTAMP
+DATE len= 10 prec= 2 - -> TIMESTAMP
+DATE len= 9999999 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= -1 prec= -1 - -> TIMESTAMP
+TIMESTAMP len= 0 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 4 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 9 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 15 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 18 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 20 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 10 prec= 2 - -> TIMESTAMP
+TIMESTAMP len= 9999999 prec= 0 - -> TIMESTAMP
+BOOLEAN len= -1 prec= -1 - -> CHAR(1)
+BOOLEAN len= 0 prec= 0 - -> CHAR(1)
+BOOLEAN len= 4 prec= 0 - -> CHAR(1)
+BOOLEAN len= 9 prec= 0 - -> CHAR(1)
+BOOLEAN len= 15 prec= 0 - -> CHAR(1)
+BOOLEAN len= 18 prec= 0 - -> CHAR(1)
+BOOLEAN len= 20 prec= 0 - -> CHAR(1)
+BOOLEAN len= 10 prec= 2 - -> CHAR(1)
+BOOLEAN len= 9999999 prec= 0 - -> CHAR(1)
+BINARY len= -1 prec= -1 - -> UNKNOWN
+BINARY len= 0 prec= 0 - -> UNKNOWN
+BINARY len= 4 prec= 0 - -> UNKNOWN
+BINARY len= 9 prec= 0 - -> UNKNOWN
+BINARY len= 15 prec= 0 - -> UNKNOWN
+BINARY len= 18 prec= 0 - -> UNKNOWN
+BINARY len= 20 prec= 0 - -> UNKNOWN
+BINARY len= 10 prec= 2 - -> UNKNOWN
+BINARY len= 9999999 prec= 0 - -> UNKNOWN
+INET len= -1 prec= -1 - -> UNKNOWN
+INET len= 0 prec= 0 - -> UNKNOWN
+INET len= 4 prec= 0 - -> UNKNOWN
+INET len= 9 prec= 0 - -> UNKNOWN
+INET len= 15 prec= 0 - -> UNKNOWN
+INET len= 18 prec= 0 - -> UNKNOWN
+INET len= 20 prec= 0 - -> UNKNOWN
+INET len= 10 prec= 2 - -> UNKNOWN
+INET len= 9999999 prec= 0 - -> UNKNOWN
+JSON len= -1 prec= -1 - -> JSON
+JSON len= 0 prec= 0 - -> JSON
+JSON len= 4 prec= 0 - -> JSON
+JSON len= 9 prec= 0 - -> JSON
+JSON len= 15 prec= 0 - -> JSON
+JSON len= 18 prec= 0 - -> JSON
+JSON len= 20 prec= 0 - -> JSON
+JSON len= 10 prec= 2 - -> JSON
+JSON len= 9999999 prec= 0 - -> JSON
+
+[keys]
+INTEGER len= -1 prec= -1 tk -> DECIMAL
+INTEGER len= -1 prec= -1 pk -> INT
+INTEGER len= -1 prec= -1 autoinc -> DECIMAL
+INTEGER len= 0 prec= 0 tk -> DECIMAL
+INTEGER len= 0 prec= 0 pk -> INT
+INTEGER len= 0 prec= 0 autoinc -> DECIMAL
+INTEGER len= 4 prec= 0 tk -> DECIMAL
+INTEGER len= 4 prec= 0 pk -> INT
+INTEGER len= 4 prec= 0 autoinc -> DECIMAL
+INTEGER len= 9 prec= 0 tk -> DECIMAL
+INTEGER len= 9 prec= 0 pk -> INT
+INTEGER len= 9 prec= 0 autoinc -> DECIMAL
+INTEGER len= 15 prec= 0 tk -> DECIMAL
+INTEGER len= 15 prec= 0 pk -> DECIMAL(15)
+INTEGER len= 15 prec= 0 autoinc -> DECIMAL
+INTEGER len= 18 prec= 0 tk -> DECIMAL
+INTEGER len= 18 prec= 0 pk -> DECIMAL(18)
+INTEGER len= 18 prec= 0 autoinc -> DECIMAL
+INTEGER len= 20 prec= 0 tk -> DECIMAL
+INTEGER len= 20 prec= 0 pk -> DECIMAL(20)
+INTEGER len= 20 prec= 0 autoinc -> DECIMAL
+INTEGER len= 10 prec= 2 tk -> DECIMAL
+INTEGER len= 10 prec= 2 pk -> DECIMAL(10)
+INTEGER len= 10 prec= 2 autoinc -> DECIMAL
+INTEGER len= 9999999 prec= 0 tk -> DECIMAL
+INTEGER len= 9999999 prec= 0 pk -> DECIMAL(9999999)
+INTEGER len= 9999999 prec= 0 autoinc -> DECIMAL
+NUMBER len= -1 prec= -1 tk -> DECIMAL
+NUMBER len= -1 prec= -1 pk -> DOUBLE
+NUMBER len= -1 prec= -1 autoinc -> DECIMAL
+NUMBER len= 0 prec= 0 tk -> DECIMAL
+NUMBER len= 0 prec= 0 pk -> DOUBLE
+NUMBER len= 0 prec= 0 autoinc -> DECIMAL
+NUMBER len= 4 prec= 0 tk -> DECIMAL
+NUMBER len= 4 prec= 0 pk -> DOUBLE
+NUMBER len= 4 prec= 0 autoinc -> DECIMAL
+NUMBER len= 9 prec= 0 tk -> DECIMAL
+NUMBER len= 9 prec= 0 pk -> DOUBLE
+NUMBER len= 9 prec= 0 autoinc -> DECIMAL
+NUMBER len= 15 prec= 0 tk -> DECIMAL
+NUMBER len= 15 prec= 0 pk -> DOUBLE
+NUMBER len= 15 prec= 0 autoinc -> DECIMAL
+NUMBER len= 18 prec= 0 tk -> DECIMAL
+NUMBER len= 18 prec= 0 pk -> DOUBLE
+NUMBER len= 18 prec= 0 autoinc -> DECIMAL
+NUMBER len= 20 prec= 0 tk -> DECIMAL
+NUMBER len= 20 prec= 0 pk -> DOUBLE
+NUMBER len= 20 prec= 0 autoinc -> DECIMAL
+NUMBER len= 10 prec= 2 tk -> DECIMAL
+NUMBER len= 10 prec= 2 pk -> DOUBLE
+NUMBER len= 10 prec= 2 autoinc -> DECIMAL
+NUMBER len= 9999999 prec= 0 tk -> DECIMAL
+NUMBER len= 9999999 prec= 0 pk -> DOUBLE
+NUMBER len= 9999999 prec= 0 autoinc -> DECIMAL
+BIGNUMBER len= -1 prec= -1 tk -> DECIMAL
+BIGNUMBER len= -1 prec= -1 pk -> DECIMAL(16,16)
+BIGNUMBER len= -1 prec= -1 autoinc -> DECIMAL
+BIGNUMBER len= 0 prec= 0 tk -> DECIMAL
+BIGNUMBER len= 0 prec= 0 pk -> DECIMAL(16,16)
+BIGNUMBER len= 0 prec= 0 autoinc -> DECIMAL
+BIGNUMBER len= 4 prec= 0 tk -> DECIMAL
+BIGNUMBER len= 4 prec= 0 pk -> DECIMAL(4,16)
+BIGNUMBER len= 4 prec= 0 autoinc -> DECIMAL
+BIGNUMBER len= 9 prec= 0 tk -> DECIMAL
+BIGNUMBER len= 9 prec= 0 pk -> DECIMAL(9,16)
+BIGNUMBER len= 9 prec= 0 autoinc -> DECIMAL
+BIGNUMBER len= 15 prec= 0 tk -> DECIMAL
+BIGNUMBER len= 15 prec= 0 pk -> DECIMAL(15,16)
+BIGNUMBER len= 15 prec= 0 autoinc -> DECIMAL
+BIGNUMBER len= 18 prec= 0 tk -> DECIMAL
+BIGNUMBER len= 18 prec= 0 pk -> DECIMAL(18,16)
+BIGNUMBER len= 18 prec= 0 autoinc -> DECIMAL
+BIGNUMBER len= 20 prec= 0 tk -> DECIMAL
+BIGNUMBER len= 20 prec= 0 pk -> DECIMAL(20,16)
+BIGNUMBER len= 20 prec= 0 autoinc -> DECIMAL
+BIGNUMBER len= 10 prec= 2 tk -> DECIMAL
+BIGNUMBER len= 10 prec= 2 pk -> DECIMAL(10,2)
+BIGNUMBER len= 10 prec= 2 autoinc -> DECIMAL
+BIGNUMBER len= 9999999 prec= 0 tk -> DECIMAL
+BIGNUMBER len= 9999999 prec= 0 pk -> DECIMAL(9999999,16)
+BIGNUMBER len= 9999999 prec= 0 autoinc -> DECIMAL
+
+[alter]
+STRING len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN ( COL VARCHAR )
+STRING len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL VARCHAR
+STRING len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN ( COL VARCHAR(15) )
+STRING len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL VARCHAR(15)
+STRING len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN ( COL VARCHAR(10) )
+STRING len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL VARCHAR(10)
+INTEGER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN ( COL INT )
+INTEGER len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL INT
+INTEGER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN ( COL DECIMAL(15) )
+INTEGER len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL DECIMAL(15)
+INTEGER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN ( COL DECIMAL(10) )
+INTEGER len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL DECIMAL(10)
+NUMBER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN ( COL DOUBLE )
+NUMBER len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL DOUBLE
+NUMBER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN ( COL DOUBLE )
+NUMBER len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL DOUBLE
+NUMBER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN ( COL DOUBLE )
+NUMBER len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL DOUBLE
+BIGNUMBER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN ( COL DECIMAL(16,16) )
+BIGNUMBER len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL DECIMAL(16,16)
+BIGNUMBER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN ( COL DECIMAL(15,16) )
+BIGNUMBER len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL DECIMAL(15,16)
+BIGNUMBER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN ( COL DECIMAL(10,2) )
+BIGNUMBER len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL DECIMAL(10,2)
+DATE len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN ( COL TIMESTAMP )
+DATE len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL TIMESTAMP
+DATE len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN ( COL TIMESTAMP )
+DATE len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL TIMESTAMP
+DATE len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN ( COL TIMESTAMP )
+DATE len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL TIMESTAMP
+TIMESTAMP len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN ( COL TIMESTAMP )
+TIMESTAMP len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL TIMESTAMP
+TIMESTAMP len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN ( COL TIMESTAMP )
+TIMESTAMP len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL TIMESTAMP
+TIMESTAMP len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN ( COL TIMESTAMP )
+TIMESTAMP len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL TIMESTAMP
+BOOLEAN len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN ( COL CHAR(1) )
+BOOLEAN len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL CHAR(1)
+BOOLEAN len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN ( COL CHAR(1) )
+BOOLEAN len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL CHAR(1)
+BOOLEAN len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN ( COL CHAR(1) )
+BOOLEAN len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL CHAR(1)
+BINARY len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN ( COL UNKNOWN )
+BINARY len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL UNKNOWN
+BINARY len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN ( COL UNKNOWN )
+BINARY len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL UNKNOWN
+BINARY len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN ( COL UNKNOWN )
+BINARY len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL UNKNOWN
+INET len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN ( COL UNKNOWN )
+INET len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL UNKNOWN
+INET len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN ( COL UNKNOWN )
+INET len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL UNKNOWN
+INET len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN ( COL UNKNOWN )
+INET len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL UNKNOWN
+JSON len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN ( COL JSON )
+JSON len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COLUMN COL JSON
+JSON len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN ( COL JSON )
+JSON len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COLUMN COL JSON
+JSON len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN ( COL JSON )
+JSON len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COLUMN COL JSON
diff --git a/plugins/databases/clickhouse/src/main/java/org/apache/hop/databases/clickhouse/ClickhouseDatabaseMeta.java b/plugins/databases/clickhouse/src/main/java/org/apache/hop/databases/clickhouse/ClickhouseDatabaseMeta.java
index 9b394b262b..14c9baaa22 100644
--- a/plugins/databases/clickhouse/src/main/java/org/apache/hop/databases/clickhouse/ClickhouseDatabaseMeta.java
+++ b/plugins/databases/clickhouse/src/main/java/org/apache/hop/databases/clickhouse/ClickhouseDatabaseMeta.java
@@ -26,6 +26,7 @@ import org.apache.hop.core.database.DatabaseMeta;
import org.apache.hop.core.database.DatabaseMetaPlugin;
import org.apache.hop.core.database.DriverDownload;
import org.apache.hop.core.database.IDatabase;
+import org.apache.hop.core.database.types.ColumnContext;
import org.apache.hop.core.gui.plugin.GuiPlugin;
import org.apache.hop.core.row.IValueMeta;
import org.apache.hop.core.util.Utils;
@@ -39,7 +40,8 @@ import org.apache.hop.core.util.Utils;
type = "CLICKHOUSE",
typeDescription = "ClickHouse",
image = "clikhouse.svg",
- documentationUrl = "/database/databases/clickhouse.html")
+ documentationUrl = "/database/databases/clickhouse.html",
+ classLoaderGroup = "clickhouse-db")
@GuiPlugin(id = "GUI-ClickhouseDatabaseMeta")
public class ClickhouseDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
@@ -135,7 +137,7 @@ public class ClickhouseDatabaseMeta extends BaseDatabaseMeta implements IDatabas
return CONST_ALTER_TABLE
+ tableName
+ " ADD COLUMN "
- + getFieldDefinition(v, tk, pk, useAutoinc, true, false);
+ + getColumnDefinition(v, tk, pk, useAutoinc, true, false, ColumnContext.Purpose.ADD_COLUMN);
}
@Override
@@ -150,7 +152,8 @@ public class ClickhouseDatabaseMeta extends BaseDatabaseMeta implements IDatabas
return CONST_ALTER_TABLE
+ tableName
+ " MODIFY COLUMN "
- + getFieldDefinition(v, tk, pk, useAutoinc, true, false);
+ + getColumnDefinition(
+ v, tk, pk, useAutoinc, true, false, ColumnContext.Purpose.MODIFY_COLUMN);
}
@Override
diff --git a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java b/plugins/databases/clickhouse/src/test/java/org/apache/hop/databases/clickhouse/ClickhouseFieldDefinitionGoldenTest.java
similarity index 58%
copy from plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
copy to plugins/databases/clickhouse/src/test/java/org/apache/hop/databases/clickhouse/ClickhouseFieldDefinitionGoldenTest.java
index d138cf8a7f..a83202d5ea 100644
--- a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
+++ b/plugins/databases/clickhouse/src/test/java/org/apache/hop/databases/clickhouse/ClickhouseFieldDefinitionGoldenTest.java
@@ -14,19 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.hop.databases.clickhouse;
-package org.apache.hop.databases.cockroachdb;
-
-import org.apache.hop.core.database.DatabaseMetaPlugin;
import org.apache.hop.core.database.IDatabase;
-import org.apache.hop.core.gui.plugin.GuiPlugin;
-import org.apache.hop.databases.postgresql.PostgreSqlDatabaseMeta;
+import org.apache.hop.junit.database.BaseFieldDefinitionGoldenTest;
-/** Contains PostgreSQL specific information through static final members */
-@DatabaseMetaPlugin(
- type = "COCKROACHDB",
- typeDescription = "CockroachDB",
- image = "cockroachdb.svg",
- documentationUrl = "/database/databases/postgresql.html")
-@GuiPlugin(id = "GUI-CockroachDatabaseMeta")
-public class CockroachDatabaseMeta extends PostgreSqlDatabaseMeta implements IDatabase {}
+class ClickhouseFieldDefinitionGoldenTest extends BaseFieldDefinitionGoldenTest {
+ @Override
+ protected IDatabase createDatabase() {
+ return new ClickhouseDatabaseMeta();
+ }
+}
diff --git a/plugins/databases/clickhouse/src/test/resources/org/apache/hop/databases/clickhouse/ClickhouseFieldDefinitionGoldenTest.txt b/plugins/databases/clickhouse/src/test/resources/org/apache/hop/databases/clickhouse/ClickhouseFieldDefinitionGoldenTest.txt
new file mode 100644
index 0000000000..44f69b4ee3
--- /dev/null
+++ b/plugins/databases/clickhouse/src/test/resources/org/apache/hop/databases/clickhouse/ClickhouseFieldDefinitionGoldenTest.txt
@@ -0,0 +1,240 @@
+# org.apache.hop.databases.clickhouse.ClickhouseDatabaseMeta
+# Generated by ClickhouseFieldDefinitionGoldenTest. Do not hand-edit.
+# Records current behaviour including known bugs. See the class javadoc.
+
+[plain]
+STRING len= -1 prec= -1 - -> STRING
+STRING len= 0 prec= 0 - -> STRING
+STRING len= 4 prec= 0 - -> STRING
+STRING len= 9 prec= 0 - -> STRING
+STRING len= 15 prec= 0 - -> STRING
+STRING len= 18 prec= 0 - -> STRING
+STRING len= 20 prec= 0 - -> STRING
+STRING len= 10 prec= 2 - -> STRING
+STRING len= 9999999 prec= 0 - -> STRING
+INTEGER len= -1 prec= -1 - -> INT32
+INTEGER len= 0 prec= 0 - -> INT32
+INTEGER len= 4 prec= 0 - -> INT32
+INTEGER len= 9 prec= 0 - -> INT32
+INTEGER len= 15 prec= 0 - -> INT64
+INTEGER len= 18 prec= 0 - -> INT64
+INTEGER len= 20 prec= 0 - -> INT128
+INTEGER len= 10 prec= 2 - -> INT64
+INTEGER len= 9999999 prec= 0 - -> INT128
+NUMBER len= -1 prec= -1 - -> FLOAT64
+NUMBER len= 0 prec= 0 - -> FLOAT64
+NUMBER len= 4 prec= 0 - -> FLOAT64
+NUMBER len= 9 prec= 0 - -> FLOAT64
+NUMBER len= 15 prec= 0 - -> FLOAT64
+NUMBER len= 18 prec= 0 - -> FLOAT64
+NUMBER len= 20 prec= 0 - -> FLOAT64
+NUMBER len= 10 prec= 2 - -> FLOAT64
+NUMBER len= 9999999 prec= 0 - -> FLOAT64
+BIGNUMBER len= -1 prec= -1 - -> DECIMAL(16,16)
+BIGNUMBER len= 0 prec= 0 - -> DECIMAL(16,16)
+BIGNUMBER len= 4 prec= 0 - -> DECIMAL(4,16)
+BIGNUMBER len= 9 prec= 0 - -> DECIMAL(9,16)
+BIGNUMBER len= 15 prec= 0 - -> DECIMAL(15,16)
+BIGNUMBER len= 18 prec= 0 - -> DECIMAL(18,16)
+BIGNUMBER len= 20 prec= 0 - -> DECIMAL(20,16)
+BIGNUMBER len= 10 prec= 2 - -> DECIMAL(10,2)
+BIGNUMBER len= 9999999 prec= 0 - -> DECIMAL(9999999,16)
+DATE len= -1 prec= -1 - -> DATE
+DATE len= 0 prec= 0 - -> DATE
+DATE len= 4 prec= 0 - -> DATE
+DATE len= 9 prec= 0 - -> DATE
+DATE len= 15 prec= 0 - -> DATE
+DATE len= 18 prec= 0 - -> DATE
+DATE len= 20 prec= 0 - -> DATE
+DATE len= 10 prec= 2 - -> DATE
+DATE len= 9999999 prec= 0 - -> DATE
+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= 15 prec= 0 - -> DATETIME
+TIMESTAMP len= 18 prec= 0 - -> DATETIME
+TIMESTAMP len= 20 prec= 0 - -> DATETIME
+TIMESTAMP len= 10 prec= 2 - -> DATETIME
+TIMESTAMP len= 9999999 prec= 0 - -> DATETIME
+BOOLEAN len= -1 prec= -1 - -> UINT8
+BOOLEAN len= 0 prec= 0 - -> UINT8
+BOOLEAN len= 4 prec= 0 - -> UINT8
+BOOLEAN len= 9 prec= 0 - -> UINT8
+BOOLEAN len= 15 prec= 0 - -> UINT8
+BOOLEAN len= 18 prec= 0 - -> UINT8
+BOOLEAN len= 20 prec= 0 - -> UINT8
+BOOLEAN len= 10 prec= 2 - -> UINT8
+BOOLEAN len= 9999999 prec= 0 - -> UINT8
+BINARY len= -1 prec= -1 - -> UNSUPPORTED
+BINARY len= 0 prec= 0 - -> UNSUPPORTED
+BINARY len= 4 prec= 0 - -> UNSUPPORTED
+BINARY len= 9 prec= 0 - -> UNSUPPORTED
+BINARY len= 15 prec= 0 - -> UNSUPPORTED
+BINARY len= 18 prec= 0 - -> UNSUPPORTED
+BINARY len= 20 prec= 0 - -> UNSUPPORTED
+BINARY len= 10 prec= 2 - -> UNSUPPORTED
+BINARY len= 9999999 prec= 0 - -> UNSUPPORTED
+INET len= -1 prec= -1 - -> UNKNOWN
+INET len= 0 prec= 0 - -> UNKNOWN
+INET len= 4 prec= 0 - -> UNKNOWN
+INET len= 9 prec= 0 - -> UNKNOWN
+INET len= 15 prec= 0 - -> UNKNOWN
+INET len= 18 prec= 0 - -> UNKNOWN
+INET len= 20 prec= 0 - -> UNKNOWN
+INET len= 10 prec= 2 - -> UNKNOWN
+INET len= 9999999 prec= 0 - -> UNKNOWN
+JSON len= -1 prec= -1 - -> JSON
+JSON len= 0 prec= 0 - -> JSON
+JSON len= 4 prec= 0 - -> JSON
+JSON len= 9 prec= 0 - -> JSON
+JSON len= 15 prec= 0 - -> JSON
+JSON len= 18 prec= 0 - -> JSON
+JSON len= 20 prec= 0 - -> JSON
+JSON len= 10 prec= 2 - -> JSON
+JSON len= 9999999 prec= 0 - -> JSON
+
+[keys]
+INTEGER len= -1 prec= -1 tk -> UUID NOT NULL PRIMARY KEY
+INTEGER len= -1 prec= -1 pk -> UUID NOT NULL PRIMARY KEY
+INTEGER len= -1 prec= -1 autoinc -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 0 prec= 0 tk -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 0 prec= 0 pk -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 0 prec= 0 autoinc -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 4 prec= 0 tk -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 4 prec= 0 pk -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 4 prec= 0 autoinc -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 9 prec= 0 tk -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 9 prec= 0 pk -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 9 prec= 0 autoinc -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 15 prec= 0 tk -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 15 prec= 0 pk -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 15 prec= 0 autoinc -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 18 prec= 0 tk -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 18 prec= 0 pk -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 18 prec= 0 autoinc -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 20 prec= 0 tk -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 20 prec= 0 pk -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 20 prec= 0 autoinc -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 10 prec= 2 tk -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 10 prec= 2 pk -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 10 prec= 2 autoinc -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 9999999 prec= 0 tk -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 9999999 prec= 0 pk -> UUID NOT NULL PRIMARY KEY
+INTEGER len= 9999999 prec= 0 autoinc -> UUID NOT NULL PRIMARY KEY
+NUMBER len= -1 prec= -1 tk -> UUID NOT NULL PRIMARY KEY
+NUMBER len= -1 prec= -1 pk -> UUID NOT NULL PRIMARY KEY
+NUMBER len= -1 prec= -1 autoinc -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 0 prec= 0 tk -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 0 prec= 0 pk -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 0 prec= 0 autoinc -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 4 prec= 0 tk -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 4 prec= 0 pk -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 4 prec= 0 autoinc -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 9 prec= 0 tk -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 9 prec= 0 pk -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 9 prec= 0 autoinc -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 15 prec= 0 tk -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 15 prec= 0 pk -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 15 prec= 0 autoinc -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 18 prec= 0 tk -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 18 prec= 0 pk -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 18 prec= 0 autoinc -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 20 prec= 0 tk -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 20 prec= 0 pk -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 20 prec= 0 autoinc -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 10 prec= 2 tk -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 10 prec= 2 pk -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 10 prec= 2 autoinc -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 9999999 prec= 0 tk -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 9999999 prec= 0 pk -> UUID NOT NULL PRIMARY KEY
+NUMBER len= 9999999 prec= 0 autoinc -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= -1 prec= -1 tk -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= -1 prec= -1 pk -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= -1 prec= -1 autoinc -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 0 prec= 0 tk -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 0 prec= 0 pk -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 0 prec= 0 autoinc -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 4 prec= 0 tk -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 4 prec= 0 pk -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 4 prec= 0 autoinc -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 9 prec= 0 tk -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 9 prec= 0 pk -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 9 prec= 0 autoinc -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 15 prec= 0 tk -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 15 prec= 0 pk -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 15 prec= 0 autoinc -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 18 prec= 0 tk -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 18 prec= 0 pk -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 18 prec= 0 autoinc -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 20 prec= 0 tk -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 20 prec= 0 pk -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 20 prec= 0 autoinc -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 10 prec= 2 tk -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 10 prec= 2 pk -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 10 prec= 2 autoinc -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 9999999 prec= 0 tk -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 9999999 prec= 0 pk -> UUID NOT NULL PRIMARY KEY
+BIGNUMBER len= 9999999 prec= 0 autoinc -> UUID NOT NULL PRIMARY KEY
+
+[alter]
+STRING len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL STRING
+STRING len= -1 prec= -1 modify -> ALTER TABLE TBL MODIFY COLUMN COL STRING
+STRING len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL STRING
+STRING len= 15 prec= 0 modify -> ALTER TABLE TBL MODIFY COLUMN COL STRING
+STRING len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL STRING
+STRING len= 10 prec= 2 modify -> ALTER TABLE TBL MODIFY COLUMN COL STRING
+INTEGER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL INT32
+INTEGER len= -1 prec= -1 modify -> ALTER TABLE TBL MODIFY COLUMN COL INT32
+INTEGER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL INT64
+INTEGER len= 15 prec= 0 modify -> ALTER TABLE TBL MODIFY COLUMN COL INT64
+INTEGER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL INT64
+INTEGER len= 10 prec= 2 modify -> ALTER TABLE TBL MODIFY COLUMN COL INT64
+NUMBER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL FLOAT64
+NUMBER len= -1 prec= -1 modify -> ALTER TABLE TBL MODIFY COLUMN COL FLOAT64
+NUMBER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL FLOAT64
+NUMBER len= 15 prec= 0 modify -> ALTER TABLE TBL MODIFY COLUMN COL FLOAT64
+NUMBER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL FLOAT64
+NUMBER len= 10 prec= 2 modify -> ALTER TABLE TBL MODIFY COLUMN COL FLOAT64
+BIGNUMBER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL DECIMAL(16,16)
+BIGNUMBER len= -1 prec= -1 modify -> ALTER TABLE TBL MODIFY COLUMN COL DECIMAL(16,16)
+BIGNUMBER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL DECIMAL(15,16)
+BIGNUMBER len= 15 prec= 0 modify -> ALTER TABLE TBL MODIFY COLUMN COL DECIMAL(15,16)
+BIGNUMBER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL DECIMAL(10,2)
+BIGNUMBER len= 10 prec= 2 modify -> ALTER TABLE TBL MODIFY COLUMN COL DECIMAL(10,2)
+DATE len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL DATE
+DATE len= -1 prec= -1 modify -> ALTER TABLE TBL MODIFY COLUMN COL DATE
+DATE len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL DATE
+DATE len= 15 prec= 0 modify -> ALTER TABLE TBL MODIFY COLUMN COL DATE
+DATE len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL DATE
+DATE len= 10 prec= 2 modify -> ALTER TABLE TBL MODIFY COLUMN COL DATE
+TIMESTAMP len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL DATETIME
+TIMESTAMP len= -1 prec= -1 modify -> ALTER TABLE TBL MODIFY COLUMN COL DATETIME
+TIMESTAMP len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL DATETIME
+TIMESTAMP len= 15 prec= 0 modify -> ALTER TABLE TBL MODIFY COLUMN COL DATETIME
+TIMESTAMP len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL DATETIME
+TIMESTAMP len= 10 prec= 2 modify -> ALTER TABLE TBL MODIFY COLUMN COL DATETIME
+BOOLEAN len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL UINT8
+BOOLEAN len= -1 prec= -1 modify -> ALTER TABLE TBL MODIFY COLUMN COL UINT8
+BOOLEAN len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL UINT8
+BOOLEAN len= 15 prec= 0 modify -> ALTER TABLE TBL MODIFY COLUMN COL UINT8
+BOOLEAN len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL UINT8
+BOOLEAN len= 10 prec= 2 modify -> ALTER TABLE TBL MODIFY COLUMN COL UINT8
+BINARY len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL UNSUPPORTED
+BINARY len= -1 prec= -1 modify -> ALTER TABLE TBL MODIFY COLUMN COL UNSUPPORTED
+BINARY len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL UNSUPPORTED
+BINARY len= 15 prec= 0 modify -> ALTER TABLE TBL MODIFY COLUMN COL UNSUPPORTED
+BINARY len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL UNSUPPORTED
+BINARY len= 10 prec= 2 modify -> ALTER TABLE TBL MODIFY COLUMN COL UNSUPPORTED
+INET len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL UNKNOWN
+INET len= -1 prec= -1 modify -> ALTER TABLE TBL MODIFY COLUMN COL UNKNOWN
+INET len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL UNKNOWN
+INET len= 15 prec= 0 modify -> ALTER TABLE TBL MODIFY COLUMN COL UNKNOWN
+INET len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL UNKNOWN
+INET len= 10 prec= 2 modify -> ALTER TABLE TBL MODIFY COLUMN COL UNKNOWN
+JSON len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL JSON
+JSON len= -1 prec= -1 modify -> ALTER TABLE TBL MODIFY COLUMN COL JSON
+JSON len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL JSON
+JSON len= 15 prec= 0 modify -> ALTER TABLE TBL MODIFY COLUMN COL JSON
+JSON len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL JSON
+JSON len= 10 prec= 2 modify -> ALTER TABLE TBL MODIFY COLUMN COL JSON
diff --git a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java b/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
index d138cf8a7f..c16b88975e 100644
--- a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
+++ b/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
@@ -27,6 +27,7 @@ import org.apache.hop.databases.postgresql.PostgreSqlDatabaseMeta;
type = "COCKROACHDB",
typeDescription = "CockroachDB",
image = "cockroachdb.svg",
- documentationUrl = "/database/databases/postgresql.html")
+ documentationUrl = "/database/databases/postgresql.html",
+ classLoaderGroup = "cockroachdb-db")
@GuiPlugin(id = "GUI-CockroachDatabaseMeta")
public class CockroachDatabaseMeta extends PostgreSqlDatabaseMeta implements IDatabase {}
diff --git a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java b/plugins/databases/cockroachdb/src/test/java/org/apache/hop/databases/cockroachdb/CockroachFieldDefinitionGoldenTest.java
similarity index 61%
copy from plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
copy to plugins/databases/cockroachdb/src/test/java/org/apache/hop/databases/cockroachdb/CockroachFieldDefinitionGoldenTest.java
index d138cf8a7f..016f594889 100644
--- a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
+++ b/plugins/databases/cockroachdb/src/test/java/org/apache/hop/databases/cockroachdb/CockroachFieldDefinitionGoldenTest.java
@@ -14,19 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package org.apache.hop.databases.cockroachdb;
-import org.apache.hop.core.database.DatabaseMetaPlugin;
import org.apache.hop.core.database.IDatabase;
-import org.apache.hop.core.gui.plugin.GuiPlugin;
-import org.apache.hop.databases.postgresql.PostgreSqlDatabaseMeta;
+import org.apache.hop.junit.database.BaseFieldDefinitionGoldenTest;
-/** Contains PostgreSQL specific information through static final members */
-@DatabaseMetaPlugin(
- type = "COCKROACHDB",
- typeDescription = "CockroachDB",
- image = "cockroachdb.svg",
- documentationUrl = "/database/databases/postgresql.html")
-@GuiPlugin(id = "GUI-CockroachDatabaseMeta")
-public class CockroachDatabaseMeta extends PostgreSqlDatabaseMeta implements IDatabase {}
+class CockroachFieldDefinitionGoldenTest extends BaseFieldDefinitionGoldenTest {
+ @Override
+ protected IDatabase createDatabase() {
+ return new CockroachDatabaseMeta();
+ }
+}
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
new file mode 100644
index 0000000000..441a311844
--- /dev/null
+++ b/plugins/databases/cockroachdb/src/test/resources/org/apache/hop/databases/cockroachdb/CockroachFieldDefinitionGoldenTest.txt
@@ -0,0 +1,240 @@
+# org.apache.hop.databases.cockroachdb.CockroachDatabaseMeta
+# Generated by CockroachFieldDefinitionGoldenTest. Do not hand-edit.
+# Records current behaviour including known bugs. See the class javadoc.
+
+[plain]
+STRING len= -1 prec= -1 - -> TEXT
+STRING len= 0 prec= 0 - -> TEXT
+STRING len= 4 prec= 0 - -> VARCHAR(4)
+STRING len= 9 prec= 0 - -> VARCHAR(9)
+STRING len= 15 prec= 0 - -> VARCHAR(15)
+STRING len= 18 prec= 0 - -> VARCHAR(18)
+STRING len= 20 prec= 0 - -> VARCHAR(20)
+STRING len= 10 prec= 2 - -> VARCHAR(10)
+STRING len= 9999999 prec= 0 - -> TEXT
+INTEGER len= -1 prec= -1 - -> DOUBLE PRECISION
+INTEGER len= 0 prec= 0 - -> DOUBLE PRECISION
+INTEGER len= 4 prec= 0 - -> SMALLINT
+INTEGER len= 9 prec= 0 - -> INTEGER
+INTEGER len= 15 prec= 0 - -> BIGINT
+INTEGER len= 18 prec= 0 - -> BIGINT
+INTEGER len= 20 prec= 0 - -> NUMERIC(20, 0)
+INTEGER len= 10 prec= 2 - -> BIGINT
+INTEGER len= 9999999 prec= 0 - -> NUMERIC(1073741824, 0)
+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= 15 prec= 0 - -> BIGINT
+NUMBER len= 18 prec= 0 - -> BIGINT
+NUMBER len= 20 prec= 0 - -> NUMERIC(20, 0)
+NUMBER len= 10 prec= 2 - -> NUMERIC(12, 2)
+NUMBER len= 9999999 prec= 0 - -> NUMERIC(1073741824, 0)
+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= 15 prec= 0 - -> BIGINT
+BIGNUMBER len= 18 prec= 0 - -> BIGINT
+BIGNUMBER len= 20 prec= 0 - -> NUMERIC(20, 0)
+BIGNUMBER len= 10 prec= 2 - -> NUMERIC(12, 2)
+BIGNUMBER len= 9999999 prec= 0 - -> NUMERIC(1073741824, 0)
+DATE len= -1 prec= -1 - -> TIMESTAMP
+DATE len= 0 prec= 0 - -> TIMESTAMP
+DATE len= 4 prec= 0 - -> TIMESTAMP
+DATE len= 9 prec= 0 - -> TIMESTAMP
+DATE len= 15 prec= 0 - -> TIMESTAMP
+DATE len= 18 prec= 0 - -> TIMESTAMP
+DATE len= 20 prec= 0 - -> TIMESTAMP
+DATE len= 10 prec= 2 - -> TIMESTAMP
+DATE len= 9999999 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= -1 prec= -1 - -> TIMESTAMP
+TIMESTAMP len= 0 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 4 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 9 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 15 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 18 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 20 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 10 prec= 2 - -> TIMESTAMP
+TIMESTAMP len= 9999999 prec= 0 - -> TIMESTAMP
+BOOLEAN len= -1 prec= -1 - -> CHAR(1)
+BOOLEAN len= 0 prec= 0 - -> CHAR(1)
+BOOLEAN len= 4 prec= 0 - -> CHAR(1)
+BOOLEAN len= 9 prec= 0 - -> CHAR(1)
+BOOLEAN len= 15 prec= 0 - -> CHAR(1)
+BOOLEAN len= 18 prec= 0 - -> CHAR(1)
+BOOLEAN len= 20 prec= 0 - -> CHAR(1)
+BOOLEAN len= 10 prec= 2 - -> CHAR(1)
+BOOLEAN len= 9999999 prec= 0 - -> CHAR(1)
+BINARY len= -1 prec= -1 - -> BYTEA
+BINARY len= 0 prec= 0 - -> BYTEA
+BINARY len= 4 prec= 0 - -> BYTEA
+BINARY len= 9 prec= 0 - -> BYTEA
+BINARY len= 15 prec= 0 - -> BYTEA
+BINARY len= 18 prec= 0 - -> BYTEA
+BINARY len= 20 prec= 0 - -> BYTEA
+BINARY len= 10 prec= 2 - -> BYTEA
+BINARY len= 9999999 prec= 0 - -> BYTEA
+INET len= -1 prec= -1 - -> INET
+INET len= 0 prec= 0 - -> INET
+INET len= 4 prec= 0 - -> INET
+INET len= 9 prec= 0 - -> INET
+INET len= 15 prec= 0 - -> INET
+INET len= 18 prec= 0 - -> INET
+INET len= 20 prec= 0 - -> INET
+INET len= 10 prec= 2 - -> INET
+INET len= 9999999 prec= 0 - -> INET
+JSON len= -1 prec= -1 - -> JSONB
+JSON len= 0 prec= 0 - -> JSONB
+JSON len= 4 prec= 0 - -> JSONB
+JSON len= 9 prec= 0 - -> JSONB
+JSON len= 15 prec= 0 - -> JSONB
+JSON len= 18 prec= 0 - -> JSONB
+JSON len= 20 prec= 0 - -> JSONB
+JSON len= 10 prec= 2 - -> JSONB
+JSON len= 9999999 prec= 0 - -> JSONB
+
+[keys]
+INTEGER len= -1 prec= -1 tk -> BIGSERIAL
+INTEGER len= -1 prec= -1 pk -> BIGSERIAL
+INTEGER len= -1 prec= -1 autoinc -> BIGSERIAL
+INTEGER len= 0 prec= 0 tk -> BIGSERIAL
+INTEGER len= 0 prec= 0 pk -> BIGSERIAL
+INTEGER len= 0 prec= 0 autoinc -> BIGSERIAL
+INTEGER len= 4 prec= 0 tk -> BIGSERIAL
+INTEGER len= 4 prec= 0 pk -> BIGSERIAL
+INTEGER len= 4 prec= 0 autoinc -> BIGSERIAL
+INTEGER len= 9 prec= 0 tk -> BIGSERIAL
+INTEGER len= 9 prec= 0 pk -> BIGSERIAL
+INTEGER len= 9 prec= 0 autoinc -> BIGSERIAL
+INTEGER len= 15 prec= 0 tk -> BIGSERIAL
+INTEGER len= 15 prec= 0 pk -> BIGSERIAL
+INTEGER len= 15 prec= 0 autoinc -> BIGSERIAL
+INTEGER len= 18 prec= 0 tk -> BIGSERIAL
+INTEGER len= 18 prec= 0 pk -> BIGSERIAL
+INTEGER len= 18 prec= 0 autoinc -> BIGSERIAL
+INTEGER len= 20 prec= 0 tk -> BIGSERIAL
+INTEGER len= 20 prec= 0 pk -> BIGSERIAL
+INTEGER len= 20 prec= 0 autoinc -> BIGSERIAL
+INTEGER len= 10 prec= 2 tk -> BIGSERIAL
+INTEGER len= 10 prec= 2 pk -> BIGSERIAL
+INTEGER len= 10 prec= 2 autoinc -> BIGSERIAL
+INTEGER len= 9999999 prec= 0 tk -> BIGSERIAL
+INTEGER len= 9999999 prec= 0 pk -> BIGSERIAL
+INTEGER len= 9999999 prec= 0 autoinc -> BIGSERIAL
+NUMBER len= -1 prec= -1 tk -> BIGSERIAL
+NUMBER len= -1 prec= -1 pk -> BIGSERIAL
+NUMBER len= -1 prec= -1 autoinc -> BIGSERIAL
+NUMBER len= 0 prec= 0 tk -> BIGSERIAL
+NUMBER len= 0 prec= 0 pk -> BIGSERIAL
+NUMBER len= 0 prec= 0 autoinc -> BIGSERIAL
+NUMBER len= 4 prec= 0 tk -> BIGSERIAL
+NUMBER len= 4 prec= 0 pk -> BIGSERIAL
+NUMBER len= 4 prec= 0 autoinc -> BIGSERIAL
+NUMBER len= 9 prec= 0 tk -> BIGSERIAL
+NUMBER len= 9 prec= 0 pk -> BIGSERIAL
+NUMBER len= 9 prec= 0 autoinc -> BIGSERIAL
+NUMBER len= 15 prec= 0 tk -> BIGSERIAL
+NUMBER len= 15 prec= 0 pk -> BIGSERIAL
+NUMBER len= 15 prec= 0 autoinc -> BIGSERIAL
+NUMBER len= 18 prec= 0 tk -> BIGSERIAL
+NUMBER len= 18 prec= 0 pk -> BIGSERIAL
+NUMBER len= 18 prec= 0 autoinc -> BIGSERIAL
+NUMBER len= 20 prec= 0 tk -> BIGSERIAL
+NUMBER len= 20 prec= 0 pk -> BIGSERIAL
+NUMBER len= 20 prec= 0 autoinc -> BIGSERIAL
+NUMBER len= 10 prec= 2 tk -> BIGSERIAL
+NUMBER len= 10 prec= 2 pk -> BIGSERIAL
+NUMBER len= 10 prec= 2 autoinc -> BIGSERIAL
+NUMBER len= 9999999 prec= 0 tk -> BIGSERIAL
+NUMBER len= 9999999 prec= 0 pk -> BIGSERIAL
+NUMBER len= 9999999 prec= 0 autoinc -> BIGSERIAL
+BIGNUMBER len= -1 prec= -1 tk -> BIGSERIAL
+BIGNUMBER len= -1 prec= -1 pk -> BIGSERIAL
+BIGNUMBER len= -1 prec= -1 autoinc -> BIGSERIAL
+BIGNUMBER len= 0 prec= 0 tk -> BIGSERIAL
+BIGNUMBER len= 0 prec= 0 pk -> BIGSERIAL
+BIGNUMBER len= 0 prec= 0 autoinc -> BIGSERIAL
+BIGNUMBER len= 4 prec= 0 tk -> BIGSERIAL
+BIGNUMBER len= 4 prec= 0 pk -> BIGSERIAL
+BIGNUMBER len= 4 prec= 0 autoinc -> BIGSERIAL
+BIGNUMBER len= 9 prec= 0 tk -> BIGSERIAL
+BIGNUMBER len= 9 prec= 0 pk -> BIGSERIAL
+BIGNUMBER len= 9 prec= 0 autoinc -> BIGSERIAL
+BIGNUMBER len= 15 prec= 0 tk -> BIGSERIAL
+BIGNUMBER len= 15 prec= 0 pk -> BIGSERIAL
+BIGNUMBER len= 15 prec= 0 autoinc -> BIGSERIAL
+BIGNUMBER len= 18 prec= 0 tk -> BIGSERIAL
+BIGNUMBER len= 18 prec= 0 pk -> BIGSERIAL
+BIGNUMBER len= 18 prec= 0 autoinc -> BIGSERIAL
+BIGNUMBER len= 20 prec= 0 tk -> BIGSERIAL
+BIGNUMBER len= 20 prec= 0 pk -> BIGSERIAL
+BIGNUMBER len= 20 prec= 0 autoinc -> BIGSERIAL
+BIGNUMBER len= 10 prec= 2 tk -> BIGSERIAL
+BIGNUMBER len= 10 prec= 2 pk -> BIGSERIAL
+BIGNUMBER len= 10 prec= 2 autoinc -> BIGSERIAL
+BIGNUMBER len= 9999999 prec= 0 tk -> BIGSERIAL
+BIGNUMBER len= 9999999 prec= 0 pk -> BIGSERIAL
+BIGNUMBER len= 9999999 prec= 0 autoinc -> BIGSERIAL
+
+[alter]
+STRING len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL TEXT
+STRING len= -1 prec= -1 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL TEXT; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+STRING len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL VARCHAR(15)
+STRING len= 15 prec= 0 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL VARCHAR(15); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+STRING len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL VARCHAR(10)
+STRING len= 10 prec= 2 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL VARCHAR(10); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+INTEGER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL DOUBLE PRECISION
+INTEGER 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;
+INTEGER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL BIGINT
+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;
+INTEGER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL BIGINT
+INTEGER len= 10 prec= 2 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= 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;
+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;
+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= 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;
+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;
+DATE len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+DATE len= -1 prec= -1 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL TIMESTAMP; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+DATE len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+DATE len= 15 prec= 0 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL TIMESTAMP; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+DATE len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+DATE len= 10 prec= 2 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL TIMESTAMP; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+TIMESTAMP len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+TIMESTAMP len= -1 prec= -1 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL TIMESTAMP; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+TIMESTAMP len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+TIMESTAMP len= 15 prec= 0 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL TIMESTAMP; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+TIMESTAMP len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+TIMESTAMP len= 10 prec= 2 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL TIMESTAMP; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+BOOLEAN len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL CHAR(1)
+BOOLEAN len= -1 prec= -1 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL CHAR(1); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+BOOLEAN len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL CHAR(1)
+BOOLEAN len= 15 prec= 0 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL CHAR(1); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+BOOLEAN len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL CHAR(1)
+BOOLEAN len= 10 prec= 2 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL CHAR(1); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+BINARY len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL BYTEA
+BINARY len= -1 prec= -1 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL BYTEA; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+BINARY len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL BYTEA
+BINARY len= 15 prec= 0 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL BYTEA; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+BINARY len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL BYTEA
+BINARY len= 10 prec= 2 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL BYTEA; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+INET len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL INET
+INET len= -1 prec= -1 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL INET; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+INET len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL INET
+INET len= 15 prec= 0 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL INET; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+INET len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL INET
+INET len= 10 prec= 2 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL INET; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+JSON len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL JSONB
+JSON len= -1 prec= -1 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL JSONB; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+JSON len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL JSONB
+JSON len= 15 prec= 0 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL JSONB; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+JSON len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL JSONB
+JSON len= 10 prec= 2 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL JSONB; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
diff --git a/plugins/databases/cratedb/src/test/java/org/apache/hop/databases/cratedb/CrateDBDatabaseMetaTest.java b/plugins/databases/cratedb/src/test/java/org/apache/hop/databases/cratedb/CrateDBDatabaseMetaTest.java
index 8994881c9b..880f7abedb 100644
--- a/plugins/databases/cratedb/src/test/java/org/apache/hop/databases/cratedb/CrateDBDatabaseMetaTest.java
+++ b/plugins/databases/cratedb/src/test/java/org/apache/hop/databases/cratedb/CrateDBDatabaseMetaTest.java
@@ -271,8 +271,11 @@ class CrateDBDatabaseMetaTest {
"ALTER TABLE FOO ADD COLUMN BAR DOUBLE PRECISION",
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
+ // rules, inherited here from PostgreSQL. Before those were consulted here, this produced
+ // "BAR UNKNOWN".
assertEquals(
- "ALTER TABLE FOO ADD COLUMN BAR UNKNOWN",
+ "ALTER TABLE FOO ADD COLUMN BAR INET",
nativeMeta.getAddColumnStatement(
"FOO", new ValueMetaInternetAddress("BAR"), "", false, "", false));
diff --git a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java b/plugins/databases/cratedb/src/test/java/org/apache/hop/databases/cratedb/CrateDBFieldDefinitionGoldenTest.java
similarity index 58%
copy from plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
copy to plugins/databases/cratedb/src/test/java/org/apache/hop/databases/cratedb/CrateDBFieldDefinitionGoldenTest.java
index d138cf8a7f..00330ca49d 100644
--- a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
+++ b/plugins/databases/cratedb/src/test/java/org/apache/hop/databases/cratedb/CrateDBFieldDefinitionGoldenTest.java
@@ -14,19 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.hop.databases.cratedb;
-package org.apache.hop.databases.cockroachdb;
-
-import org.apache.hop.core.database.DatabaseMetaPlugin;
import org.apache.hop.core.database.IDatabase;
-import org.apache.hop.core.gui.plugin.GuiPlugin;
-import org.apache.hop.databases.postgresql.PostgreSqlDatabaseMeta;
+import org.apache.hop.junit.database.BaseFieldDefinitionGoldenTest;
-/** Contains PostgreSQL specific information through static final members */
-@DatabaseMetaPlugin(
- type = "COCKROACHDB",
- typeDescription = "CockroachDB",
- image = "cockroachdb.svg",
- documentationUrl = "/database/databases/postgresql.html")
-@GuiPlugin(id = "GUI-CockroachDatabaseMeta")
-public class CockroachDatabaseMeta extends PostgreSqlDatabaseMeta implements IDatabase {}
+class CrateDBFieldDefinitionGoldenTest extends BaseFieldDefinitionGoldenTest {
+ @Override
+ protected IDatabase createDatabase() {
+ return new CrateDBDatabaseMeta();
+ }
+}
diff --git a/plugins/databases/cratedb/src/test/resources/org/apache/hop/databases/cratedb/CrateDBFieldDefinitionGoldenTest.txt b/plugins/databases/cratedb/src/test/resources/org/apache/hop/databases/cratedb/CrateDBFieldDefinitionGoldenTest.txt
new file mode 100644
index 0000000000..27f8d9ebc1
--- /dev/null
+++ b/plugins/databases/cratedb/src/test/resources/org/apache/hop/databases/cratedb/CrateDBFieldDefinitionGoldenTest.txt
@@ -0,0 +1,240 @@
+# org.apache.hop.databases.cratedb.CrateDBDatabaseMeta
+# Generated by CrateDBFieldDefinitionGoldenTest. Do not hand-edit.
+# Records current behaviour including known bugs. See the class javadoc.
+
+[plain]
+STRING len= -1 prec= -1 - -> TEXT
+STRING len= 0 prec= 0 - -> TEXT
+STRING len= 4 prec= 0 - -> VARCHAR(4)
+STRING len= 9 prec= 0 - -> VARCHAR(9)
+STRING len= 15 prec= 0 - -> VARCHAR(15)
+STRING len= 18 prec= 0 - -> VARCHAR(18)
+STRING len= 20 prec= 0 - -> VARCHAR(20)
+STRING len= 10 prec= 2 - -> VARCHAR(10)
+STRING len= 9999999 prec= 0 - -> TEXT
+INTEGER len= -1 prec= -1 - -> SMALLINT
+INTEGER len= 0 prec= 0 - -> SMALLINT
+INTEGER len= 4 prec= 0 - -> SMALLINT
+INTEGER len= 9 prec= 0 - -> INTEGER
+INTEGER len= 15 prec= 0 - -> BIGINT
+INTEGER len= 18 prec= 0 - -> BIGINT
+INTEGER len= 20 prec= 0 - -> BIGINT
+INTEGER len= 10 prec= 2 - -> BIGINT
+INTEGER len= 9999999 prec= 0 - -> BIGINT
+NUMBER len= -1 prec= -1 - -> DOUBLE PRECISION
+NUMBER len= 0 prec= 0 - -> DOUBLE PRECISION
+NUMBER len= 4 prec= 0 - -> DOUBLE PRECISION
+NUMBER len= 9 prec= 0 - -> DOUBLE PRECISION
+NUMBER len= 15 prec= 0 - -> DOUBLE PRECISION
+NUMBER len= 18 prec= 0 - -> DOUBLE PRECISION
+NUMBER len= 20 prec= 0 - -> DOUBLE PRECISION
+NUMBER len= 10 prec= 2 - -> DOUBLE PRECISION
+NUMBER len= 9999999 prec= 0 - -> DOUBLE PRECISION
+BIGNUMBER len= -1 prec= -1 - -> DOUBLE PRECISION
+BIGNUMBER len= 0 prec= 0 - -> DOUBLE PRECISION
+BIGNUMBER len= 4 prec= 0 - -> DOUBLE PRECISION
+BIGNUMBER len= 9 prec= 0 - -> DOUBLE PRECISION
+BIGNUMBER len= 15 prec= 0 - -> DOUBLE PRECISION
+BIGNUMBER len= 18 prec= 0 - -> DOUBLE PRECISION
+BIGNUMBER len= 20 prec= 0 - -> DOUBLE PRECISION
+BIGNUMBER len= 10 prec= 2 - -> DOUBLE PRECISION
+BIGNUMBER len= 9999999 prec= 0 - -> DOUBLE PRECISION
+DATE len= -1 prec= -1 - -> TIMESTAMP
+DATE len= 0 prec= 0 - -> TIMESTAMP
+DATE len= 4 prec= 0 - -> TIMESTAMP
+DATE len= 9 prec= 0 - -> TIMESTAMP
+DATE len= 15 prec= 0 - -> TIMESTAMP
+DATE len= 18 prec= 0 - -> TIMESTAMP
+DATE len= 20 prec= 0 - -> TIMESTAMP
+DATE len= 10 prec= 2 - -> TIMESTAMP
+DATE len= 9999999 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= -1 prec= -1 - -> TIMESTAMP
+TIMESTAMP len= 0 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 4 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 9 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 15 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 18 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 20 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 10 prec= 2 - -> TIMESTAMP
+TIMESTAMP len= 9999999 prec= 0 - -> TIMESTAMP
+BOOLEAN len= -1 prec= -1 - -> CHAR(1)
+BOOLEAN len= 0 prec= 0 - -> CHAR(1)
+BOOLEAN len= 4 prec= 0 - -> CHAR(1)
+BOOLEAN len= 9 prec= 0 - -> CHAR(1)
+BOOLEAN len= 15 prec= 0 - -> CHAR(1)
+BOOLEAN len= 18 prec= 0 - -> CHAR(1)
+BOOLEAN len= 20 prec= 0 - -> CHAR(1)
+BOOLEAN len= 10 prec= 2 - -> CHAR(1)
+BOOLEAN len= 9999999 prec= 0 - -> CHAR(1)
+BINARY len= -1 prec= -1 - -> BYTEA
+BINARY len= 0 prec= 0 - -> BYTEA
+BINARY len= 4 prec= 0 - -> BYTEA
+BINARY len= 9 prec= 0 - -> BYTEA
+BINARY len= 15 prec= 0 - -> BYTEA
+BINARY len= 18 prec= 0 - -> BYTEA
+BINARY len= 20 prec= 0 - -> BYTEA
+BINARY len= 10 prec= 2 - -> BYTEA
+BINARY len= 9999999 prec= 0 - -> BYTEA
+INET len= -1 prec= -1 - -> INET
+INET len= 0 prec= 0 - -> INET
+INET len= 4 prec= 0 - -> INET
+INET len= 9 prec= 0 - -> INET
+INET len= 15 prec= 0 - -> INET
+INET len= 18 prec= 0 - -> INET
+INET len= 20 prec= 0 - -> INET
+INET len= 10 prec= 2 - -> INET
+INET len= 9999999 prec= 0 - -> INET
+JSON len= -1 prec= -1 - -> JSONB
+JSON len= 0 prec= 0 - -> JSONB
+JSON len= 4 prec= 0 - -> JSONB
+JSON len= 9 prec= 0 - -> JSONB
+JSON len= 15 prec= 0 - -> JSONB
+JSON len= 18 prec= 0 - -> JSONB
+JSON len= 20 prec= 0 - -> JSONB
+JSON len= 10 prec= 2 - -> JSONB
+JSON len= 9999999 prec= 0 - -> JSONB
+
+[keys]
+INTEGER len= -1 prec= -1 tk -> BIGSERIAL
+INTEGER len= -1 prec= -1 pk -> BIGSERIAL
+INTEGER len= -1 prec= -1 autoinc -> BIGSERIAL
+INTEGER len= 0 prec= 0 tk -> BIGSERIAL
+INTEGER len= 0 prec= 0 pk -> BIGSERIAL
+INTEGER len= 0 prec= 0 autoinc -> BIGSERIAL
+INTEGER len= 4 prec= 0 tk -> BIGSERIAL
+INTEGER len= 4 prec= 0 pk -> BIGSERIAL
+INTEGER len= 4 prec= 0 autoinc -> BIGSERIAL
+INTEGER len= 9 prec= 0 tk -> BIGSERIAL
+INTEGER len= 9 prec= 0 pk -> BIGSERIAL
+INTEGER len= 9 prec= 0 autoinc -> BIGSERIAL
+INTEGER len= 15 prec= 0 tk -> BIGSERIAL
+INTEGER len= 15 prec= 0 pk -> BIGSERIAL
+INTEGER len= 15 prec= 0 autoinc -> BIGSERIAL
+INTEGER len= 18 prec= 0 tk -> BIGSERIAL
+INTEGER len= 18 prec= 0 pk -> BIGSERIAL
+INTEGER len= 18 prec= 0 autoinc -> BIGSERIAL
+INTEGER len= 20 prec= 0 tk -> BIGSERIAL
+INTEGER len= 20 prec= 0 pk -> BIGSERIAL
+INTEGER len= 20 prec= 0 autoinc -> BIGSERIAL
+INTEGER len= 10 prec= 2 tk -> BIGSERIAL
+INTEGER len= 10 prec= 2 pk -> BIGSERIAL
+INTEGER len= 10 prec= 2 autoinc -> BIGSERIAL
+INTEGER len= 9999999 prec= 0 tk -> BIGSERIAL
+INTEGER len= 9999999 prec= 0 pk -> BIGSERIAL
+INTEGER len= 9999999 prec= 0 autoinc -> BIGSERIAL
+NUMBER len= -1 prec= -1 tk -> BIGSERIAL
+NUMBER len= -1 prec= -1 pk -> BIGSERIAL
+NUMBER len= -1 prec= -1 autoinc -> BIGSERIAL
+NUMBER len= 0 prec= 0 tk -> BIGSERIAL
+NUMBER len= 0 prec= 0 pk -> BIGSERIAL
+NUMBER len= 0 prec= 0 autoinc -> BIGSERIAL
+NUMBER len= 4 prec= 0 tk -> BIGSERIAL
+NUMBER len= 4 prec= 0 pk -> BIGSERIAL
+NUMBER len= 4 prec= 0 autoinc -> BIGSERIAL
+NUMBER len= 9 prec= 0 tk -> BIGSERIAL
+NUMBER len= 9 prec= 0 pk -> BIGSERIAL
+NUMBER len= 9 prec= 0 autoinc -> BIGSERIAL
+NUMBER len= 15 prec= 0 tk -> BIGSERIAL
+NUMBER len= 15 prec= 0 pk -> BIGSERIAL
+NUMBER len= 15 prec= 0 autoinc -> BIGSERIAL
+NUMBER len= 18 prec= 0 tk -> BIGSERIAL
+NUMBER len= 18 prec= 0 pk -> BIGSERIAL
+NUMBER len= 18 prec= 0 autoinc -> BIGSERIAL
+NUMBER len= 20 prec= 0 tk -> BIGSERIAL
+NUMBER len= 20 prec= 0 pk -> BIGSERIAL
+NUMBER len= 20 prec= 0 autoinc -> BIGSERIAL
+NUMBER len= 10 prec= 2 tk -> BIGSERIAL
+NUMBER len= 10 prec= 2 pk -> BIGSERIAL
+NUMBER len= 10 prec= 2 autoinc -> BIGSERIAL
+NUMBER len= 9999999 prec= 0 tk -> BIGSERIAL
+NUMBER len= 9999999 prec= 0 pk -> BIGSERIAL
+NUMBER len= 9999999 prec= 0 autoinc -> BIGSERIAL
+BIGNUMBER len= -1 prec= -1 tk -> BIGSERIAL
+BIGNUMBER len= -1 prec= -1 pk -> BIGSERIAL
+BIGNUMBER len= -1 prec= -1 autoinc -> BIGSERIAL
+BIGNUMBER len= 0 prec= 0 tk -> BIGSERIAL
+BIGNUMBER len= 0 prec= 0 pk -> BIGSERIAL
+BIGNUMBER len= 0 prec= 0 autoinc -> BIGSERIAL
+BIGNUMBER len= 4 prec= 0 tk -> BIGSERIAL
+BIGNUMBER len= 4 prec= 0 pk -> BIGSERIAL
+BIGNUMBER len= 4 prec= 0 autoinc -> BIGSERIAL
+BIGNUMBER len= 9 prec= 0 tk -> BIGSERIAL
+BIGNUMBER len= 9 prec= 0 pk -> BIGSERIAL
+BIGNUMBER len= 9 prec= 0 autoinc -> BIGSERIAL
+BIGNUMBER len= 15 prec= 0 tk -> BIGSERIAL
+BIGNUMBER len= 15 prec= 0 pk -> BIGSERIAL
+BIGNUMBER len= 15 prec= 0 autoinc -> BIGSERIAL
+BIGNUMBER len= 18 prec= 0 tk -> BIGSERIAL
+BIGNUMBER len= 18 prec= 0 pk -> BIGSERIAL
+BIGNUMBER len= 18 prec= 0 autoinc -> BIGSERIAL
+BIGNUMBER len= 20 prec= 0 tk -> BIGSERIAL
+BIGNUMBER len= 20 prec= 0 pk -> BIGSERIAL
+BIGNUMBER len= 20 prec= 0 autoinc -> BIGSERIAL
+BIGNUMBER len= 10 prec= 2 tk -> BIGSERIAL
+BIGNUMBER len= 10 prec= 2 pk -> BIGSERIAL
+BIGNUMBER len= 10 prec= 2 autoinc -> BIGSERIAL
+BIGNUMBER len= 9999999 prec= 0 tk -> BIGSERIAL
+BIGNUMBER len= 9999999 prec= 0 pk -> BIGSERIAL
+BIGNUMBER len= 9999999 prec= 0 autoinc -> BIGSERIAL
+
+[alter]
+STRING len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL TEXT
+STRING len= -1 prec= -1 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL TEXT; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+STRING len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL VARCHAR(15)
+STRING len= 15 prec= 0 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL VARCHAR(15); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+STRING len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL VARCHAR(10)
+STRING len= 10 prec= 2 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL VARCHAR(10); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+INTEGER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL SMALLINT
+INTEGER len= -1 prec= -1 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL SMALLINT; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+INTEGER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL BIGINT
+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;
+INTEGER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL BIGINT
+INTEGER len= 10 prec= 2 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= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL DOUBLE PRECISION
+NUMBER len= 15 prec= 0 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 DOUBLE PRECISION
+NUMBER len= 10 prec= 2 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= -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= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL DOUBLE PRECISION
+BIGNUMBER len= 15 prec= 0 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 DOUBLE PRECISION
+BIGNUMBER len= 10 prec= 2 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;
+DATE len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+DATE len= -1 prec= -1 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL TIMESTAMP; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+DATE len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+DATE len= 15 prec= 0 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL TIMESTAMP; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+DATE len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+DATE len= 10 prec= 2 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL TIMESTAMP; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+TIMESTAMP len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+TIMESTAMP len= -1 prec= -1 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL TIMESTAMP; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+TIMESTAMP len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+TIMESTAMP len= 15 prec= 0 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL TIMESTAMP; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+TIMESTAMP len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+TIMESTAMP len= 10 prec= 2 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL TIMESTAMP; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+BOOLEAN len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL CHAR(1)
+BOOLEAN len= -1 prec= -1 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL CHAR(1); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+BOOLEAN len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL CHAR(1)
+BOOLEAN len= 15 prec= 0 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL CHAR(1); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+BOOLEAN len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL CHAR(1)
+BOOLEAN len= 10 prec= 2 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL CHAR(1); UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+BINARY len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL BYTEA
+BINARY len= -1 prec= -1 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL BYTEA; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+BINARY len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL BYTEA
+BINARY len= 15 prec= 0 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL BYTEA; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+BINARY len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL BYTEA
+BINARY len= 10 prec= 2 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL BYTEA; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+INET len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL INET
+INET len= -1 prec= -1 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL INET; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+INET len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL INET
+INET len= 15 prec= 0 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL INET; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+INET len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL INET
+INET len= 10 prec= 2 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL INET; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+JSON len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL JSONB
+JSON len= -1 prec= -1 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL JSONB; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+JSON len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL JSONB
+JSON len= 15 prec= 0 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL JSONB; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
+JSON len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL JSONB
+JSON len= 10 prec= 2 modify -> ALTER TABLE TBL ADD COLUMN COL_KTL JSONB; UPDATE TBL SET COL_KTL=COL; ALTER TABLE TBL DROP COLUMN COL; ALTER TABLE TBL RENAME COL_KTL TO COL;
diff --git a/plugins/databases/db2/src/main/java/org/apache/hop/databases/db2/DB2DatabaseMeta.java b/plugins/databases/db2/src/main/java/org/apache/hop/databases/db2/DB2DatabaseMeta.java
index 28a41317f7..9436a8ccf9 100644
--- a/plugins/databases/db2/src/main/java/org/apache/hop/databases/db2/DB2DatabaseMeta.java
+++ b/plugins/databases/db2/src/main/java/org/apache/hop/databases/db2/DB2DatabaseMeta.java
@@ -23,6 +23,7 @@ import org.apache.hop.core.database.DatabaseMeta;
import org.apache.hop.core.database.DatabaseMetaPlugin;
import org.apache.hop.core.database.DriverDownload;
import org.apache.hop.core.database.IDatabase;
+import org.apache.hop.core.database.types.ColumnContext;
import org.apache.hop.core.gui.plugin.GuiPlugin;
import org.apache.hop.core.row.IValueMeta;
@@ -31,7 +32,8 @@ import org.apache.hop.core.row.IValueMeta;
type = "DB2",
typeDescription = "DB2",
image = "db2.svg",
- documentationUrl = "/database/databases/db2.html")
+ documentationUrl = "/database/databases/db2.html",
+ classLoaderGroup = "db2-db")
@GuiPlugin(id = "GUI-DB2DatabaseMeta")
public class DB2DatabaseMeta extends BaseDatabaseMeta implements IDatabase {
@@ -113,7 +115,7 @@ public class DB2DatabaseMeta extends BaseDatabaseMeta implements IDatabase {
return ALTER_TABLE
+ tableName
+ " ADD COLUMN "
- + getFieldDefinition(v, tk, pk, useAutoinc, true, false);
+ + getColumnDefinition(v, tk, pk, useAutoinc, true, false, ColumnContext.Purpose.ADD_COLUMN);
}
/**
@@ -153,7 +155,8 @@ public class DB2DatabaseMeta extends BaseDatabaseMeta implements IDatabase {
ALTER_TABLE
+ tableName
+ " ADD COLUMN "
- + getFieldDefinition(v, tk, pk, useAutoinc, true, false);
+ + getColumnDefinition(
+ v, tk, pk, useAutoinc, true, false, ColumnContext.Purpose.MODIFY_COLUMN);
return retval;
}
diff --git a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java b/plugins/databases/db2/src/test/java/org/apache/hop/databases/db2/DB2FieldDefinitionGoldenTest.java
similarity index 58%
copy from plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
copy to plugins/databases/db2/src/test/java/org/apache/hop/databases/db2/DB2FieldDefinitionGoldenTest.java
index d138cf8a7f..99c8ad7444 100644
--- a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
+++ b/plugins/databases/db2/src/test/java/org/apache/hop/databases/db2/DB2FieldDefinitionGoldenTest.java
@@ -14,19 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.hop.databases.db2;
-package org.apache.hop.databases.cockroachdb;
-
-import org.apache.hop.core.database.DatabaseMetaPlugin;
import org.apache.hop.core.database.IDatabase;
-import org.apache.hop.core.gui.plugin.GuiPlugin;
-import org.apache.hop.databases.postgresql.PostgreSqlDatabaseMeta;
+import org.apache.hop.junit.database.BaseFieldDefinitionGoldenTest;
-/** Contains PostgreSQL specific information through static final members */
-@DatabaseMetaPlugin(
- type = "COCKROACHDB",
- typeDescription = "CockroachDB",
- image = "cockroachdb.svg",
- documentationUrl = "/database/databases/postgresql.html")
-@GuiPlugin(id = "GUI-CockroachDatabaseMeta")
-public class CockroachDatabaseMeta extends PostgreSqlDatabaseMeta implements IDatabase {}
+class DB2FieldDefinitionGoldenTest extends BaseFieldDefinitionGoldenTest {
+ @Override
+ protected IDatabase createDatabase() {
+ return new DB2DatabaseMeta();
+ }
+}
diff --git a/plugins/databases/db2/src/test/resources/org/apache/hop/databases/db2/DB2FieldDefinitionGoldenTest.txt b/plugins/databases/db2/src/test/resources/org/apache/hop/databases/db2/DB2FieldDefinitionGoldenTest.txt
new file mode 100644
index 0000000000..6fa4fd8784
--- /dev/null
+++ b/plugins/databases/db2/src/test/resources/org/apache/hop/databases/db2/DB2FieldDefinitionGoldenTest.txt
@@ -0,0 +1,240 @@
+# org.apache.hop.databases.db2.DB2DatabaseMeta
+# Generated by DB2FieldDefinitionGoldenTest. Do not hand-edit.
+# Records current behaviour including known bugs. See the class javadoc.
+
+[plain]
+STRING len= -1 prec= -1 - -> VARCHAR()
+STRING len= 0 prec= 0 - -> VARCHAR()
+STRING len= 4 prec= 0 - -> VARCHAR(4)
+STRING len= 9 prec= 0 - -> VARCHAR(9)
+STRING len= 15 prec= 0 - -> VARCHAR(15)
+STRING len= 18 prec= 0 - -> VARCHAR(18)
+STRING len= 20 prec= 0 - -> VARCHAR(20)
+STRING len= 10 prec= 2 - -> VARCHAR(10)
+STRING len= 9999999 prec= 0 - -> CLOB
+INTEGER len= -1 prec= -1 - -> SMALLINT
+INTEGER len= 0 prec= 0 - -> SMALLINT
+INTEGER len= 4 prec= 0 - -> SMALLINT
+INTEGER len= 9 prec= 0 - -> INTEGER
+INTEGER len= 15 prec= 0 - -> BIGINT
+INTEGER len= 18 prec= 0 - -> BIGINT
+INTEGER len= 20 prec= 0 - -> DECIMAL(20)
+INTEGER len= 10 prec= 2 - -> BIGINT
+INTEGER len= 9999999 prec= 0 - -> DECIMAL(9999999)
+NUMBER len= -1 prec= -1 - -> DOUBLE
+NUMBER len= 0 prec= 0 - -> DOUBLE
+NUMBER len= 4 prec= 0 - -> DOUBLE
+NUMBER len= 9 prec= 0 - -> DOUBLE
+NUMBER len= 15 prec= 0 - -> DOUBLE
+NUMBER len= 18 prec= 0 - -> DOUBLE
+NUMBER len= 20 prec= 0 - -> DOUBLE
+NUMBER len= 10 prec= 2 - -> DOUBLE
+NUMBER len= 9999999 prec= 0 - -> DOUBLE
+BIGNUMBER len= -1 prec= -1 - -> DECIMAL(16,16)
+BIGNUMBER len= 0 prec= 0 - -> DECIMAL(16,16)
+BIGNUMBER len= 4 prec= 0 - -> DECIMAL(4,16)
+BIGNUMBER len= 9 prec= 0 - -> DECIMAL(9,16)
+BIGNUMBER len= 15 prec= 0 - -> DECIMAL(15,16)
+BIGNUMBER len= 18 prec= 0 - -> DECIMAL(18,16)
+BIGNUMBER len= 20 prec= 0 - -> DECIMAL(20,16)
+BIGNUMBER len= 10 prec= 2 - -> DECIMAL(10,2)
+BIGNUMBER len= 9999999 prec= 0 - -> DECIMAL(9999999,16)
+DATE len= -1 prec= -1 - -> TIMESTAMP
+DATE len= 0 prec= 0 - -> TIMESTAMP
+DATE len= 4 prec= 0 - -> TIMESTAMP
+DATE len= 9 prec= 0 - -> TIMESTAMP
+DATE len= 15 prec= 0 - -> TIMESTAMP
+DATE len= 18 prec= 0 - -> TIMESTAMP
+DATE len= 20 prec= 0 - -> TIMESTAMP
+DATE len= 10 prec= 2 - -> TIMESTAMP
+DATE len= 9999999 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= -1 prec= -1 - -> TIMESTAMP
+TIMESTAMP len= 0 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 4 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 9 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 15 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 18 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 20 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 10 prec= 2 - -> TIMESTAMP
+TIMESTAMP len= 9999999 prec= 0 - -> TIMESTAMP
+BOOLEAN len= -1 prec= -1 - -> CHARACTER(1)
+BOOLEAN len= 0 prec= 0 - -> CHARACTER(1)
+BOOLEAN len= 4 prec= 0 - -> CHARACTER(1)
+BOOLEAN len= 9 prec= 0 - -> CHARACTER(1)
+BOOLEAN len= 15 prec= 0 - -> CHARACTER(1)
+BOOLEAN len= 18 prec= 0 - -> CHARACTER(1)
+BOOLEAN len= 20 prec= 0 - -> CHARACTER(1)
+BOOLEAN len= 10 prec= 2 - -> CHARACTER(1)
+BOOLEAN len= 9999999 prec= 0 - -> CHARACTER(1)
+BINARY len= -1 prec= -1 - -> BLOB
+BINARY len= 0 prec= 0 - -> BLOB
+BINARY len= 4 prec= 0 - -> CHAR(4) FOR BIT DATA
+BINARY len= 9 prec= 0 - -> CHAR(9) FOR BIT DATA
+BINARY len= 15 prec= 0 - -> CHAR(15) FOR BIT DATA
+BINARY len= 18 prec= 0 - -> CHAR(18) FOR BIT DATA
+BINARY len= 20 prec= 0 - -> CHAR(20) FOR BIT DATA
+BINARY len= 10 prec= 2 - -> CHAR(10) FOR BIT DATA
+BINARY len= 9999999 prec= 0 - -> BLOB(9999999)
+INET len= -1 prec= -1 - -> UNKNOWN
+INET len= 0 prec= 0 - -> UNKNOWN
+INET len= 4 prec= 0 - -> UNKNOWN
+INET len= 9 prec= 0 - -> UNKNOWN
+INET len= 15 prec= 0 - -> UNKNOWN
+INET len= 18 prec= 0 - -> UNKNOWN
+INET len= 20 prec= 0 - -> UNKNOWN
+INET len= 10 prec= 2 - -> UNKNOWN
+INET len= 9999999 prec= 0 - -> UNKNOWN
+JSON len= -1 prec= -1 - -> JSON
+JSON len= 0 prec= 0 - -> JSON
+JSON len= 4 prec= 0 - -> JSON
+JSON len= 9 prec= 0 - -> JSON
+JSON len= 15 prec= 0 - -> JSON
+JSON len= 18 prec= 0 - -> JSON
+JSON len= 20 prec= 0 - -> JSON
+JSON len= 10 prec= 2 - -> JSON
+JSON len= 9999999 prec= 0 - -> JSON
+
+[keys]
+INTEGER len= -1 prec= -1 tk -> SMALLINT
+INTEGER len= -1 prec= -1 pk -> SMALLINT
+INTEGER len= -1 prec= -1 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+INTEGER len= 0 prec= 0 tk -> SMALLINT
+INTEGER len= 0 prec= 0 pk -> SMALLINT
+INTEGER len= 0 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+INTEGER len= 4 prec= 0 tk -> SMALLINT
+INTEGER len= 4 prec= 0 pk -> SMALLINT
+INTEGER len= 4 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+INTEGER len= 9 prec= 0 tk -> INTEGER
+INTEGER len= 9 prec= 0 pk -> INTEGER
+INTEGER len= 9 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+INTEGER len= 15 prec= 0 tk -> BIGINT
+INTEGER len= 15 prec= 0 pk -> BIGINT
+INTEGER len= 15 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+INTEGER len= 18 prec= 0 tk -> BIGINT
+INTEGER len= 18 prec= 0 pk -> BIGINT
+INTEGER len= 18 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+INTEGER len= 20 prec= 0 tk -> DECIMAL(20)
+INTEGER len= 20 prec= 0 pk -> DECIMAL(20)
+INTEGER len= 20 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+INTEGER len= 10 prec= 2 tk -> BIGINT
+INTEGER len= 10 prec= 2 pk -> BIGINT
+INTEGER len= 10 prec= 2 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+INTEGER len= 9999999 prec= 0 tk -> DECIMAL(9999999)
+INTEGER len= 9999999 prec= 0 pk -> DECIMAL(9999999)
+INTEGER len= 9999999 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+NUMBER len= -1 prec= -1 tk -> DOUBLE
+NUMBER len= -1 prec= -1 pk -> DOUBLE
+NUMBER len= -1 prec= -1 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+NUMBER len= 0 prec= 0 tk -> DOUBLE
+NUMBER len= 0 prec= 0 pk -> DOUBLE
+NUMBER len= 0 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+NUMBER len= 4 prec= 0 tk -> DOUBLE
+NUMBER len= 4 prec= 0 pk -> DOUBLE
+NUMBER len= 4 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+NUMBER len= 9 prec= 0 tk -> DOUBLE
+NUMBER len= 9 prec= 0 pk -> DOUBLE
+NUMBER len= 9 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+NUMBER len= 15 prec= 0 tk -> DOUBLE
+NUMBER len= 15 prec= 0 pk -> DOUBLE
+NUMBER len= 15 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+NUMBER len= 18 prec= 0 tk -> DOUBLE
+NUMBER len= 18 prec= 0 pk -> DOUBLE
+NUMBER len= 18 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+NUMBER len= 20 prec= 0 tk -> DOUBLE
+NUMBER len= 20 prec= 0 pk -> DOUBLE
+NUMBER len= 20 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+NUMBER len= 10 prec= 2 tk -> DOUBLE
+NUMBER len= 10 prec= 2 pk -> DOUBLE
+NUMBER len= 10 prec= 2 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+NUMBER len= 9999999 prec= 0 tk -> DOUBLE
+NUMBER len= 9999999 prec= 0 pk -> DOUBLE
+NUMBER len= 9999999 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+BIGNUMBER len= -1 prec= -1 tk -> DECIMAL(16,16)
+BIGNUMBER len= -1 prec= -1 pk -> DECIMAL(16,16)
+BIGNUMBER len= -1 prec= -1 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+BIGNUMBER len= 0 prec= 0 tk -> DECIMAL(16,16)
+BIGNUMBER len= 0 prec= 0 pk -> DECIMAL(16,16)
+BIGNUMBER len= 0 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+BIGNUMBER len= 4 prec= 0 tk -> DECIMAL(4,16)
+BIGNUMBER len= 4 prec= 0 pk -> DECIMAL(4,16)
+BIGNUMBER len= 4 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+BIGNUMBER len= 9 prec= 0 tk -> DECIMAL(9,16)
+BIGNUMBER len= 9 prec= 0 pk -> DECIMAL(9,16)
+BIGNUMBER len= 9 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+BIGNUMBER len= 15 prec= 0 tk -> DECIMAL(15,16)
+BIGNUMBER len= 15 prec= 0 pk -> DECIMAL(15,16)
+BIGNUMBER len= 15 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+BIGNUMBER len= 18 prec= 0 tk -> DECIMAL(18,16)
+BIGNUMBER len= 18 prec= 0 pk -> DECIMAL(18,16)
+BIGNUMBER len= 18 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+BIGNUMBER len= 20 prec= 0 tk -> DECIMAL(20,16)
+BIGNUMBER len= 20 prec= 0 pk -> DECIMAL(20,16)
+BIGNUMBER len= 20 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+BIGNUMBER len= 10 prec= 2 tk -> DECIMAL(10,2)
+BIGNUMBER len= 10 prec= 2 pk -> DECIMAL(10,2)
+BIGNUMBER len= 10 prec= 2 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+BIGNUMBER len= 9999999 prec= 0 tk -> DECIMAL(9999999,16)
+BIGNUMBER len= 9999999 prec= 0 pk -> DECIMAL(9999999,16)
+BIGNUMBER len= 9999999 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1, NOCACHE)
+
+[alter]
+STRING len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL VARCHAR()
+STRING len= -1 prec= -1 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL VARCHAR()
+STRING len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL VARCHAR(15)
+STRING len= 15 prec= 0 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL VARCHAR(15)
+STRING len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL VARCHAR(10)
+STRING len= 10 prec= 2 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL VARCHAR(10)
+INTEGER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL SMALLINT
+INTEGER len= -1 prec= -1 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL SMALLINT
+INTEGER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL BIGINT
+INTEGER len= 15 prec= 0 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL BIGINT
+INTEGER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL BIGINT
+INTEGER len= 10 prec= 2 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL BIGINT
+NUMBER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL DOUBLE
+NUMBER len= -1 prec= -1 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL DOUBLE
+NUMBER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL DOUBLE
+NUMBER len= 15 prec= 0 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL DOUBLE
+NUMBER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL DOUBLE
+NUMBER len= 10 prec= 2 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL DOUBLE
+BIGNUMBER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL DECIMAL(16,16)
+BIGNUMBER len= -1 prec= -1 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL DECIMAL(16,16)
+BIGNUMBER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL DECIMAL(15,16)
+BIGNUMBER len= 15 prec= 0 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL DECIMAL(15,16)
+BIGNUMBER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL DECIMAL(10,2)
+BIGNUMBER len= 10 prec= 2 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL DECIMAL(10,2)
+DATE len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+DATE len= -1 prec= -1 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+DATE len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+DATE len= 15 prec= 0 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+DATE len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+DATE len= 10 prec= 2 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+TIMESTAMP len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+TIMESTAMP len= -1 prec= -1 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+TIMESTAMP len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+TIMESTAMP len= 15 prec= 0 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+TIMESTAMP len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+TIMESTAMP len= 10 prec= 2 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL TIMESTAMP
+BOOLEAN len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL CHARACTER(1)
+BOOLEAN len= -1 prec= -1 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL CHARACTER(1)
+BOOLEAN len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL CHARACTER(1)
+BOOLEAN len= 15 prec= 0 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL CHARACTER(1)
+BOOLEAN len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL CHARACTER(1)
+BOOLEAN len= 10 prec= 2 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL CHARACTER(1)
+BINARY len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL BLOB
+BINARY len= -1 prec= -1 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL BLOB
+BINARY len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL CHAR(15) FOR BIT DATA
+BINARY len= 15 prec= 0 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL CHAR(15) FOR BIT DATA
+BINARY len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL CHAR(10) FOR BIT DATA
+BINARY len= 10 prec= 2 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL CHAR(10) FOR BIT DATA
+INET len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL UNKNOWN
+INET len= -1 prec= -1 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL UNKNOWN
+INET len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL UNKNOWN
+INET len= 15 prec= 0 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL UNKNOWN
+INET len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL UNKNOWN
+INET len= 10 prec= 2 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL UNKNOWN
+JSON len= -1 prec= -1 add -> ALTER TABLE TBL ADD COLUMN COL JSON
+JSON len= -1 prec= -1 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL JSON
+JSON len= 15 prec= 0 add -> ALTER TABLE TBL ADD COLUMN COL JSON
+JSON len= 15 prec= 0 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL JSON
+JSON len= 10 prec= 2 add -> ALTER TABLE TBL ADD COLUMN COL JSON
+JSON len= 10 prec= 2 modify -> ALTER TABLE TBL DROP COLUMN COL ; ALTER TABLE TBL ADD COLUMN COL JSON
diff --git a/plugins/databases/derby/src/main/java/org/apache/hop/databases/derby/DerbyDatabaseMeta.java b/plugins/databases/derby/src/main/java/org/apache/hop/databases/derby/DerbyDatabaseMeta.java
index 90fc8364e4..60476c7ae8 100644
--- a/plugins/databases/derby/src/main/java/org/apache/hop/databases/derby/DerbyDatabaseMeta.java
+++ b/plugins/databases/derby/src/main/java/org/apache/hop/databases/derby/DerbyDatabaseMeta.java
@@ -23,6 +23,7 @@ import org.apache.hop.core.database.DatabaseMeta;
import org.apache.hop.core.database.DatabaseMetaPlugin;
import org.apache.hop.core.database.DriverDownload;
import org.apache.hop.core.database.IDatabase;
+import org.apache.hop.core.database.types.ColumnContext;
import org.apache.hop.core.gui.plugin.GuiPlugin;
import org.apache.hop.core.row.IValueMeta;
import org.apache.hop.core.util.Utils;
@@ -31,7 +32,8 @@ import org.apache.hop.core.util.Utils;
@DatabaseMetaPlugin(
type = "DERBY",
typeDescription = "Apache Derby",
- documentationUrl = "/database/databases/derby.html")
+ documentationUrl = "/database/databases/derby.html",
+ classLoaderGroup = "derby-db")
@GuiPlugin(id = "GUI-DerbyDatabaseMeta")
public class DerbyDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
@Override
@@ -132,7 +134,7 @@ public class DerbyDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
return "ALTER TABLE "
+ tableName
+ " ADD "
- + getFieldDefinition(v, tk, pk, useAutoinc, true, false);
+ + getColumnDefinition(v, tk, pk, useAutoinc, true, false, ColumnContext.Purpose.ADD_COLUMN);
}
/**
@@ -152,7 +154,8 @@ public class DerbyDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
return "ALTER TABLE "
+ tableName
+ " ALTER "
- + getFieldDefinition(v, tk, pk, useAutoinc, true, false);
+ + getColumnDefinition(
+ v, tk, pk, useAutoinc, true, false, ColumnContext.Purpose.MODIFY_COLUMN);
}
@Override
diff --git a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java b/plugins/databases/derby/src/test/java/org/apache/hop/databases/derby/DerbyFieldDefinitionGoldenTest.java
similarity index 58%
copy from plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
copy to plugins/databases/derby/src/test/java/org/apache/hop/databases/derby/DerbyFieldDefinitionGoldenTest.java
index d138cf8a7f..c86b9afc62 100644
--- a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
+++ b/plugins/databases/derby/src/test/java/org/apache/hop/databases/derby/DerbyFieldDefinitionGoldenTest.java
@@ -14,19 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.hop.databases.derby;
-package org.apache.hop.databases.cockroachdb;
-
-import org.apache.hop.core.database.DatabaseMetaPlugin;
import org.apache.hop.core.database.IDatabase;
-import org.apache.hop.core.gui.plugin.GuiPlugin;
-import org.apache.hop.databases.postgresql.PostgreSqlDatabaseMeta;
+import org.apache.hop.junit.database.BaseFieldDefinitionGoldenTest;
-/** Contains PostgreSQL specific information through static final members */
-@DatabaseMetaPlugin(
- type = "COCKROACHDB",
- typeDescription = "CockroachDB",
- image = "cockroachdb.svg",
- documentationUrl = "/database/databases/postgresql.html")
-@GuiPlugin(id = "GUI-CockroachDatabaseMeta")
-public class CockroachDatabaseMeta extends PostgreSqlDatabaseMeta implements IDatabase {}
+class DerbyFieldDefinitionGoldenTest extends BaseFieldDefinitionGoldenTest {
+ @Override
+ protected IDatabase createDatabase() {
+ return new DerbyDatabaseMeta();
+ }
+}
diff --git a/plugins/databases/derby/src/test/resources/org/apache/hop/databases/derby/DerbyFieldDefinitionGoldenTest.txt b/plugins/databases/derby/src/test/resources/org/apache/hop/databases/derby/DerbyFieldDefinitionGoldenTest.txt
new file mode 100644
index 0000000000..1f849905d1
--- /dev/null
+++ b/plugins/databases/derby/src/test/resources/org/apache/hop/databases/derby/DerbyFieldDefinitionGoldenTest.txt
@@ -0,0 +1,240 @@
+# org.apache.hop.databases.derby.DerbyDatabaseMeta
+# Generated by DerbyFieldDefinitionGoldenTest. Do not hand-edit.
+# Records current behaviour including known bugs. See the class javadoc.
+
+[plain]
+STRING len= -1 prec= -1 - -> VARCHAR()
+STRING len= 0 prec= 0 - -> VARCHAR()
+STRING len= 4 prec= 0 - -> VARCHAR(4)
+STRING len= 9 prec= 0 - -> VARCHAR(9)
+STRING len= 15 prec= 0 - -> VARCHAR(15)
+STRING len= 18 prec= 0 - -> VARCHAR(18)
+STRING len= 20 prec= 0 - -> VARCHAR(20)
+STRING len= 10 prec= 2 - -> VARCHAR(10)
+STRING len= 9999999 prec= 0 - -> CLOB
+INTEGER len= -1 prec= -1 - -> TINYINT
+INTEGER len= 0 prec= 0 - -> TINYINT
+INTEGER len= 4 prec= 0 - -> SMALLINT
+INTEGER len= 9 prec= 0 - -> INT
+INTEGER len= 15 prec= 0 - -> BIGINT
+INTEGER len= 18 prec= 0 - -> BIGINT
+INTEGER len= 20 prec= 0 - -> DECIMAL(20)
+INTEGER len= 10 prec= 2 - -> BIGINT
+INTEGER len= 9999999 prec= 0 - -> DECIMAL(9999999)
+NUMBER len= -1 prec= -1 - -> DOUBLE
+NUMBER len= 0 prec= 0 - -> DOUBLE
+NUMBER len= 4 prec= 0 - -> DOUBLE
+NUMBER len= 9 prec= 0 - -> DOUBLE
+NUMBER len= 15 prec= 0 - -> DOUBLE
+NUMBER len= 18 prec= 0 - -> DOUBLE
+NUMBER len= 20 prec= 0 - -> DOUBLE
+NUMBER len= 10 prec= 2 - -> DOUBLE
+NUMBER len= 9999999 prec= 0 - -> DOUBLE
+BIGNUMBER len= -1 prec= -1 - -> DECIMAL(16,16)
+BIGNUMBER len= 0 prec= 0 - -> DECIMAL(16,16)
+BIGNUMBER len= 4 prec= 0 - -> DECIMAL(4,16)
+BIGNUMBER len= 9 prec= 0 - -> DECIMAL(9,16)
+BIGNUMBER len= 15 prec= 0 - -> DECIMAL(15,16)
+BIGNUMBER len= 18 prec= 0 - -> DECIMAL(18,16)
+BIGNUMBER len= 20 prec= 0 - -> DECIMAL(20,16)
+BIGNUMBER len= 10 prec= 2 - -> DECIMAL(10,2)
+BIGNUMBER len= 9999999 prec= 0 - -> DECIMAL(9999999,16)
+DATE len= -1 prec= -1 - -> TIMESTAMP
+DATE len= 0 prec= 0 - -> TIMESTAMP
+DATE len= 4 prec= 0 - -> TIMESTAMP
+DATE len= 9 prec= 0 - -> TIMESTAMP
+DATE len= 15 prec= 0 - -> TIMESTAMP
+DATE len= 18 prec= 0 - -> TIMESTAMP
+DATE len= 20 prec= 0 - -> TIMESTAMP
+DATE len= 10 prec= 2 - -> TIMESTAMP
+DATE len= 9999999 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= -1 prec= -1 - -> TIMESTAMP
+TIMESTAMP len= 0 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 4 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 9 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 15 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 18 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 20 prec= 0 - -> TIMESTAMP
+TIMESTAMP len= 10 prec= 2 - -> TIMESTAMP
+TIMESTAMP len= 9999999 prec= 0 - -> TIMESTAMP
+BOOLEAN len= -1 prec= -1 - -> CHAR(1)
+BOOLEAN len= 0 prec= 0 - -> CHAR(1)
+BOOLEAN len= 4 prec= 0 - -> CHAR(1)
+BOOLEAN len= 9 prec= 0 - -> CHAR(1)
+BOOLEAN len= 15 prec= 0 - -> CHAR(1)
+BOOLEAN len= 18 prec= 0 - -> CHAR(1)
+BOOLEAN len= 20 prec= 0 - -> CHAR(1)
+BOOLEAN len= 10 prec= 2 - -> CHAR(1)
+BOOLEAN len= 9999999 prec= 0 - -> CHAR(1)
+BINARY len= -1 prec= -1 - -> BLOB
+BINARY len= 0 prec= 0 - -> BLOB
+BINARY len= 4 prec= 0 - -> BLOB
+BINARY len= 9 prec= 0 - -> BLOB
+BINARY len= 15 prec= 0 - -> BLOB
+BINARY len= 18 prec= 0 - -> BLOB
+BINARY len= 20 prec= 0 - -> BLOB
+BINARY len= 10 prec= 2 - -> BLOB
+BINARY len= 9999999 prec= 0 - -> BLOB
+INET len= -1 prec= -1 - -> UNKNOWN
+INET len= 0 prec= 0 - -> UNKNOWN
+INET len= 4 prec= 0 - -> UNKNOWN
+INET len= 9 prec= 0 - -> UNKNOWN
+INET len= 15 prec= 0 - -> UNKNOWN
+INET len= 18 prec= 0 - -> UNKNOWN
+INET len= 20 prec= 0 - -> UNKNOWN
+INET len= 10 prec= 2 - -> UNKNOWN
+INET len= 9999999 prec= 0 - -> UNKNOWN
+JSON len= -1 prec= -1 - -> JSON
+JSON len= 0 prec= 0 - -> JSON
+JSON len= 4 prec= 0 - -> JSON
+JSON len= 9 prec= 0 - -> JSON
+JSON len= 15 prec= 0 - -> JSON
+JSON len= 18 prec= 0 - -> JSON
+JSON len= 20 prec= 0 - -> JSON
+JSON len= 10 prec= 2 - -> JSON
+JSON len= 9999999 prec= 0 - -> JSON
+
+[keys]
+INTEGER len= -1 prec= -1 tk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= -1 prec= -1 pk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= -1 prec= -1 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+INTEGER len= 0 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 0 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 0 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+INTEGER len= 4 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 4 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 4 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+INTEGER len= 9 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 9 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 9 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+INTEGER len= 15 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 15 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 15 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+INTEGER len= 18 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 18 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 18 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+INTEGER len= 20 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 20 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 20 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+INTEGER len= 10 prec= 2 tk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 10 prec= 2 pk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 10 prec= 2 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+INTEGER len= 9999999 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 9999999 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 9999999 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+NUMBER len= -1 prec= -1 tk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= -1 prec= -1 pk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= -1 prec= -1 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+NUMBER len= 0 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 0 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 0 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+NUMBER len= 4 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 4 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 4 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+NUMBER len= 9 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 9 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 9 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+NUMBER len= 15 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 15 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 15 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+NUMBER len= 18 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 18 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 18 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+NUMBER len= 20 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 20 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 20 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+NUMBER len= 10 prec= 2 tk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 10 prec= 2 pk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 10 prec= 2 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+NUMBER len= 9999999 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 9999999 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 9999999 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+BIGNUMBER len= -1 prec= -1 tk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= -1 prec= -1 pk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= -1 prec= -1 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+BIGNUMBER len= 0 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 0 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 0 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+BIGNUMBER len= 4 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 4 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 4 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+BIGNUMBER len= 9 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 9 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 9 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+BIGNUMBER len= 15 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 15 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 15 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+BIGNUMBER len= 18 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 18 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 18 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+BIGNUMBER len= 20 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 20 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 20 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+BIGNUMBER len= 10 prec= 2 tk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 10 prec= 2 pk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 10 prec= 2 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+BIGNUMBER len= 9999999 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 9999999 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 9999999 prec= 0 autoinc -> BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 0, INCREMENT BY 1)
+
+[alter]
+STRING len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL VARCHAR()
+STRING len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COL VARCHAR()
+STRING len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL VARCHAR(15)
+STRING len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COL VARCHAR(15)
+STRING len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL VARCHAR(10)
+STRING len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COL VARCHAR(10)
+INTEGER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL TINYINT
+INTEGER len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COL TINYINT
+INTEGER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL BIGINT
+INTEGER len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COL BIGINT
+INTEGER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL BIGINT
+INTEGER len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COL BIGINT
+NUMBER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL DOUBLE
+NUMBER len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COL DOUBLE
+NUMBER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL DOUBLE
+NUMBER len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COL DOUBLE
+NUMBER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL DOUBLE
+NUMBER len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COL DOUBLE
+BIGNUMBER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL DECIMAL(16,16)
+BIGNUMBER len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COL DECIMAL(16,16)
+BIGNUMBER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL DECIMAL(15,16)
+BIGNUMBER len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COL DECIMAL(15,16)
+BIGNUMBER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL DECIMAL(10,2)
+BIGNUMBER len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COL DECIMAL(10,2)
+DATE len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL TIMESTAMP
+DATE len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COL TIMESTAMP
+DATE len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL TIMESTAMP
+DATE len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COL TIMESTAMP
+DATE len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL TIMESTAMP
+DATE len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COL TIMESTAMP
+TIMESTAMP len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL TIMESTAMP
+TIMESTAMP len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COL TIMESTAMP
+TIMESTAMP len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL TIMESTAMP
+TIMESTAMP len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COL TIMESTAMP
+TIMESTAMP len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL TIMESTAMP
+TIMESTAMP len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COL TIMESTAMP
+BOOLEAN len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL CHAR(1)
+BOOLEAN len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COL CHAR(1)
+BOOLEAN len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL CHAR(1)
+BOOLEAN len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COL CHAR(1)
+BOOLEAN len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL CHAR(1)
+BOOLEAN len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COL CHAR(1)
+BINARY len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL BLOB
+BINARY len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COL BLOB
+BINARY len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL BLOB
+BINARY len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COL BLOB
+BINARY len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL BLOB
+BINARY len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COL BLOB
+INET len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL UNKNOWN
+INET len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COL UNKNOWN
+INET len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL UNKNOWN
+INET len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COL UNKNOWN
+INET len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL UNKNOWN
+INET len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COL UNKNOWN
+JSON len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL JSON
+JSON len= -1 prec= -1 modify -> ALTER TABLE TBL ALTER COL JSON
+JSON len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL JSON
+JSON len= 15 prec= 0 modify -> ALTER TABLE TBL ALTER COL JSON
+JSON len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL JSON
+JSON len= 10 prec= 2 modify -> ALTER TABLE TBL ALTER COL JSON
diff --git a/plugins/databases/doris/src/main/java/org/apache/hop/databases/doris/DorisDatabaseMeta.java b/plugins/databases/doris/src/main/java/org/apache/hop/databases/doris/DorisDatabaseMeta.java
index 70697650fc..1c3a765a21 100644
--- a/plugins/databases/doris/src/main/java/org/apache/hop/databases/doris/DorisDatabaseMeta.java
+++ b/plugins/databases/doris/src/main/java/org/apache/hop/databases/doris/DorisDatabaseMeta.java
@@ -27,7 +27,8 @@ import org.apache.hop.databases.mysql.MySqlDatabaseMeta;
type = "DORIS",
typeDescription = "Apache Doris",
image = "doris.svg",
- documentationUrl = "/database/databases/doris.html")
+ documentationUrl = "/database/databases/doris.html",
+ classLoaderGroup = "doris-db")
@GuiPlugin(id = "GUI-DorisDatabaseMeta")
public class DorisDatabaseMeta extends MySqlDatabaseMeta {
diff --git a/plugins/databases/doris/src/main/java/org/apache/hop/pipeline/transforms/dorisbulkloader/DorisBulkLoaderMeta.java b/plugins/databases/doris/src/main/java/org/apache/hop/pipeline/transforms/dorisbulkloader/DorisBulkLoaderMeta.java
index ea85b924bb..c2cbc6caac 100644
--- a/plugins/databases/doris/src/main/java/org/apache/hop/pipeline/transforms/dorisbulkloader/DorisBulkLoaderMeta.java
+++ b/plugins/databases/doris/src/main/java/org/apache/hop/pipeline/transforms/dorisbulkloader/DorisBulkLoaderMeta.java
@@ -39,7 +39,8 @@ import org.apache.hop.pipeline.transform.TransformMeta;
description = "i18n::BaseTransform.TypeTooltipDesc.DorisBulkLoader",
categoryDescription = "i18n:org.apache.hop.pipeline.transform:BaseTransform.Category.Bulk",
keywords = "i18n::DorisBulkLoaderMeta.keyword",
- documentationUrl = "/pipeline/transforms/dorisbulkloader.html")
+ documentationUrl = "/pipeline/transforms/dorisbulkloader.html",
+ classLoaderGroup = "doris-db")
public class DorisBulkLoaderMeta extends BaseTransformMeta<DorisBulkLoader, DorisBulkLoaderData> {
private static final Class<?> PKG = DorisBulkLoaderMeta.class;
diff --git a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java b/plugins/databases/doris/src/test/java/org/apache/hop/databases/doris/DorisFieldDefinitionGoldenTest.java
similarity index 58%
copy from plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
copy to plugins/databases/doris/src/test/java/org/apache/hop/databases/doris/DorisFieldDefinitionGoldenTest.java
index d138cf8a7f..60fa3dec3e 100644
--- a/plugins/databases/cockroachdb/src/main/java/org/apache/hop/databases/cockroachdb/CockroachDatabaseMeta.java
+++ b/plugins/databases/doris/src/test/java/org/apache/hop/databases/doris/DorisFieldDefinitionGoldenTest.java
@@ -14,19 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.hop.databases.doris;
-package org.apache.hop.databases.cockroachdb;
-
-import org.apache.hop.core.database.DatabaseMetaPlugin;
import org.apache.hop.core.database.IDatabase;
-import org.apache.hop.core.gui.plugin.GuiPlugin;
-import org.apache.hop.databases.postgresql.PostgreSqlDatabaseMeta;
+import org.apache.hop.junit.database.BaseFieldDefinitionGoldenTest;
-/** Contains PostgreSQL specific information through static final members */
-@DatabaseMetaPlugin(
- type = "COCKROACHDB",
- typeDescription = "CockroachDB",
- image = "cockroachdb.svg",
- documentationUrl = "/database/databases/postgresql.html")
-@GuiPlugin(id = "GUI-CockroachDatabaseMeta")
-public class CockroachDatabaseMeta extends PostgreSqlDatabaseMeta implements IDatabase {}
+class DorisFieldDefinitionGoldenTest extends BaseFieldDefinitionGoldenTest {
+ @Override
+ protected IDatabase createDatabase() {
+ return new DorisDatabaseMeta();
+ }
+}
diff --git a/plugins/databases/doris/src/test/resources/org/apache/hop/databases/doris/DorisFieldDefinitionGoldenTest.txt b/plugins/databases/doris/src/test/resources/org/apache/hop/databases/doris/DorisFieldDefinitionGoldenTest.txt
new file mode 100644
index 0000000000..82996d7791
--- /dev/null
+++ b/plugins/databases/doris/src/test/resources/org/apache/hop/databases/doris/DorisFieldDefinitionGoldenTest.txt
@@ -0,0 +1,240 @@
+# org.apache.hop.databases.doris.DorisDatabaseMeta
+# Generated by DorisFieldDefinitionGoldenTest. Do not hand-edit.
+# Records current behaviour including known bugs. See the class javadoc.
+
+[plain]
+STRING len= -1 prec= -1 - -> STRING
+STRING len= 0 prec= 0 - -> STRING
+STRING len= 4 prec= 0 - -> VARCHAR(4)
+STRING len= 9 prec= 0 - -> VARCHAR(9)
+STRING len= 15 prec= 0 - -> VARCHAR(15)
+STRING len= 18 prec= 0 - -> VARCHAR(18)
+STRING len= 20 prec= 0 - -> VARCHAR(20)
+STRING len= 10 prec= 2 - -> VARCHAR(10)
+STRING len= 9999999 prec= 0 - -> STRING
+INTEGER len= -1 prec= -1 - -> TINYINT
+INTEGER len= 0 prec= 0 - -> TINYINT
+INTEGER len= 4 prec= 0 - -> SMALLINT
+INTEGER len= 9 prec= 0 - -> INT
+INTEGER len= 15 prec= 0 - -> BIGINT
+INTEGER len= 18 prec= 0 - -> BIGINT
+INTEGER len= 20 prec= 0 - -> DECIMAL(20)
+INTEGER len= 10 prec= 2 - -> BIGINT
+INTEGER len= 9999999 prec= 0 - -> DECIMAL(2147483647)
+NUMBER len= -1 prec= -1 - -> DOUBLE
+NUMBER len= 0 prec= 0 - -> DOUBLE
+NUMBER len= 4 prec= 0 - -> DOUBLE
+NUMBER len= 9 prec= 0 - -> DOUBLE
+NUMBER len= 15 prec= 0 - -> DOUBLE
+NUMBER len= 18 prec= 0 - -> DOUBLE
+NUMBER len= 20 prec= 0 - -> DOUBLE
+NUMBER len= 10 prec= 2 - -> DOUBLE
+NUMBER len= 9999999 prec= 0 - -> DOUBLE
+BIGNUMBER len= -1 prec= -1 - -> DECIMAL(16,16)
+BIGNUMBER len= 0 prec= 0 - -> DECIMAL(16,16)
+BIGNUMBER len= 4 prec= 0 - -> DECIMAL(4,16)
+BIGNUMBER len= 9 prec= 0 - -> DECIMAL(9,16)
+BIGNUMBER len= 15 prec= 0 - -> DECIMAL(15,16)
+BIGNUMBER len= 18 prec= 0 - -> DECIMAL(18,16)
+BIGNUMBER len= 20 prec= 0 - -> DECIMAL(20,16)
+BIGNUMBER len= 10 prec= 2 - -> DECIMAL(10,2)
+BIGNUMBER len= 9999999 prec= 0 - -> DECIMAL(2147483647,16)
+DATE len= -1 prec= -1 - -> DATETIME
+DATE len= 0 prec= 0 - -> DATETIME
+DATE len= 4 prec= 0 - -> DATETIME
+DATE len= 9 prec= 0 - -> DATETIME
+DATE len= 15 prec= 0 - -> DATETIME
+DATE len= 18 prec= 0 - -> DATETIME
+DATE len= 20 prec= 0 - -> DATETIME
+DATE len= 10 prec= 2 - -> 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= 15 prec= 0 - -> DATETIME
+TIMESTAMP len= 18 prec= 0 - -> DATETIME
+TIMESTAMP len= 20 prec= 0 - -> DATETIME
+TIMESTAMP len= 10 prec= 2 - -> DATETIME
+TIMESTAMP len= 9999999 prec= 0 - -> DATETIME
+BOOLEAN len= -1 prec= -1 - -> CHAR(1)
+BOOLEAN len= 0 prec= 0 - -> CHAR(1)
+BOOLEAN len= 4 prec= 0 - -> CHAR(1)
+BOOLEAN len= 9 prec= 0 - -> CHAR(1)
+BOOLEAN len= 15 prec= 0 - -> CHAR(1)
+BOOLEAN len= 18 prec= 0 - -> CHAR(1)
+BOOLEAN len= 20 prec= 0 - -> CHAR(1)
+BOOLEAN len= 10 prec= 2 - -> CHAR(1)
+BOOLEAN len= 9999999 prec= 0 - -> CHAR(1)
+BINARY len= -1 prec= -1 - -> BITMAP
+BINARY len= 0 prec= 0 - -> BITMAP
+BINARY len= 4 prec= 0 - -> BITMAP
+BINARY len= 9 prec= 0 - -> BITMAP
+BINARY len= 15 prec= 0 - -> BITMAP
+BINARY len= 18 prec= 0 - -> BITMAP
+BINARY len= 20 prec= 0 - -> BITMAP
+BINARY len= 10 prec= 2 - -> BITMAP
+BINARY len= 9999999 prec= 0 - -> BITMAP
+INET len= -1 prec= -1 - -> UNKNOWN
+INET len= 0 prec= 0 - -> UNKNOWN
+INET len= 4 prec= 0 - -> UNKNOWN
+INET len= 9 prec= 0 - -> UNKNOWN
+INET len= 15 prec= 0 - -> UNKNOWN
+INET len= 18 prec= 0 - -> UNKNOWN
+INET len= 20 prec= 0 - -> UNKNOWN
+INET len= 10 prec= 2 - -> UNKNOWN
+INET len= 9999999 prec= 0 - -> UNKNOWN
+JSON len= -1 prec= -1 - -> JSON
+JSON len= 0 prec= 0 - -> JSON
+JSON len= 4 prec= 0 - -> JSON
+JSON len= 9 prec= 0 - -> JSON
+JSON len= 15 prec= 0 - -> JSON
+JSON len= 18 prec= 0 - -> JSON
+JSON len= 20 prec= 0 - -> JSON
+JSON len= 10 prec= 2 - -> JSON
+JSON len= 9999999 prec= 0 - -> JSON
+
+[keys]
+INTEGER len= -1 prec= -1 tk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= -1 prec= -1 pk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= -1 prec= -1 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+INTEGER len= 0 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 0 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 0 prec= 0 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+INTEGER len= 4 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 4 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 4 prec= 0 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+INTEGER len= 9 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 9 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 9 prec= 0 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+INTEGER len= 15 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 15 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 15 prec= 0 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+INTEGER len= 18 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 18 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 18 prec= 0 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+INTEGER len= 20 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 20 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 20 prec= 0 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+INTEGER len= 10 prec= 2 tk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 10 prec= 2 pk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 10 prec= 2 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+INTEGER len= 9999999 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 9999999 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+INTEGER len= 9999999 prec= 0 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+NUMBER len= -1 prec= -1 tk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= -1 prec= -1 pk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= -1 prec= -1 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+NUMBER len= 0 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 0 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 0 prec= 0 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+NUMBER len= 4 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 4 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 4 prec= 0 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+NUMBER len= 9 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 9 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 9 prec= 0 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+NUMBER len= 15 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 15 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 15 prec= 0 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+NUMBER len= 18 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 18 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 18 prec= 0 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+NUMBER len= 20 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 20 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 20 prec= 0 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+NUMBER len= 10 prec= 2 tk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 10 prec= 2 pk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 10 prec= 2 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+NUMBER len= 9999999 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 9999999 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+NUMBER len= 9999999 prec= 0 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+BIGNUMBER len= -1 prec= -1 tk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= -1 prec= -1 pk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= -1 prec= -1 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+BIGNUMBER len= 0 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 0 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 0 prec= 0 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+BIGNUMBER len= 4 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 4 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 4 prec= 0 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+BIGNUMBER len= 9 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 9 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 9 prec= 0 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+BIGNUMBER len= 15 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 15 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 15 prec= 0 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+BIGNUMBER len= 18 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 18 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 18 prec= 0 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+BIGNUMBER len= 20 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 20 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 20 prec= 0 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+BIGNUMBER len= 10 prec= 2 tk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 10 prec= 2 pk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 10 prec= 2 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+BIGNUMBER len= 9999999 prec= 0 tk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 9999999 prec= 0 pk -> BIGINT NOT NULL PRIMARY KEY
+BIGNUMBER len= 9999999 prec= 0 autoinc -> BIGINT AUTO_INCREMENT NOT NULL PRIMARY KEY
+
+[alter]
+STRING len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL STRING
+STRING len= -1 prec= -1 modify -> ALTER TABLE TBL MODIFY COL STRING
+STRING len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL VARCHAR(15)
+STRING len= 15 prec= 0 modify -> ALTER TABLE TBL MODIFY COL VARCHAR(15)
+STRING len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL VARCHAR(10)
+STRING len= 10 prec= 2 modify -> ALTER TABLE TBL MODIFY COL VARCHAR(10)
+INTEGER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL TINYINT
+INTEGER len= -1 prec= -1 modify -> ALTER TABLE TBL MODIFY COL TINYINT
+INTEGER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL BIGINT
+INTEGER len= 15 prec= 0 modify -> ALTER TABLE TBL MODIFY COL BIGINT
+INTEGER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL BIGINT
+INTEGER len= 10 prec= 2 modify -> ALTER TABLE TBL MODIFY COL BIGINT
+NUMBER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL DOUBLE
+NUMBER len= -1 prec= -1 modify -> ALTER TABLE TBL MODIFY COL DOUBLE
+NUMBER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL DOUBLE
+NUMBER len= 15 prec= 0 modify -> ALTER TABLE TBL MODIFY COL DOUBLE
+NUMBER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL DOUBLE
+NUMBER len= 10 prec= 2 modify -> ALTER TABLE TBL MODIFY COL DOUBLE
+BIGNUMBER len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL DECIMAL(16,16)
+BIGNUMBER len= -1 prec= -1 modify -> ALTER TABLE TBL MODIFY COL DECIMAL(16,16)
+BIGNUMBER len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL DECIMAL(15,16)
+BIGNUMBER len= 15 prec= 0 modify -> ALTER TABLE TBL MODIFY COL DECIMAL(15,16)
+BIGNUMBER len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL DECIMAL(10,2)
+BIGNUMBER len= 10 prec= 2 modify -> ALTER TABLE TBL MODIFY COL DECIMAL(10,2)
+DATE len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL DATETIME
+DATE len= -1 prec= -1 modify -> ALTER TABLE TBL MODIFY COL DATETIME
+DATE len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL DATETIME
+DATE len= 15 prec= 0 modify -> ALTER TABLE TBL MODIFY COL DATETIME
+DATE len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL DATETIME
+DATE len= 10 prec= 2 modify -> ALTER TABLE TBL MODIFY COL DATETIME
+TIMESTAMP len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL DATETIME
+TIMESTAMP len= -1 prec= -1 modify -> ALTER TABLE TBL MODIFY COL DATETIME
+TIMESTAMP len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL DATETIME
+TIMESTAMP len= 15 prec= 0 modify -> ALTER TABLE TBL MODIFY COL DATETIME
+TIMESTAMP len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL DATETIME
+TIMESTAMP len= 10 prec= 2 modify -> ALTER TABLE TBL MODIFY COL DATETIME
+BOOLEAN len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL CHAR(1)
+BOOLEAN len= -1 prec= -1 modify -> ALTER TABLE TBL MODIFY COL CHAR(1)
+BOOLEAN len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL CHAR(1)
+BOOLEAN len= 15 prec= 0 modify -> ALTER TABLE TBL MODIFY COL CHAR(1)
+BOOLEAN len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL CHAR(1)
+BOOLEAN len= 10 prec= 2 modify -> ALTER TABLE TBL MODIFY COL CHAR(1)
+BINARY len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL BITMAP
+BINARY len= -1 prec= -1 modify -> ALTER TABLE TBL MODIFY COL BITMAP
+BINARY len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL BITMAP
+BINARY len= 15 prec= 0 modify -> ALTER TABLE TBL MODIFY COL BITMAP
+BINARY len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL BITMAP
+BINARY len= 10 prec= 2 modify -> ALTER TABLE TBL MODIFY COL BITMAP
+INET len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL UNKNOWN
+INET len= -1 prec= -1 modify -> ALTER TABLE TBL MODIFY COL UNKNOWN
+INET len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL UNKNOWN
+INET len= 15 prec= 0 modify -> ALTER TABLE TBL MODIFY COL UNKNOWN
+INET len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL UNKNOWN
+INET len= 10 prec= 2 modify -> ALTER TABLE TBL MODIFY COL UNKNOWN
+JSON len= -1 prec= -1 add -> ALTER TABLE TBL ADD COL JSON
+JSON len= -1 prec= -1 modify -> ALTER TABLE TBL MODIFY COL JSON
+JSON len= 15 prec= 0 add -> ALTER TABLE TBL ADD COL JSON
+JSON len= 15 prec= 0 modify -> ALTER TABLE TBL MODIFY COL JSON
+JSON len= 10 prec= 2 add -> ALTER TABLE TBL ADD COL JSON
+JSON len= 10 prec= 2 modify -> ALTER TABLE TBL MODIFY COL JSON
diff --git a/plugins/databases/duckdb/src/main/java/org/apache/hop/databases/duckdb/DuckDBDatabaseMeta.java b/plugins/databases/duckdb/src/main/java/org/apache/hop/databases/duckdb/DuckDBDatabaseMeta.java
index e2ec8c6f1b..039b817e5d 100644
--- a/plugins/databases/duckdb/src/main/java/org/apache/hop/databases/duckdb/DuckDBDatabaseMeta.java
+++ b/plugins/databases/duckdb/src/main/java/org/apache/hop/databases/duckdb/DuckDBDatabaseMeta.java
@@ -17,12 +17,17 @@
package org.apache.hop.databases.duckdb;
+import java.util.List;
import org.apache.hop.core.Const;
import org.apache.hop.core.database.BaseDatabaseMeta;
import org.apache.hop.core.database.DatabaseMeta;
import org.apache.hop.core.database.DatabaseMetaPlugin;
import org.apache.hop.core.database.DriverDownload;
import org.apache.hop.core.database.IDatabase;
+import org.apache.hop.core.database.types.ColumnContext;
+import org.apache.hop.core.database.types.DatabaseTypes;
+import org.apache.hop.core.database.types.IDatabaseTypeRule;
+import org.apache.hop.core.database.types.JdbcDateValues;
import org.apache.hop.core.exception.HopDatabaseException;
import org.apache.hop.core.gui.plugin.GuiPlugin;
import org.apache.hop.core.row.IValueMeta;
@@ -31,10 +36,23 @@ import org.apache.hop.core.row.IValueMeta;
type = "DuckDB",
typeDescription = "DuckDB",
image = "duckdb.svg",
- documentationUrl = "/database/databases/duckdb.html")
+ documentationUrl = "/database/databases/duckdb.html",
+ classLoaderGroup = "duckdb-db")
@GuiPlugin(id = "GUI-DuckDBDatabaseMeta")
public class DuckDBDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
+ private static final List<IDatabaseTypeRule> TYPE_RULES =
+ DatabaseTypes.rules()
+ // As of DuckDB JDBC 0.10.0 the Calendar overloads of setDate and setTimestamp are not
+ // implemented, so a configured time zone cannot be passed to the driver.
+ .bind(IValueMeta.TYPE_DATE, JdbcDateValues.WITHOUT_CALENDAR_OVERLOADS)
+ .build();
+
+ @Override
+ public List<IDatabaseTypeRule> getTypeRules() {
+ return TYPE_RULES;
+ }
+
@Override
public String getCreateTableStatement() {
return super.getCreateTableStatement();
@@ -57,8 +75,6 @@ public class DuckDBDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
if (addFieldName) {
retval += fieldname + " ";
- } else {
- retval += fieldname + " TYPE ";
}
int type = v.getType();
@@ -177,7 +193,8 @@ public class DuckDBDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
return "ALTER TABLE "
+ tableName
+ " ADD COLUMN "
- + getFieldDefinition(v, tk, pk, useAutoIncrement, true, false);
+ + getColumnDefinition(
+ v, tk, pk, useAutoIncrement, true, false, ColumnContext.Purpose.ADD_COLUMN);
}
@Override
@@ -188,10 +205,17 @@ public class DuckDBDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
boolean useAutoIncrement,
String pk,
boolean semicolon) {
+ // The column name and the TYPE keyword belong to the ALTER syntax, not to the column
+ // definition. Asking for a definition without a field name has to return the type on its own,
... 13573 lines suppressed ...