From 5336a0b31b27d654467fe4525c23e585cdc62219 Mon Sep 17 00:00:00 2001 From: sharpca <110613026+sharpca@users.noreply.github.com> Date: Sun, 1 Nov 2026 15:00:43 +0800 Subject: [PATCH 1/8] [Fix][Connector-V2] Make JDBC dialect type converters locale independent The JDBC dialect type converters convert the source type name with String#toUpperCase() / String#toLowerCase() without a locale before matching it against the dialect type constants. Under a Turkish or Azerbaijani default locale a lower-case "integer" upper-cases to "INTEGER" with a dotted capital I and an upper-case "INT4" lower-cases to "int4" with a dotless i, so the type no longer matches any constant and the converter falls through to its unsupported-type branch. Pass Locale.ROOT to every remaining case conversion in connector-jdbc so that the same source type maps to the same SeaTunnel type on any worker. The MySQL, OceanBase and SQL Server converters, JdbcDialect and CatalogUtils are already covered by #13063, #13076 and #13077 and are not touched here. --- .../catalog/dm/DamengDataTypeConvertor.java | 3 +- .../jdbc/catalog/duckdb/DuckDBCatalog.java | 3 +- .../jdbc/catalog/mysql/MySqlCatalog.java | 3 +- .../oceanbase/OceanBaseMySqlCatalog.java | 5 +- .../OceanBaseOracleCreateTableSqlBuilder.java | 4 +- .../catalog/redshift/RedshiftCatalog.java | 5 +- .../redshift/RedshiftDataTypeConvertor.java | 3 +- .../catalog/sqlserver/SqlServerURLParser.java | 4 +- .../jdbc/catalog/xugu/XuguCatalog.java | 5 +- .../catalog/yashandb/YashanDbCatalog.java | 7 +- .../dialect/db2/DB2TypeConverter.java | 4 +- .../dialect/dm/DmdbTypeConverter.java | 4 +- .../dialect/gbase8a/Gbase8aTypeMapper.java | 3 +- .../internal/dialect/hive/HiveTypeMapper.java | 3 +- .../dialect/iris/IrisTypeConverter.java | 3 +- .../kingbase/KingbaseTypeConverter.java | 3 +- .../dialect/oracle/OracleTypeConverter.java | 4 +- .../dialect/phoenix/PhoenixTypeConverter.java | 4 +- .../dialect/presto/PrestoTypeMapper.java | 3 +- .../dialect/psql/PostgresDialect.java | 5 +- .../dialect/psql/PostgresTypeConverter.java | 3 +- .../redshift/RedshiftTypeConverter.java | 3 +- .../dialect/saphana/SapHanaTypeConverter.java | 3 +- .../snowflake/SnowflakeTypeConverter.java | 4 +- .../dialect/sqlite/SqliteTypeMapper.java | 4 +- .../tablestore/TablestoreTypeMapper.java | 3 +- .../dialect/teradata/TeradataTypeMapper.java | 3 +- .../dialect/vertica/VerticaTypeMapper.java | 3 +- .../dialect/xugu/XuguTypeConverter.java | 4 +- .../yashandb/YashanDbTypeConverter.java | 3 +- .../dialect/JdbcTypeConverterLocaleTest.java | 90 +++++++++++++++++++ 31 files changed, 167 insertions(+), 34 deletions(-) create mode 100644 seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcTypeConverterLocaleTest.java diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/dm/DamengDataTypeConvertor.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/dm/DamengDataTypeConvertor.java index 533f7db9cd40..4bffdd244f7b 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/dm/DamengDataTypeConvertor.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/dm/DamengDataTypeConvertor.java @@ -30,6 +30,7 @@ import com.google.auto.service.AutoService; import java.util.Collections; +import java.util.Locale; import java.util.Map; import static org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.dm.DmdbTypeConverter.DM_DEC; @@ -61,7 +62,7 @@ public SeaTunnelDataType toSeaTunnelType( String field, String dataType, Map properties) { Integer precision = null; Integer scale = null; - switch (dataType.toUpperCase()) { + switch (dataType.toUpperCase(Locale.ROOT)) { case DM_NUMERIC: case DM_NUMBER: case DM_DECIMAL: diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/duckdb/DuckDBCatalog.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/duckdb/DuckDBCatalog.java index e9652f129d71..bc45bd58d17e 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/duckdb/DuckDBCatalog.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/duckdb/DuckDBCatalog.java @@ -42,6 +42,7 @@ import java.util.Enumeration; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Properties; import java.util.regex.Pattern; @@ -262,6 +263,6 @@ protected String getOptionTableName(TablePath tablePath) { } private boolean isDuckDBDecimal(String typeName) { - return typeName.toUpperCase().startsWith(DuckDBTypeConverter.DUCKDB_DECIMAL); + return typeName.toUpperCase(Locale.ROOT).startsWith(DuckDBTypeConverter.DUCKDB_DECIMAL); } } diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/mysql/MySqlCatalog.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/mysql/MySqlCatalog.java index 4a3bdf642caf..075405a04fa6 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/mysql/MySqlCatalog.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/mysql/MySqlCatalog.java @@ -49,6 +49,7 @@ import java.sql.Statement; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Properties; @@ -221,7 +222,7 @@ protected Column buildColumn(ResultSet resultSet) throws SQLException { // e.g. tinyint(1) unsigned String columnType = resultSet.getString("COLUMN_TYPE"); // e.g. tinyint - String dataType = resultSet.getString("DATA_TYPE").toUpperCase(); + String dataType = resultSet.getString("DATA_TYPE").toUpperCase(Locale.ROOT); String comment = resultSet.getString("COLUMN_COMMENT"); Object defaultValue = resultSet.getObject("COLUMN_DEFAULT"); String isNullableStr = resultSet.getString("IS_NULLABLE"); diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseMySqlCatalog.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseMySqlCatalog.java index 1fa3a57c9ddf..b6cd5442287b 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseMySqlCatalog.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseMySqlCatalog.java @@ -49,6 +49,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Locale; @Slf4j public class OceanBaseMySqlCatalog extends AbstractJdbcCatalog { @@ -142,12 +143,12 @@ protected Column buildColumn(ResultSet resultSet) throws SQLException { // e.g. tinyint(1) unsigned String columnType = resultSet.getString("COLUMN_TYPE"); // e.g. tinyint - String dataType = resultSet.getString("DATA_TYPE").toUpperCase(); + String dataType = resultSet.getString("DATA_TYPE").toUpperCase(Locale.ROOT); String comment = resultSet.getString("COLUMN_COMMENT"); Object defaultValue = resultSet.getObject("COLUMN_DEFAULT"); String isNullableStr = resultSet.getString("IS_NULLABLE"); - if (dataType.toUpperCase().startsWith("VECTOR")) { + if (dataType.toUpperCase(Locale.ROOT).startsWith("VECTOR")) { dataType = "VECTOR"; } diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseOracleCreateTableSqlBuilder.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseOracleCreateTableSqlBuilder.java index 34de2822b742..23af6ed1a2ca 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseOracleCreateTableSqlBuilder.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseOracleCreateTableSqlBuilder.java @@ -25,6 +25,8 @@ import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.DatabaseIdentifier; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oracle.OracleTypeConverter; +import java.util.Locale; + public class OceanBaseOracleCreateTableSqlBuilder extends OracleCreateTableSqlBuilder { public OceanBaseOracleCreateTableSqlBuilder(CatalogTable catalogTable, boolean createIndex) { @@ -46,7 +48,7 @@ protected String buildColumnSql(Column column) { // handle OceanBase Oracle compatible mode unsupported types, please refer // https://www.oceanbase.com/docs/enterprise-oceanbase-database-cn-10000000000355002 // and https://www.oceanbase.com/docs/enterprise-oms-doc-cn-1000000002530110 - switch (column.getSourceType().toUpperCase()) { + switch (column.getSourceType().toUpperCase(Locale.ROOT)) { case OracleTypeConverter.ORACLE_LONG: columnType = OracleTypeConverter.ORACLE_CLOB; break; diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/redshift/RedshiftCatalog.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/redshift/RedshiftCatalog.java index 3d130aec4634..0cd805953fff 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/redshift/RedshiftCatalog.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/redshift/RedshiftCatalog.java @@ -33,6 +33,7 @@ import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Locale; @Slf4j public class RedshiftCatalog extends AbstractJdbcCatalog { @@ -82,7 +83,7 @@ protected String getTableName(ResultSet rs) throws SQLException { .append(".") .append(rs.getString(2)) .toString() - .toLowerCase(); + .toLowerCase(Locale.ROOT); } @Override @@ -133,7 +134,7 @@ protected TableIdentifier getTableIdentifier(TablePath tablePath) { @Override protected Column buildColumn(ResultSet resultSet) throws SQLException { String columnName = resultSet.getString("COLUMN_NAME"); - String typeName = resultSet.getString("DATA_TYPE").toUpperCase(); + String typeName = resultSet.getString("DATA_TYPE").toUpperCase(Locale.ROOT); long precision = resultSet.getLong("NUMERIC_PRECISION"); int scale = resultSet.getInt("NUMERIC_SCALE"); long columnLength = resultSet.getLong("CHARACTER_MAXIMUM_LENGTH"); diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/redshift/RedshiftDataTypeConvertor.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/redshift/RedshiftDataTypeConvertor.java index 743a26a5c9ac..35e5dfd17c9c 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/redshift/RedshiftDataTypeConvertor.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/redshift/RedshiftDataTypeConvertor.java @@ -31,6 +31,7 @@ import com.google.auto.service.AutoService; import java.util.Collections; +import java.util.Locale; import java.util.Map; import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; @@ -58,7 +59,7 @@ public SeaTunnelDataType toSeaTunnelType( Integer precision = null; Integer scale = null; - switch (connectorDataType.toUpperCase()) { + switch (connectorDataType.toUpperCase(Locale.ROOT)) { case RedshiftTypeConverter.PG_NUMERIC: precision = MapUtils.getInteger(dataTypeProperties, PRECISION, DEFAULT_PRECISION); scale = MapUtils.getInteger(dataTypeProperties, SCALE, DEFAULT_SCALE); diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/sqlserver/SqlServerURLParser.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/sqlserver/SqlServerURLParser.java index cb512386f844..2184b5077dee 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/sqlserver/SqlServerURLParser.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/sqlserver/SqlServerURLParser.java @@ -24,6 +24,7 @@ import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.LinkedHashMap; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -49,7 +50,8 @@ public static JdbcUrlUtil.UrlInfo parse(String url) { props.entrySet().stream() .collect( Collectors.toMap( - e -> e.getKey().toUpperCase(), Map.Entry::getValue)); + e -> e.getKey().toUpperCase(Locale.ROOT), + Map.Entry::getValue)); serverName = propsWithUpperCaseKey.get("SERVERNAME"); instanceName = propsWithUpperCaseKey.get("INSTANCENAME"); dbInstance = propsWithUpperCaseKey.getOrDefault("DATABASENAME", props.get("DATABASE")); diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/xugu/XuguCatalog.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/xugu/XuguCatalog.java index e10886e6d912..c9c4cd828b34 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/xugu/XuguCatalog.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/xugu/XuguCatalog.java @@ -41,6 +41,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.stream.Collectors; @@ -157,13 +158,13 @@ public boolean databaseExists(String databaseName) throws CatalogException { } try { return querySQLResultExists( - defaultUrl, getDatabaseWithConditionSql(databaseName.toUpperCase())); + defaultUrl, getDatabaseWithConditionSql(databaseName.toUpperCase(Locale.ROOT))); } catch (SeaTunnelRuntimeException e) { if (e.getSeaTunnelErrorCode().getCode().equals(UNSUPPORTED_METHOD.getCode())) { log.warn( "The catalog: {} is not supported the getDatabaseWithConditionSql for databaseExists", this.catalogName); - return listDatabases().contains(databaseName.toUpperCase()); + return listDatabases().contains(databaseName.toUpperCase(Locale.ROOT)); } throw e; } catch (SQLException e) { diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/yashandb/YashanDbCatalog.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/yashandb/YashanDbCatalog.java index cd9a1b849acb..09dfc5865b4d 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/yashandb/YashanDbCatalog.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/yashandb/YashanDbCatalog.java @@ -44,6 +44,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Set; import java.util.stream.Collectors; @@ -153,7 +154,11 @@ protected Column buildColumn(ResultSet resultSet) throws SQLException { // DATA_TYPE from all_tab_columns may include precision like TIMESTAMP(6), // strip parenthesized digits to get the pure type name String typeName = - resultSet.getString("DATA_TYPE").toUpperCase().replaceAll("\\(\\d+\\)", "").trim(); + resultSet + .getString("DATA_TYPE") + .toUpperCase(Locale.ROOT) + .replaceAll("\\(\\d+\\)", "") + .trim(); // e.g NUMBER(10, 2) String fullTypeName = resultSet.getString("FULL_TYPE_NAME"); long columnLength = resultSet.getLong("COLUMN_LENGTH"); diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/db2/DB2TypeConverter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/db2/DB2TypeConverter.java index 73a95337f150..9c7a6a15bcac 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/db2/DB2TypeConverter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/db2/DB2TypeConverter.java @@ -32,6 +32,8 @@ import com.google.auto.service.AutoService; import lombok.extern.slf4j.Slf4j; +import java.util.Locale; + // reference https://www.ibm.com/docs/en/db2/11.5?topic=statements-create-table#r0000927__title__52 @Slf4j @AutoService(TypeConverter.class) @@ -106,7 +108,7 @@ public Column convert(BasicTypeDefine typeDefine) { .defaultValue(typeDefine.getDefaultValue()) .comment(typeDefine.getComment()); - String db2Type = typeDefine.getDataType().toUpperCase(); + String db2Type = typeDefine.getDataType().toUpperCase(Locale.ROOT); switch (db2Type) { case DB2_BOOLEAN: builder.sourceType(DB2_BOOLEAN); diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/dm/DmdbTypeConverter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/dm/DmdbTypeConverter.java index 226789328310..c5ad5aaa2dd4 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/dm/DmdbTypeConverter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/dm/DmdbTypeConverter.java @@ -32,6 +32,8 @@ import com.google.auto.service.AutoService; import lombok.extern.slf4j.Slf4j; +import java.util.Locale; + // reference https://eco.dameng.com/document/dm/zh-cn/sql-dev/dmpl-sql-datatype.html @Slf4j @AutoService(TypeConverter.class) @@ -125,7 +127,7 @@ public Column convert(BasicTypeDefine typeDefine) { .defaultValue(typeDefine.getDefaultValue()) .comment(typeDefine.getComment()); - String dmType = typeDefine.getDataType().toUpperCase(); + String dmType = typeDefine.getDataType().toUpperCase(Locale.ROOT); if (dmType.startsWith("TIMESTAMP(") && dmType.endsWith(" WITH LOCAL TIME ZONE")) { dmType = DM_TIMESTAMP_WITH_LOCAL_TIME_ZONE; } diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/gbase8a/Gbase8aTypeMapper.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/gbase8a/Gbase8aTypeMapper.java index 84935274f1d2..fcabd50d5b3f 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/gbase8a/Gbase8aTypeMapper.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/gbase8a/Gbase8aTypeMapper.java @@ -30,6 +30,7 @@ import java.sql.ResultSetMetaData; import java.sql.SQLException; +import java.util.Locale; @Slf4j public class Gbase8aTypeMapper implements JdbcDialectTypeMapper { @@ -64,7 +65,7 @@ public class Gbase8aTypeMapper implements JdbcDialectTypeMapper { @Override public SeaTunnelDataType mapping(ResultSetMetaData metadata, int colIndex) throws SQLException { - String gbase8aType = metadata.getColumnTypeName(colIndex).toUpperCase(); + String gbase8aType = metadata.getColumnTypeName(colIndex).toUpperCase(Locale.ROOT); int precision = metadata.getPrecision(colIndex); int scale = metadata.getScale(colIndex); switch (gbase8aType) { diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/hive/HiveTypeMapper.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/hive/HiveTypeMapper.java index 54057ddead5a..b859670ff771 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/hive/HiveTypeMapper.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/hive/HiveTypeMapper.java @@ -30,6 +30,7 @@ import java.sql.ResultSetMetaData; import java.sql.SQLException; +import java.util.Locale; public class HiveTypeMapper implements JdbcDialectTypeMapper { @@ -68,7 +69,7 @@ public class HiveTypeMapper implements JdbcDialectTypeMapper { @Override public SeaTunnelDataType mapping(ResultSetMetaData metadata, int colIndex) throws SQLException { - String columnType = metadata.getColumnTypeName(colIndex).toUpperCase(); + String columnType = metadata.getColumnTypeName(colIndex).toUpperCase(Locale.ROOT); int precision = metadata.getPrecision(colIndex); switch (columnType) { case HIVE_TINYINT: diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/iris/IrisTypeConverter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/iris/IrisTypeConverter.java index 1baee2ddad1c..e2c1072c193b 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/iris/IrisTypeConverter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/iris/IrisTypeConverter.java @@ -31,6 +31,7 @@ import com.google.auto.service.AutoService; import lombok.extern.slf4j.Slf4j; +import java.util.Locale; import java.util.Objects; /** @@ -144,7 +145,7 @@ public Column convert(BasicTypeDefine typeDefine) { .nullable(typeDefine.isNullable()) .defaultValue(typeDefine.getDefaultValue()) .comment(typeDefine.getComment()); - String irisDataType = typeDefine.getDataType().toUpperCase(); + String irisDataType = typeDefine.getDataType().toUpperCase(Locale.ROOT); long charOrBinaryLength = Objects.nonNull(typeDefineLength) && typeDefineLength > 0 ? typeDefineLength : 1; switch (irisDataType) { diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/kingbase/KingbaseTypeConverter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/kingbase/KingbaseTypeConverter.java index 08133ba83d4f..9cea19a8aa98 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/kingbase/KingbaseTypeConverter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/kingbase/KingbaseTypeConverter.java @@ -37,6 +37,7 @@ import lombok.extern.slf4j.Slf4j; import java.sql.Types; +import java.util.Locale; // reference https://help.kingbase.com.cn/v8/development/sql-plsql/sql/datatype.html#id2 @Slf4j @@ -74,7 +75,7 @@ public Column convert(BasicTypeDefine typeDefine) { .defaultValue(typeDefine.getDefaultValue()) .comment(typeDefine.getComment()); - String kingbaseDataType = typeDefine.getDataType().toUpperCase(); + String kingbaseDataType = typeDefine.getDataType().toUpperCase(Locale.ROOT); switch (kingbaseDataType) { // MySQL compatibility - only types not in PostgresTypeConverter // int not in PG (PG has SMALLINT/INTEGER/BIGINT) diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oracle/OracleTypeConverter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oracle/OracleTypeConverter.java index b997367485c9..dc6e9f63791f 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oracle/OracleTypeConverter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oracle/OracleTypeConverter.java @@ -33,6 +33,8 @@ import com.google.auto.service.AutoService; import lombok.extern.slf4j.Slf4j; +import java.util.Locale; + // reference https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Data-Types.html @Slf4j @AutoService(TypeConverter.class) @@ -127,7 +129,7 @@ public Column convert(BasicTypeDefine typeDefine) { .defaultValue(typeDefine.getDefaultValue()) .comment(typeDefine.getComment()); - String oracleType = typeDefine.getDataType().toUpperCase(); + String oracleType = typeDefine.getDataType().toUpperCase(Locale.ROOT); if (oracleType.startsWith(ORACLE_INTERVAL)) { // The JDBC driver may report precision qualifiers in the type name, // e.g. "INTERVAL DAY(2) TO SECOND(6)", strip them for exact matching. diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/phoenix/PhoenixTypeConverter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/phoenix/PhoenixTypeConverter.java index 8facf0304385..d0700246593f 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/phoenix/PhoenixTypeConverter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/phoenix/PhoenixTypeConverter.java @@ -32,6 +32,8 @@ import lombok.extern.slf4j.Slf4j; +import java.util.Locale; + import static org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.DatabaseIdentifier.PHOENIX; // reference https://phoenix.apache.org/language/datatypes.html @@ -98,7 +100,7 @@ public Column convert(BasicTypeDefine typeDefine) { .defaultValue(typeDefine.getDefaultValue()) .comment(typeDefine.getComment()); - String phoenixDataType = typeDefine.getDataType().toUpperCase(); + String phoenixDataType = typeDefine.getDataType().toUpperCase(Locale.ROOT); switch (phoenixDataType) { case PHOENIX_BOOLEAN: builder.dataType(BasicType.BOOLEAN_TYPE); diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/presto/PrestoTypeMapper.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/presto/PrestoTypeMapper.java index 2ef5fa921acd..2a7889a8aea3 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/presto/PrestoTypeMapper.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/presto/PrestoTypeMapper.java @@ -28,6 +28,7 @@ import java.sql.ResultSetMetaData; import java.sql.SQLException; +import java.util.Locale; public class PrestoTypeMapper implements JdbcDialectTypeMapper { // ============================data types===================== @@ -66,7 +67,7 @@ public class PrestoTypeMapper implements JdbcDialectTypeMapper { @Override public SeaTunnelDataType mapping(ResultSetMetaData metadata, int colIndex) throws SQLException { - String columnType = metadata.getColumnTypeName(colIndex).toUpperCase(); + String columnType = metadata.getColumnTypeName(colIndex).toUpperCase(Locale.ROOT); // VARCHAR(x) ---> VARCHAR if (columnType.indexOf("(") > -1) { columnType = columnType.split("\\(")[0]; diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/psql/PostgresDialect.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/psql/PostgresDialect.java index 0572163d1223..76db4ec99a75 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/psql/PostgresDialect.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/psql/PostgresDialect.java @@ -50,6 +50,7 @@ import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -275,7 +276,7 @@ public Long approximateRowCntStatement(Connection connection, JdbcSourceTable ta boolean useTableStats = StringUtils.isBlank(table.getQuery()) - || (!table.getQuery().toLowerCase().contains("where") + || (!table.getQuery().toLowerCase(Locale.ROOT).contains("where") && table.getTablePath() != null && !TablePath.DEFAULT .getFullName() @@ -360,7 +361,7 @@ public void applySchemaChange( @Override public boolean needsQuotesWithDefaultValue(BasicTypeDefine columnDefine) { - String pgDataType = columnDefine.getDataType().toLowerCase(); + String pgDataType = columnDefine.getDataType().toLowerCase(Locale.ROOT); switch (pgDataType) { case PG_CHAR: case PG_VARCHAR: diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/psql/PostgresTypeConverter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/psql/PostgresTypeConverter.java index 6d4a3b13ba42..5ed9751cec93 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/psql/PostgresTypeConverter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/psql/PostgresTypeConverter.java @@ -35,6 +35,7 @@ import lombok.extern.slf4j.Slf4j; import java.sql.Types; +import java.util.Locale; import java.util.regex.Pattern; // reference http://www.postgres.cn/docs/13/datatype.html @@ -141,7 +142,7 @@ public Column convert(BasicTypeDefine typeDefine) { .defaultValue(typeDefine.getDefaultValue()) .comment(typeDefine.getComment()); - String pgDataType = typeDefine.getDataType().toLowerCase(); + String pgDataType = typeDefine.getDataType().toLowerCase(Locale.ROOT); switch (pgDataType) { case PG_BOOLEAN: builder.dataType(BasicType.BOOLEAN_TYPE); diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/redshift/RedshiftTypeConverter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/redshift/RedshiftTypeConverter.java index 3b4243030f70..7a188ece1010 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/redshift/RedshiftTypeConverter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/redshift/RedshiftTypeConverter.java @@ -35,6 +35,7 @@ import lombok.extern.slf4j.Slf4j; import java.sql.Types; +import java.util.Locale; // reference https://docs.aws.amazon.com/redshift/latest/dg/c_Supported_data_types.html @Slf4j @@ -91,7 +92,7 @@ public Column convert(BasicTypeDefine typeDefine) { .defaultValue(typeDefine.getDefaultValue()) .comment(typeDefine.getComment()); - String dataType = typeDefine.getDataType().toUpperCase(); + String dataType = typeDefine.getDataType().toUpperCase(Locale.ROOT); switch (dataType) { case REDSHIFT_BOOLEAN: builder.sourceType(REDSHIFT_BOOLEAN); diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/saphana/SapHanaTypeConverter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/saphana/SapHanaTypeConverter.java index b0e4699db2cb..97b5f0e46633 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/saphana/SapHanaTypeConverter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/saphana/SapHanaTypeConverter.java @@ -36,6 +36,7 @@ import java.util.Arrays; import java.util.List; +import java.util.Locale; // reference // https://help.sap.com/docs/SAP_HANA_PLATFORM/4fe29514fd584807ac9f2a04f6754767/20a1569875191014b507cf392724b7eb.html?locale=en-US @@ -126,7 +127,7 @@ public Column convert(BasicTypeDefine typeDefine) { .defaultValue(typeDefine.getDefaultValue()) .comment(typeDefine.getComment()); - String hanaType = typeDefine.getDataType().toUpperCase(); + String hanaType = typeDefine.getDataType().toUpperCase(Locale.ROOT); if (typeDefine.getColumnType().endsWith(" ARRAY")) { typeDefine.setColumnType(typeDefine.getColumnType().replace(" ARRAY", "")); typeDefine.setDataType(removeColumnSizeIfNeed(typeDefine.getColumnType())); diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/snowflake/SnowflakeTypeConverter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/snowflake/SnowflakeTypeConverter.java index ac0a74624771..9958a6bb1b4f 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/snowflake/SnowflakeTypeConverter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/snowflake/SnowflakeTypeConverter.java @@ -32,6 +32,8 @@ import com.google.auto.service.AutoService; import lombok.extern.slf4j.Slf4j; +import java.util.Locale; + // reference https://docs.snowflake.com/en/sql-reference/intro-summary-data-types @Slf4j @AutoService(TypeConverter.class) @@ -100,7 +102,7 @@ public Column convert(BasicTypeDefine typeDefine) { .nullable(typeDefine.isNullable()) .defaultValue(typeDefine.getDefaultValue()) .comment(typeDefine.getComment()); - String dataType = typeDefine.getDataType().toUpperCase(); + String dataType = typeDefine.getDataType().toUpperCase(Locale.ROOT); switch (dataType) { case SNOWFLAKE_SMALLINT: case SNOWFLAKE_TINYINT: diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/sqlite/SqliteTypeMapper.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/sqlite/SqliteTypeMapper.java index 21330996f74f..8dbcb0186359 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/sqlite/SqliteTypeMapper.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/sqlite/SqliteTypeMapper.java @@ -28,6 +28,7 @@ import java.sql.ResultSetMetaData; import java.sql.SQLException; +import java.util.Locale; @Slf4j public class SqliteTypeMapper implements JdbcDialectTypeMapper { @@ -96,7 +97,8 @@ public class SqliteTypeMapper implements JdbcDialectTypeMapper { @Override public SeaTunnelDataType mapping(ResultSetMetaData metadata, int colIndex) throws SQLException { - String columnTypeName = metadata.getColumnTypeName(colIndex).toUpperCase().trim(); + String columnTypeName = + metadata.getColumnTypeName(colIndex).toUpperCase(Locale.ROOT).trim(); switch (columnTypeName) { case SQLITE_BIT: case SQLITE_BOOLEAN: diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/tablestore/TablestoreTypeMapper.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/tablestore/TablestoreTypeMapper.java index 36a5eabc27e4..bc9e1c6af4ad 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/tablestore/TablestoreTypeMapper.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/tablestore/TablestoreTypeMapper.java @@ -28,6 +28,7 @@ import java.sql.ResultSetMetaData; import java.sql.SQLException; +import java.util.Locale; @Slf4j public class TablestoreTypeMapper implements JdbcDialectTypeMapper { @@ -52,7 +53,7 @@ public class TablestoreTypeMapper implements JdbcDialectTypeMapper { @Override public SeaTunnelDataType mapping(ResultSetMetaData metadata, int colIndex) throws SQLException { - String tablestoreServerType = metadata.getColumnTypeName(colIndex).toUpperCase(); + String tablestoreServerType = metadata.getColumnTypeName(colIndex).toUpperCase(Locale.ROOT); switch (tablestoreServerType) { case TABLESTORE_BOOL: return BasicType.BOOLEAN_TYPE; diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/teradata/TeradataTypeMapper.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/teradata/TeradataTypeMapper.java index f8bd0e99eaf6..771aded5cf8b 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/teradata/TeradataTypeMapper.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/teradata/TeradataTypeMapper.java @@ -28,6 +28,7 @@ import java.sql.ResultSetMetaData; import java.sql.SQLException; +import java.util.Locale; public class TeradataTypeMapper implements JdbcDialectTypeMapper { @@ -61,7 +62,7 @@ public class TeradataTypeMapper implements JdbcDialectTypeMapper { @Override public SeaTunnelDataType mapping(ResultSetMetaData metadata, int colIndex) throws SQLException { - String teradataType = metadata.getColumnTypeName(colIndex).toUpperCase(); + String teradataType = metadata.getColumnTypeName(colIndex).toUpperCase(Locale.ROOT); switch (teradataType) { case TERADATA_BYTEINT: return BasicType.BYTE_TYPE; diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/vertica/VerticaTypeMapper.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/vertica/VerticaTypeMapper.java index b9e27d170093..22481137348d 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/vertica/VerticaTypeMapper.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/vertica/VerticaTypeMapper.java @@ -32,6 +32,7 @@ import java.sql.ResultSetMetaData; import java.sql.SQLException; +import java.util.Locale; public class VerticaTypeMapper implements JdbcDialectTypeMapper { @@ -92,7 +93,7 @@ public class VerticaTypeMapper implements JdbcDialectTypeMapper { @Override public SeaTunnelDataType mapping(ResultSetMetaData metadata, int colIndex) throws SQLException { - String type = metadata.getColumnTypeName(colIndex).toUpperCase(); + String type = metadata.getColumnTypeName(colIndex).toUpperCase(Locale.ROOT); int precision = metadata.getPrecision(colIndex); int scale = metadata.getScale(colIndex); switch (type) { diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/xugu/XuguTypeConverter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/xugu/XuguTypeConverter.java index 8074e6ce4e72..ead1ff4ef859 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/xugu/XuguTypeConverter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/xugu/XuguTypeConverter.java @@ -32,6 +32,8 @@ import com.google.auto.service.AutoService; import lombok.extern.slf4j.Slf4j; +import java.util.Locale; + // reference // https://docs.xugudb.com/content/reference/sql/datatype/numerical @Slf4j @@ -104,7 +106,7 @@ public Column convert(BasicTypeDefine typeDefine) { .defaultValue(typeDefine.getDefaultValue()) .comment(typeDefine.getComment()); - String xuguDataType = typeDefine.getDataType().toUpperCase(); + String xuguDataType = typeDefine.getDataType().toUpperCase(Locale.ROOT); switch (xuguDataType) { case XUGU_BOOLEAN: case XUGU_BOOL: diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/yashandb/YashanDbTypeConverter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/yashandb/YashanDbTypeConverter.java index 022dc54c2dfa..cc6168f3ed3d 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/yashandb/YashanDbTypeConverter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/yashandb/YashanDbTypeConverter.java @@ -37,6 +37,7 @@ import java.util.Collections; import java.util.EnumSet; +import java.util.Locale; import java.util.Set; // reference @@ -164,7 +165,7 @@ public Column convert(BasicTypeDefine typeDefine) { .defaultValue(typeDefine.getDefaultValue()) .comment(typeDefine.getComment()); - String yashanType = typeDefine.getDataType().toUpperCase(); + String yashanType = typeDefine.getDataType().toUpperCase(Locale.ROOT); switch (yashanType) { // ====================== Integer types ====================== diff --git a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcTypeConverterLocaleTest.java b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcTypeConverterLocaleTest.java new file mode 100644 index 000000000000..46ca1bb7e867 --- /dev/null +++ b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcTypeConverterLocaleTest.java @@ -0,0 +1,90 @@ +/* + * 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.seatunnel.connectors.seatunnel.jdbc.internal.dialect; + +import org.apache.seatunnel.api.table.converter.BasicTypeDefine; +import org.apache.seatunnel.api.table.type.BasicType; +import org.apache.seatunnel.api.table.type.DecimalType; +import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.db2.DB2TypeConverter; +import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.dm.DmdbTypeConverter; +import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.iris.IrisTypeConverter; +import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.kingbase.KingbaseTypeConverter; +import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oracle.OracleTypeConverter; +import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.psql.PostgresTypeConverter; +import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.redshift.RedshiftTypeConverter; +import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.saphana.SapHanaTypeConverter; +import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.xugu.XuguTypeConverter; +import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.yashandb.YashanDbTypeConverter; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Locale; + +/** Verifies that the dialect type converters do not depend on the JVM default locale. */ +public class JdbcTypeConverterLocaleTest { + + @Test + public void testConvertUnderTurkishLocale() { + Locale defaultLocale = Locale.getDefault(); + try { + Locale.setDefault(new Locale("tr", "TR")); + + Assertions.assertEquals( + BasicType.INT_TYPE, + DB2TypeConverter.INSTANCE.convert(typeDefine("integer")).getDataType()); + Assertions.assertEquals( + BasicType.INT_TYPE, + DmdbTypeConverter.INSTANCE.convert(typeDefine("int")).getDataType()); + Assertions.assertEquals( + BasicType.INT_TYPE, + IrisTypeConverter.INSTANCE.convert(typeDefine("integer")).getDataType()); + Assertions.assertEquals( + BasicType.INT_TYPE, + KingbaseTypeConverter.INSTANCE.convert(typeDefine("int")).getDataType()); + Assertions.assertEquals( + new DecimalType(OracleTypeConverter.DEFAULT_PRECISION, 0), + OracleTypeConverter.INSTANCE.convert(typeDefine("integer")).getDataType()); + Assertions.assertEquals( + BasicType.INT_TYPE, + PostgresTypeConverter.INSTANCE.convert(typeDefine("INT4")).getDataType()); + Assertions.assertEquals( + BasicType.INT_TYPE, + RedshiftTypeConverter.INSTANCE.convert(typeDefine("integer")).getDataType()); + Assertions.assertEquals( + BasicType.INT_TYPE, + SapHanaTypeConverter.INSTANCE.convert(typeDefine("integer")).getDataType()); + Assertions.assertEquals( + BasicType.INT_TYPE, + XuguTypeConverter.INSTANCE.convert(typeDefine("integer")).getDataType()); + Assertions.assertEquals( + BasicType.INT_TYPE, + YashanDbTypeConverter.INSTANCE.convert(typeDefine("integer")).getDataType()); + } finally { + Locale.setDefault(defaultLocale); + } + } + + private static BasicTypeDefine typeDefine(String dataType) { + return BasicTypeDefine.builder() + .name("test") + .columnType(dataType) + .dataType(dataType) + .build(); + } +} From 958bf389f7b58b798a3f0f472be43b32e9584f2f Mon Sep 17 00:00:00 2001 From: sharpca <110613026+sharpca@users.noreply.github.com> Date: Sun, 1 Nov 2026 15:40:52 +0800 Subject: [PATCH 2/8] [Fix][Connector-V2] Cover the remaining locale dependent case conversions in connector-jdbc The quick scan of the rest of connector-jdbc requested on #13079 showed that the row count WHERE checks, the case insensitive collation normalisation and the where_condition prefix validation still lower-case the argument with the JVM default locale. Pass Locale.ROOT to those calls as well, so the remaining conversions in connector-jdbc behave like the type converters fixed in this PR. The six dialects use the same pattern that PostgresDialect already follows here; CollationBasedSplitter and JdbcSourceFactory are included because the locale changes their comparison result rather than only the spelling. --- .../seatunnel/jdbc/internal/dialect/mysql/MysqlDialect.java | 2 +- .../jdbc/internal/dialect/oceanbase/OceanBaseMysqlDialect.java | 3 ++- .../seatunnel/jdbc/internal/dialect/oracle/OracleDialect.java | 3 ++- .../jdbc/internal/dialect/redshift/RedshiftDialect.java | 3 ++- .../jdbc/internal/dialect/sqlserver/SqlServerDialect.java | 3 ++- .../jdbc/internal/dialect/yashandb/YashanDbDialect.java | 3 ++- .../seatunnel/jdbc/source/CollationBasedSplitter.java | 3 ++- .../connectors/seatunnel/jdbc/source/JdbcSourceFactory.java | 3 ++- 8 files changed, 15 insertions(+), 8 deletions(-) diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlDialect.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlDialect.java index 685b19efad20..046625d8aa25 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlDialect.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlDialect.java @@ -205,7 +205,7 @@ public Long approximateRowCntStatement(Connection connection, JdbcSourceTable ta boolean useTableStats = StringUtils.isBlank(table.getQuery()) - || (!table.getQuery().toLowerCase().contains("where") + || (!table.getQuery().toLowerCase(Locale.ROOT).contains("where") && table.getTablePath() != null && !TablePath.DEFAULT .getFullName() diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMysqlDialect.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMysqlDialect.java index f505559bad56..d147ac4d788c 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMysqlDialect.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMysqlDialect.java @@ -42,6 +42,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -204,7 +205,7 @@ public Long approximateRowCntStatement(Connection connection, JdbcSourceTable ta boolean useTableStats = StringUtils.isBlank(table.getQuery()) - || (!table.getQuery().toLowerCase().contains("where") + || (!table.getQuery().toLowerCase(Locale.ROOT).contains("where") && table.getTablePath() != null && !TablePath.DEFAULT .getFullName() diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oracle/OracleDialect.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oracle/OracleDialect.java index 2612b2c5cba8..b565a85b8fa6 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oracle/OracleDialect.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oracle/OracleDialect.java @@ -51,6 +51,7 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -242,7 +243,7 @@ public Long approximateRowCntStatement(Connection connection, JdbcSourceTable ta boolean useTableStats = StringUtils.isBlank(query) - || (!query.toLowerCase().contains("where") + || (!query.toLowerCase(Locale.ROOT).contains("where") && table.getTablePath() != null && !TablePath.DEFAULT .getFullName() diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/redshift/RedshiftDialect.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/redshift/RedshiftDialect.java index 55df1558ae44..bd3a4a220baf 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/redshift/RedshiftDialect.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/redshift/RedshiftDialect.java @@ -33,6 +33,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.util.Locale; import java.util.Optional; public class RedshiftDialect implements JdbcDialect { @@ -145,7 +146,7 @@ public Long approximateRowCntStatement(Connection connection, JdbcSourceTable ta boolean useTableStats = StringUtils.isBlank(table.getQuery()) - || (!table.getQuery().toLowerCase().contains("where") + || (!table.getQuery().toLowerCase(Locale.ROOT).contains("where") && table.getTablePath() != null && !TablePath.DEFAULT .getFullName() diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/sqlserver/SqlServerDialect.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/sqlserver/SqlServerDialect.java index 9639d4f123af..865773a51725 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/sqlserver/SqlServerDialect.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/sqlserver/SqlServerDialect.java @@ -45,6 +45,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Locale; import java.util.Optional; import java.util.stream.Collectors; @@ -199,7 +200,7 @@ public Long approximateRowCntStatement(Connection connection, JdbcSourceTable ta boolean useTableStats = StringUtils.isBlank(table.getQuery()) - || (!table.getQuery().toLowerCase().contains("where") + || (!table.getQuery().toLowerCase(Locale.ROOT).contains("where") && table.getTablePath() != null && !TablePath.DEFAULT .getFullName() diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/yashandb/YashanDbDialect.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/yashandb/YashanDbDialect.java index a8bd0ef9cfc6..52c9b6990c99 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/yashandb/YashanDbDialect.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/yashandb/YashanDbDialect.java @@ -44,6 +44,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -209,7 +210,7 @@ public Long approximateRowCntStatement(Connection connection, JdbcSourceTable ta boolean useTableStats = StringUtils.isBlank(query) - || (!query.toLowerCase().contains("where") + || (!query.toLowerCase(Locale.ROOT).contains("where") && table.getTablePath() != null && !TablePath.DEFAULT .getFullName() diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/CollationBasedSplitter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/CollationBasedSplitter.java index eb98a28a4cf9..c6f5d6a50881 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/CollationBasedSplitter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/CollationBasedSplitter.java @@ -21,6 +21,7 @@ import java.math.BigInteger; import java.util.Arrays; +import java.util.Locale; @Slf4j public class CollationBasedSplitter { @@ -140,7 +141,7 @@ private static String stringToAsciiString( expectedLength, paddingAtEnd, isCaseInsensitive); - String str = isCaseInsensitive ? s.toLowerCase() : s; + String str = isCaseInsensitive ? s.toLowerCase(Locale.ROOT) : s; char[] paddedChars = new char[expectedLength]; if (paddingAtEnd) { diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/JdbcSourceFactory.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/JdbcSourceFactory.java index 573664ad1f8c..21dd375ff6d6 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/JdbcSourceFactory.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/JdbcSourceFactory.java @@ -41,6 +41,7 @@ import java.io.Serializable; import java.util.List; +import java.util.Locale; import java.util.stream.Collectors; @Slf4j @@ -184,7 +185,7 @@ public String description() { @Override public boolean evaluate(ReadonlyConfig config, String value) { - return value == null || value.toLowerCase().startsWith("where"); + return value == null || value.toLowerCase(Locale.ROOT).startsWith("where"); } } } From dfb9ff2cdc24b62418fab0732b206414def5bf40 Mon Sep 17 00:00:00 2001 From: sharpca <110613026+sharpca@users.noreply.github.com> Date: Sun, 1 Nov 2026 22:06:56 +0800 Subject: [PATCH 3/8] [Fix][Connector-V2][JDBC] Keep this PR disjoint and cover the split path Drop MySqlCatalog, OceanBaseMySqlCatalog and SqlServerURLParser: those files are fixed by #13076 and #13077, and the OceanBase edit conflicts with the removal of the second toUpperCase() there, so leave them to those PRs. Add RedshiftCatalogLocaleTest, which drives RedshiftCatalog.buildColumn with a mocked result set under tr-TR, so catalog schema discovery is covered and not only the converters. Extend CharsetBasedSplitterTest with an I/i case that asserts the case insensitive fold of upper case I is i and that the encoded range decodes back to i. Restore the base, DISPLAY and FORMAT locale defaults and mark the locale mutating tests with @ResourceLock, so they cannot leak state into other tests. --- .../jdbc/catalog/mysql/MySqlCatalog.java | 3 +- .../oceanbase/OceanBaseMySqlCatalog.java | 5 +- .../catalog/sqlserver/SqlServerURLParser.java | 4 +- .../redshift/RedshiftCatalogLocaleTest.java | 86 +++++++++++++++++++ .../dialect/JdbcTypeConverterLocaleTest.java | 21 ++++- .../jdbc/source/CharsetBasedSplitterTest.java | 39 +++++++++ 6 files changed, 147 insertions(+), 11 deletions(-) create mode 100644 seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/redshift/RedshiftCatalogLocaleTest.java diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/mysql/MySqlCatalog.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/mysql/MySqlCatalog.java index 075405a04fa6..4a3bdf642caf 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/mysql/MySqlCatalog.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/mysql/MySqlCatalog.java @@ -49,7 +49,6 @@ import java.sql.Statement; import java.util.Iterator; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Properties; @@ -222,7 +221,7 @@ protected Column buildColumn(ResultSet resultSet) throws SQLException { // e.g. tinyint(1) unsigned String columnType = resultSet.getString("COLUMN_TYPE"); // e.g. tinyint - String dataType = resultSet.getString("DATA_TYPE").toUpperCase(Locale.ROOT); + String dataType = resultSet.getString("DATA_TYPE").toUpperCase(); String comment = resultSet.getString("COLUMN_COMMENT"); Object defaultValue = resultSet.getObject("COLUMN_DEFAULT"); String isNullableStr = resultSet.getString("IS_NULLABLE"); diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseMySqlCatalog.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseMySqlCatalog.java index b6cd5442287b..1fa3a57c9ddf 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseMySqlCatalog.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseMySqlCatalog.java @@ -49,7 +49,6 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; -import java.util.Locale; @Slf4j public class OceanBaseMySqlCatalog extends AbstractJdbcCatalog { @@ -143,12 +142,12 @@ protected Column buildColumn(ResultSet resultSet) throws SQLException { // e.g. tinyint(1) unsigned String columnType = resultSet.getString("COLUMN_TYPE"); // e.g. tinyint - String dataType = resultSet.getString("DATA_TYPE").toUpperCase(Locale.ROOT); + String dataType = resultSet.getString("DATA_TYPE").toUpperCase(); String comment = resultSet.getString("COLUMN_COMMENT"); Object defaultValue = resultSet.getObject("COLUMN_DEFAULT"); String isNullableStr = resultSet.getString("IS_NULLABLE"); - if (dataType.toUpperCase(Locale.ROOT).startsWith("VECTOR")) { + if (dataType.toUpperCase().startsWith("VECTOR")) { dataType = "VECTOR"; } diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/sqlserver/SqlServerURLParser.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/sqlserver/SqlServerURLParser.java index 2184b5077dee..cb512386f844 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/sqlserver/SqlServerURLParser.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/sqlserver/SqlServerURLParser.java @@ -24,7 +24,6 @@ import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.LinkedHashMap; -import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -50,8 +49,7 @@ public static JdbcUrlUtil.UrlInfo parse(String url) { props.entrySet().stream() .collect( Collectors.toMap( - e -> e.getKey().toUpperCase(Locale.ROOT), - Map.Entry::getValue)); + e -> e.getKey().toUpperCase(), Map.Entry::getValue)); serverName = propsWithUpperCaseKey.get("SERVERNAME"); instanceName = propsWithUpperCaseKey.get("INSTANCENAME"); dbInstance = propsWithUpperCaseKey.getOrDefault("DATABASENAME", props.get("DATABASE")); diff --git a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/redshift/RedshiftCatalogLocaleTest.java b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/redshift/RedshiftCatalogLocaleTest.java new file mode 100644 index 000000000000..ce8e1086cae1 --- /dev/null +++ b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/redshift/RedshiftCatalogLocaleTest.java @@ -0,0 +1,86 @@ +/* + * 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.seatunnel.connectors.seatunnel.jdbc.catalog.redshift; + +import org.apache.seatunnel.api.table.type.BasicType; +import org.apache.seatunnel.common.utils.JdbcUrlUtil; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceLock; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Locale; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Verifies that catalog schema discovery does not depend on the JVM default locale. + * + *

{@link RedshiftCatalog#buildColumn(ResultSet)} upper-cases the driver column type before it is + * matched against the dialect constants. Under a Turkish locale a bare {@code toUpperCase()} turns + * {@code integer} into {@code İNTEGER} ({@code U+0130}) and the column is no longer recognized, so + * this test drives the real discovery method with a mocked result set instead of calling the type + * converter directly. All three locale categories are restored afterwards. + */ +@ResourceLock("java.util.Locale") +public class RedshiftCatalogLocaleTest { + + @Test + public void testBuildColumnUnderTurkishLocale() throws SQLException { + Locale base = Locale.getDefault(); + Locale display = Locale.getDefault(Locale.Category.DISPLAY); + Locale format = Locale.getDefault(Locale.Category.FORMAT); + try { + Locale.setDefault(new Locale("tr", "TR")); + + Assertions.assertEquals( + BasicType.INT_TYPE, catalog().buildColumn(column("integer")).getDataType()); + Assertions.assertEquals( + BasicType.LONG_TYPE, catalog().buildColumn(column("bigint")).getDataType()); + } finally { + Locale.setDefault(base); + Locale.setDefault(Locale.Category.DISPLAY, display); + Locale.setDefault(Locale.Category.FORMAT, format); + } + } + + private static RedshiftCatalog catalog() { + return new RedshiftCatalog( + "redshift", + "user", + "pwd", + JdbcUrlUtil.getUrlInfo("jdbc:redshift://127.0.0.1:5439/test"), + "public", + null); + } + + private static ResultSet column(String dataType) throws SQLException { + ResultSet resultSet = mock(ResultSet.class); + when(resultSet.getString("COLUMN_NAME")).thenReturn("test"); + when(resultSet.getString("DATA_TYPE")).thenReturn(dataType); + when(resultSet.getLong("NUMERIC_PRECISION")).thenReturn(10L); + when(resultSet.getInt("NUMERIC_SCALE")).thenReturn(0); + when(resultSet.getLong("CHARACTER_MAXIMUM_LENGTH")).thenReturn(0L); + when(resultSet.getObject("COLUMN_DEFAULT")).thenReturn(null); + when(resultSet.getString("IS_NULLABLE")).thenReturn("YES"); + return resultSet; + } +} diff --git a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcTypeConverterLocaleTest.java b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcTypeConverterLocaleTest.java index 46ca1bb7e867..a79f27daf717 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcTypeConverterLocaleTest.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcTypeConverterLocaleTest.java @@ -33,15 +33,28 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceLock; import java.util.Locale; -/** Verifies that the dialect type converters do not depend on the JVM default locale. */ +/** + * Verifies that the dialect type converters do not depend on the JVM default locale. + * + *

The test switches the default locale to Turkish, where {@code i} upper-cases to a dotted + * capital {@code I} ({@code U+0130}), and asserts that each affected converter still maps the + * source type name to the same SeaTunnel type. All three locale categories are captured before the + * switch and restored afterwards, so the test cannot leak a locale into other tests sharing the + * JVM. The {@link ResourceLock} annotation keeps it out of the way if JUnit parallel execution is + * enabled later. + */ +@ResourceLock("java.util.Locale") public class JdbcTypeConverterLocaleTest { @Test public void testConvertUnderTurkishLocale() { - Locale defaultLocale = Locale.getDefault(); + Locale base = Locale.getDefault(); + Locale display = Locale.getDefault(Locale.Category.DISPLAY); + Locale format = Locale.getDefault(Locale.Category.FORMAT); try { Locale.setDefault(new Locale("tr", "TR")); @@ -76,7 +89,9 @@ public void testConvertUnderTurkishLocale() { BasicType.INT_TYPE, YashanDbTypeConverter.INSTANCE.convert(typeDefine("integer")).getDataType()); } finally { - Locale.setDefault(defaultLocale); + Locale.setDefault(base); + Locale.setDefault(Locale.Category.DISPLAY, display); + Locale.setDefault(Locale.Category.FORMAT, format); } } diff --git a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/CharsetBasedSplitterTest.java b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/CharsetBasedSplitterTest.java index c877e30827a5..a398a52e4543 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/CharsetBasedSplitterTest.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/CharsetBasedSplitterTest.java @@ -17,16 +17,20 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.source; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceLock; import lombok.extern.slf4j.Slf4j; import java.math.BigInteger; +import java.util.Locale; import static org.junit.jupiter.api.Assertions.assertEquals; @Slf4j +@ResourceLock("java.util.Locale") public class CharsetBasedSplitterTest { private static final String DEFAULT_CHARSET = "0123456789abcdefghijklmnopqrstuvwxyz"; @@ -186,4 +190,39 @@ public void testRandomStrings() { assertEquals(randomString, decoded.trim()); } } + + @Test + @DisplayName("Case insensitive encoding of I is stable under a Turkish default locale") + public void testCaseInsensitiveEncodingUnderTurkishLocale() { + Locale base = Locale.getDefault(); + Locale display = Locale.getDefault(Locale.Category.DISPLAY); + Locale format = Locale.getDefault(Locale.Category.FORMAT); + int radix = DEFAULT_CHARSET.length() + 1; + try { + Locale.setDefault(Locale.ROOT); + BigInteger lower = + CollationBasedSplitter.encodeStringToNumericRange( + "i", 1, true, true, DEFAULT_CHARSET, radix); + Assertions.assertEquals( + lower, + CollationBasedSplitter.encodeStringToNumericRange( + "I", 1, true, true, DEFAULT_CHARSET, radix)); + + Locale.setDefault(new Locale("tr", "TR")); + Assertions.assertEquals( + lower, + CollationBasedSplitter.encodeStringToNumericRange( + "I", 1, true, true, DEFAULT_CHARSET, radix), + "upper case I must fold to i and not to the dotless \u0131"); + Assertions.assertEquals( + "i", + CollationBasedSplitter.decodeNumericRangeToString( + lower.toString(), 1, radix, DEFAULT_CHARSET) + .trim()); + } finally { + Locale.setDefault(base); + Locale.setDefault(Locale.Category.DISPLAY, display); + Locale.setDefault(Locale.Category.FORMAT, format); + } + } } From 704af8840297933b6a6775fd7fa035f3cfdaf97b Mon Sep 17 00:00:00 2001 From: sharpca <110613026+sharpca@users.noreply.github.com> Date: Mon, 2 Nov 2026 02:23:06 +0800 Subject: [PATCH 4/8] [Test][E2E][JDBC] Cover Turkish locale string range splits end to end Add a focused case to JdbcMysqlSplitIT: a case insensitive utf8mb4_general_ci table holding mixed I/i identifiers, discovered and split with the default locale set to tr-TR, then read through the real split statements. Every row must be read exactly once across all splits, so a locale dependent fold that shifts the split boundaries shows up as a missing or duplicated row. The locale is applied before catalog discovery and restored afterwards. --- .../seatunnel/jdbc/JdbcMysqlSplitIT.java | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-7/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/JdbcMysqlSplitIT.java b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-7/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/JdbcMysqlSplitIT.java index fe850d5f9c31..0ba46f86ed7a 100644 --- a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-7/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/JdbcMysqlSplitIT.java +++ b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-7/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/JdbcMysqlSplitIT.java @@ -69,6 +69,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -873,4 +874,105 @@ public void testFixedNumericSplitsReadAllRows() throws Exception { } } } + + @Test + public void testFixedStringSplitsReadAllRowsUnderTurkishLocale() throws Exception { + String tableName = "fixed_string_locale"; + String qualifiedTable = MYSQL_DATABASE + "." + tableName; + List ids = + Arrays.asList( + "I1", "i2", "I3", "i4", "I5", "i6", "I7", "i8", "I9", "i10", + "I11", "i12", "I13", "i14", "I15", "i16", "I17", "i18", "I19", + "i20"); + try (Connection connection = getJdbcConnection(); + Statement statement = connection.createStatement()) { + statement.execute( + "CREATE TABLE " + + qualifiedTable + + " (id VARCHAR(32) PRIMARY KEY) COLLATE utf8mb4_general_ci"); + try { + try (PreparedStatement insert = + connection.prepareStatement( + "INSERT INTO " + qualifiedTable + " (id) VALUES (?)")) { + for (String id : ids) { + insert.setString(1, id); + insert.addBatch(); + } + insert.executeBatch(); + } + + String query = "SELECT id FROM " + qualifiedTable; + Map configMap = new HashMap<>(); + configMap.put("url", mysqlUrlInfo.getUrlWithDatabase().get()); + configMap.put("driver", "com.mysql.cj.jdbc.Driver"); + configMap.put("username", MYSQL_USERNAME); + configMap.put("password", MYSQL_PASSWORD); + configMap.put("query", query); + configMap.put("partition_column", "id"); + configMap.put("split.string_split_mode", "charset_based"); + + TablePath tablePath = TablePath.of(MYSQL_DATABASE, tableName); + Locale base = Locale.getDefault(); + Locale display = Locale.getDefault(Locale.Category.DISPLAY); + Locale format = Locale.getDefault(Locale.Category.FORMAT); + try (MySqlCatalog mySqlCatalog = + new MySqlCatalog( + "mysql", + MYSQL_USERNAME, + MYSQL_PASSWORD, + mysqlUrlInfo, + null); + FixedChunkSplitter splitter = getFixedChunkSplitter(configMap)) { + try { + Locale.setDefault(new Locale("tr", "TR")); + + mySqlCatalog.open(); + CatalogTable table = mySqlCatalog.getTable(tablePath); + JdbcSourceTable jdbcSourceTable = + JdbcSourceTable.builder() + .tablePath(tablePath) + .catalogTable(table) + .query(query) + .partitionColumn("id") + .partitionNumber(4) + .build(); + + Map readCounts = new HashMap<>(); + int splitCount = 0; + for (JdbcSourceSplit split : splitter.generateSplits(jdbcSourceTable)) { + splitCount++; + try (PreparedStatement splitStatement = + splitter.generateSplitStatement( + split, table.getTableSchema()); + ResultSet resultSet = splitStatement.executeQuery()) { + while (resultSet.next()) { + readCounts.merge(resultSet.getString(1), 1, Integer::sum); + } + } + } + + Assertions.assertTrue( + splitCount > 1, + "expected more than one split, got " + splitCount); + Assertions.assertEquals( + ids.size(), + readCounts.size(), + "row identities read across splits differ: " + readCounts.keySet()); + for (String id : ids) { + Assertions.assertEquals( + 1, + readCounts.getOrDefault(id, 0), + "row " + id + " was not read exactly once"); + } + } finally { + Locale.setDefault(base); + Locale.setDefault(Locale.Category.DISPLAY, display); + Locale.setDefault(Locale.Category.FORMAT, format); + } + } + } finally { + statement.execute("DROP TABLE IF EXISTS " + qualifiedTable); + } + } + } } From cabfb80dd00f1998b726374cae2aa20a0058b59a Mon Sep 17 00:00:00 2001 From: sharpca <110613026+sharpca@users.noreply.github.com> Date: Mon, 2 Nov 2026 13:21:02 +0800 Subject: [PATCH 5/8] [Test][Connector-V2][JDBC] Report the failing converter in the locale test Run each dialect converter as its own parameterized invocation and carry the converter name into the assertion message, so a regression in a single dialect fails naming that converter instead of one combined assertion. Capture the base, DISPLAY and FORMAT defaults before switching to tr-TR and restore them in @AfterEach, which JUnit runs even when the conversion throws, so a Turkish default cannot leak into other tests in the same JVM. --- .../dialect/JdbcTypeConverterLocaleTest.java | 146 ++++++++++++------ 1 file changed, 97 insertions(+), 49 deletions(-) diff --git a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcTypeConverterLocaleTest.java b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcTypeConverterLocaleTest.java index a79f27daf717..fb18a7ba26ba 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcTypeConverterLocaleTest.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcTypeConverterLocaleTest.java @@ -18,8 +18,10 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect; import org.apache.seatunnel.api.table.converter.BasicTypeDefine; +import org.apache.seatunnel.api.table.converter.TypeConverter; import org.apache.seatunnel.api.table.type.BasicType; import org.apache.seatunnel.api.table.type.DecimalType; +import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.db2.DB2TypeConverter; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.dm.DmdbTypeConverter; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.iris.IrisTypeConverter; @@ -31,68 +33,114 @@ import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.xugu.XuguTypeConverter; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.yashandb.YashanDbTypeConverter; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import java.util.Locale; +import java.util.stream.Stream; /** * Verifies that the dialect type converters do not depend on the JVM default locale. * - *

The test switches the default locale to Turkish, where {@code i} upper-cases to a dotted - * capital {@code I} ({@code U+0130}), and asserts that each affected converter still maps the - * source type name to the same SeaTunnel type. All three locale categories are captured before the - * switch and restored afterwards, so the test cannot leak a locale into other tests sharing the - * JVM. The {@link ResourceLock} annotation keeps it out of the way if JUnit parallel execution is - * enabled later. + *

Every converter runs as its own parameterized invocation, so a regression in a single dialect + * fails with that converter's name instead of hiding inside one combined assertion. + * + *

The default locale is switched to Turkish, where {@code i} upper-cases to a dotted capital + * {@code I} ({@code U+0130}), in {@link BeforeEach} and restored in {@link AfterEach}: the previous + * base, {@code DISPLAY} and {@code FORMAT} defaults are captured before the switch, and JUnit runs + * the restore even when the converter call throws, so a Turkish default cannot leak into other + * tests sharing the JVM. {@link ResourceLock} keeps the class out of the way if parallel execution + * is enabled later. */ @ResourceLock("java.util.Locale") public class JdbcTypeConverterLocaleTest { - @Test - public void testConvertUnderTurkishLocale() { - Locale base = Locale.getDefault(); - Locale display = Locale.getDefault(Locale.Category.DISPLAY); - Locale format = Locale.getDefault(Locale.Category.FORMAT); - try { - Locale.setDefault(new Locale("tr", "TR")); + private Locale base; + private Locale display; + private Locale format; + + @BeforeEach + public void switchToTurkishLocale() { + base = Locale.getDefault(); + display = Locale.getDefault(Locale.Category.DISPLAY); + format = Locale.getDefault(Locale.Category.FORMAT); + Locale.setDefault(new Locale("tr", "TR")); + } + + @AfterEach + public void restoreDefaultLocale() { + Locale.setDefault(base); + Locale.setDefault(Locale.Category.DISPLAY, display); + Locale.setDefault(Locale.Category.FORMAT, format); + } + + @ParameterizedTest(name = "{0} maps {2}") + @MethodSource("converters") + public void convertsSourceTypeUnderTurkishLocale( + String dialect, + TypeConverter converter, + String sourceType, + SeaTunnelDataType expectedType) { + Assertions.assertEquals( + expectedType, + converter.convert(typeDefine(sourceType)).getDataType(), + dialect + " folded " + sourceType + " to the wrong type under tr-TR"); + } - Assertions.assertEquals( - BasicType.INT_TYPE, - DB2TypeConverter.INSTANCE.convert(typeDefine("integer")).getDataType()); - Assertions.assertEquals( - BasicType.INT_TYPE, - DmdbTypeConverter.INSTANCE.convert(typeDefine("int")).getDataType()); - Assertions.assertEquals( - BasicType.INT_TYPE, - IrisTypeConverter.INSTANCE.convert(typeDefine("integer")).getDataType()); - Assertions.assertEquals( - BasicType.INT_TYPE, - KingbaseTypeConverter.INSTANCE.convert(typeDefine("int")).getDataType()); - Assertions.assertEquals( - new DecimalType(OracleTypeConverter.DEFAULT_PRECISION, 0), - OracleTypeConverter.INSTANCE.convert(typeDefine("integer")).getDataType()); - Assertions.assertEquals( - BasicType.INT_TYPE, - PostgresTypeConverter.INSTANCE.convert(typeDefine("INT4")).getDataType()); - Assertions.assertEquals( - BasicType.INT_TYPE, - RedshiftTypeConverter.INSTANCE.convert(typeDefine("integer")).getDataType()); - Assertions.assertEquals( - BasicType.INT_TYPE, - SapHanaTypeConverter.INSTANCE.convert(typeDefine("integer")).getDataType()); - Assertions.assertEquals( - BasicType.INT_TYPE, - XuguTypeConverter.INSTANCE.convert(typeDefine("integer")).getDataType()); - Assertions.assertEquals( - BasicType.INT_TYPE, - YashanDbTypeConverter.INSTANCE.convert(typeDefine("integer")).getDataType()); - } finally { - Locale.setDefault(base); - Locale.setDefault(Locale.Category.DISPLAY, display); - Locale.setDefault(Locale.Category.FORMAT, format); - } + private static Stream converters() { + return Stream.of( + Arguments.of( + "DB2TypeConverter", + DB2TypeConverter.INSTANCE, + "integer", + BasicType.INT_TYPE), + Arguments.of( + "DmdbTypeConverter", DmdbTypeConverter.INSTANCE, "int", BasicType.INT_TYPE), + Arguments.of( + "IrisTypeConverter", + IrisTypeConverter.INSTANCE, + "integer", + BasicType.INT_TYPE), + Arguments.of( + "KingbaseTypeConverter", + KingbaseTypeConverter.INSTANCE, + "int", + BasicType.INT_TYPE), + Arguments.of( + "OracleTypeConverter", + OracleTypeConverter.INSTANCE, + "integer", + new DecimalType(OracleTypeConverter.DEFAULT_PRECISION, 0)), + Arguments.of( + "PostgresTypeConverter", + PostgresTypeConverter.INSTANCE, + "INT4", + BasicType.INT_TYPE), + Arguments.of( + "RedshiftTypeConverter", + RedshiftTypeConverter.INSTANCE, + "integer", + BasicType.INT_TYPE), + Arguments.of( + "SapHanaTypeConverter", + SapHanaTypeConverter.INSTANCE, + "integer", + BasicType.INT_TYPE), + Arguments.of( + "XuguTypeConverter", + XuguTypeConverter.INSTANCE, + "integer", + BasicType.INT_TYPE), + Arguments.of( + "YashanDbTypeConverter", + YashanDbTypeConverter.INSTANCE, + "integer", + BasicType.INT_TYPE)); } private static BasicTypeDefine typeDefine(String dataType) { From 5dd1edd72cea929420c6fcd3bf1dbd27f20a893c Mon Sep 17 00:00:00 2001 From: sharpca Date: Tue, 3 Nov 2026 23:01:48 +0800 Subject: [PATCH 6/8] [Test][Connector-V2][JDBC] Assert split bounds across locales in the string split IT The IT only compared the rows it read, which cannot observe the locale bug: the first split still starts at the literal MIN and the last one still ends at the literal MAX, so every row is read exactly once whatever the encoding is. Under tr-TR the interior bounds decode to strings that start with the U+0001 placeholder instead of "i" - that is the difference a caller actually sees. Generate the splits once under Locale.ROOT and once under tr-TR and compare the boundaries. Additionally: - pin split.string_split_mode_collate, because isCaseInsensitive is derived from the connection collation (JdbcDialect#getCollationSequence) and, when the option is unset, a server default that does not tie A with a leaves the lower-casing branch untested; - assert that interior bounds are already folded, so the fixture fails loudly if that branch is ever skipped instead of silently going inert; - build the catalog table before switching the locale, so the test does not depend on MySqlCatalog from #13076. --- .../seatunnel/jdbc/JdbcMysqlSplitIT.java | 129 +++++++++++++----- 1 file changed, 93 insertions(+), 36 deletions(-) diff --git a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-7/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/JdbcMysqlSplitIT.java b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-7/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/JdbcMysqlSplitIT.java index 0ba46f86ed7a..f555e60066cd 100644 --- a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-7/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/JdbcMysqlSplitIT.java +++ b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-7/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/JdbcMysqlSplitIT.java @@ -875,15 +875,29 @@ public void testFixedNumericSplitsReadAllRows() throws Exception { } } + /** + * The charset_based string splitter encodes MIN/MAX of the partition column into a number and + * decodes the interior boundaries back to strings. With a Turkish default locale a bare + * {@code toLowerCase()} turns the {@code I} of {@code I1} into a dotless {@code ı} ({@code + * U+0131}); {@code CollationBasedSplitter#encodeStringToNumericRange} maps any character that is + * not in the collation sequence to index 0, the same index as the padding character, so the + * encoded range collapses and every interior boundary decodes to a string that starts with the + * {@code U+0001} placeholder instead of {@code i}. + * + *

Asserting on the rows that come back cannot see that difference: the first split still + * starts at the literal MIN, the last one still ends at the literal MAX, so every row is still + * read exactly once - the boundaries just move. This test therefore compares the generated + * split boundaries under {@link Locale#ROOT} and under {@code tr-TR}, and keeps the row identity + * check as an extra guard. + */ @Test - public void testFixedStringSplitsReadAllRowsUnderTurkishLocale() throws Exception { + public void testFixedStringSplitBoundsAreLocaleIndependent() throws Exception { String tableName = "fixed_string_locale"; String qualifiedTable = MYSQL_DATABASE + "." + tableName; List ids = Arrays.asList( - "I1", "i2", "I3", "i4", "I5", "i6", "I7", "i8", "I9", "i10", - "I11", "i12", "I13", "i14", "I15", "i16", "I17", "i18", "I19", - "i20"); + "I1", "i2", "I3", "i4", "I5", "i6", "I7", "i8", "I9", "i10", "I11", "i12", + "i13", "i14", "I15", "i16", "I17", "i18", "I19", "i20"); try (Connection connection = getJdbcConnection(); Statement statement = connection.createStatement()) { statement.execute( @@ -910,11 +924,15 @@ public void testFixedStringSplitsReadAllRowsUnderTurkishLocale() throws Exceptio configMap.put("query", query); configMap.put("partition_column", "id"); configMap.put("split.string_split_mode", "charset_based"); + // isCaseInsensitive is not read from the table collation: FixedChunkSplitter asks + // JdbcDialect#getCollationSequence for the ASCII order of the connection and treats + // a sequence that contains an "aa"-like pair as case insensitive. Pinning the + // collation here keeps that decision deterministic (the server default may not tie + // A with a) and documents which collation the fixture actually exercises. The + // table collation above still drives the MIN/MAX ordering the fixture relies on. + configMap.put("split.string_split_mode_collate", "utf8mb4_general_ci"); TablePath tablePath = TablePath.of(MYSQL_DATABASE, tableName); - Locale base = Locale.getDefault(); - Locale display = Locale.getDefault(Locale.Category.DISPLAY); - Locale format = Locale.getDefault(Locale.Category.FORMAT); try (MySqlCatalog mySqlCatalog = new MySqlCatalog( "mysql", @@ -923,23 +941,36 @@ public void testFixedStringSplitsReadAllRowsUnderTurkishLocale() throws Exceptio mysqlUrlInfo, null); FixedChunkSplitter splitter = getFixedChunkSplitter(configMap)) { + // MySqlCatalog is fixed in the sibling PR, so read the catalog table before the + // default locale is switched: only the split generation below has to run under + // tr-TR. + mySqlCatalog.open(); + CatalogTable table = mySqlCatalog.getTable(tablePath); + JdbcSourceTable jdbcSourceTable = + JdbcSourceTable.builder() + .tablePath(tablePath) + .catalogTable(table) + .query(query) + .partitionColumn("id") + .partitionNumber(4) + .build(); + + List rootBounds; + List turkishBounds; + Map readCounts = new HashMap<>(); + int splitCount = 0; + Locale base = Locale.getDefault(); + Locale display = Locale.getDefault(Locale.Category.DISPLAY); + Locale format = Locale.getDefault(Locale.Category.FORMAT); try { - Locale.setDefault(new Locale("tr", "TR")); + Locale.setDefault(Locale.ROOT); + rootBounds = splitBounds(splitter, jdbcSourceTable); - mySqlCatalog.open(); - CatalogTable table = mySqlCatalog.getTable(tablePath); - JdbcSourceTable jdbcSourceTable = - JdbcSourceTable.builder() - .tablePath(tablePath) - .catalogTable(table) - .query(query) - .partitionColumn("id") - .partitionNumber(4) - .build(); - - Map readCounts = new HashMap<>(); - int splitCount = 0; - for (JdbcSourceSplit split : splitter.generateSplits(jdbcSourceTable)) { + Locale.setDefault(new Locale("tr", "TR")); + Collection turkishSplits = + splitter.generateSplits(jdbcSourceTable); + turkishBounds = splitBounds(turkishSplits); + for (JdbcSourceSplit split : turkishSplits) { splitCount++; try (PreparedStatement splitStatement = splitter.generateSplitStatement( @@ -950,29 +981,55 @@ public void testFixedStringSplitsReadAllRowsUnderTurkishLocale() throws Exceptio } } } - - Assertions.assertTrue( - splitCount > 1, - "expected more than one split, got " + splitCount); - Assertions.assertEquals( - ids.size(), - readCounts.size(), - "row identities read across splits differ: " + readCounts.keySet()); - for (String id : ids) { - Assertions.assertEquals( - 1, - readCounts.getOrDefault(id, 0), - "row " + id + " was not read exactly once"); - } } finally { Locale.setDefault(base); Locale.setDefault(Locale.Category.DISPLAY, display); Locale.setDefault(Locale.Category.FORMAT, format); } + + Assertions.assertEquals( + rootBounds, + turkishBounds, + "split bounds depend on the JVM default locale"); + for (int i = 0; i < rootBounds.size() - 1; i++) { + String interiorBound = rootBounds.get(i); + Assertions.assertEquals( + interiorBound.toLowerCase(Locale.ROOT), + interiorBound, + "interior bound " + + interiorBound + + " is not folded to the case insensitive sequence, so" + + " this fixture no longer exercises the lower-casing path"); + } + Assertions.assertTrue( + splitCount > 1, "expected more than one split, got " + splitCount); + Assertions.assertEquals( + ids.size(), + readCounts.size(), + "row identities read across splits differ: " + readCounts.keySet()); + for (String id : ids) { + Assertions.assertEquals( + 1, + readCounts.getOrDefault(id, 0), + "row " + id + " was not read exactly once"); + } } } finally { statement.execute("DROP TABLE IF EXISTS " + qualifiedTable); } } } + + private static List splitBounds( + FixedChunkSplitter splitter, JdbcSourceTable table) throws Exception { + return splitBounds(splitter.generateSplits(table)); + } + + private static List splitBounds(Collection splits) { + List bounds = new ArrayList<>(); + for (JdbcSourceSplit split : splits) { + bounds.add(split.getSplitStart() + ".." + split.getSplitEnd()); + } + return bounds; + } } From 24c2d87839f31a2fa04e87c9458af7d985587e4d Mon Sep 17 00:00:00 2001 From: sharpca Date: Wed, 4 Nov 2026 00:36:08 +0800 Subject: [PATCH 7/8] [Test][Connector-V2][JDBC] Apply spotless formatting to the split bounds IT The fork build failed at Check code style: google-java-format reflowed the Javadoc paragraph and wanted the splitBounds signature on one line. Replace the Javadoc with line comments (not reflowed) and join the signature. --- .../seatunnel/jdbc/JdbcMysqlSplitIT.java | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-7/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/JdbcMysqlSplitIT.java b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-7/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/JdbcMysqlSplitIT.java index f555e60066cd..8c1338209ed2 100644 --- a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-7/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/JdbcMysqlSplitIT.java +++ b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-7/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/JdbcMysqlSplitIT.java @@ -875,21 +875,17 @@ public void testFixedNumericSplitsReadAllRows() throws Exception { } } - /** - * The charset_based string splitter encodes MIN/MAX of the partition column into a number and - * decodes the interior boundaries back to strings. With a Turkish default locale a bare - * {@code toLowerCase()} turns the {@code I} of {@code I1} into a dotless {@code ı} ({@code - * U+0131}); {@code CollationBasedSplitter#encodeStringToNumericRange} maps any character that is - * not in the collation sequence to index 0, the same index as the padding character, so the - * encoded range collapses and every interior boundary decodes to a string that starts with the - * {@code U+0001} placeholder instead of {@code i}. - * - *

Asserting on the rows that come back cannot see that difference: the first split still - * starts at the literal MIN, the last one still ends at the literal MAX, so every row is still - * read exactly once - the boundaries just move. This test therefore compares the generated - * split boundaries under {@link Locale#ROOT} and under {@code tr-TR}, and keeps the row identity - * check as an extra guard. - */ + // The charset_based string splitter encodes MIN/MAX of the partition column into a number and + // decodes the interior boundaries back to strings. Under tr-TR a bare toLowerCase() turns the + // I of "I1" into a dotless i (U+0131), and CollationBasedSplitter#charToIndex maps any + // character outside the collation sequence to index 0 - the same index as the padding + // character - so the encoded range collapses and every interior bound decodes to a string that + // starts with the U+0001 placeholder instead of "i". + // + // Asserting on the rows read cannot see that: the first split still starts at the literal MIN + // and the last still ends at the literal MAX, so every row is read exactly once and only the + // boundaries move. This test therefore compares the generated boundaries under Locale.ROOT and + // under tr-TR, and keeps the row identity check as an extra guard. @Test public void testFixedStringSplitBoundsAreLocaleIndependent() throws Exception { String tableName = "fixed_string_locale"; @@ -1020,8 +1016,8 @@ public void testFixedStringSplitBoundsAreLocaleIndependent() throws Exception { } } - private static List splitBounds( - FixedChunkSplitter splitter, JdbcSourceTable table) throws Exception { + private static List splitBounds(FixedChunkSplitter splitter, JdbcSourceTable table) + throws Exception { return splitBounds(splitter.generateSplits(table)); } From de80d166dc58bcfc974106645c29e13521945725 Mon Sep 17 00:00:00 2001 From: sharpca Date: Wed, 4 Nov 2026 06:27:30 +0800 Subject: [PATCH 8/8] [Test][Connector-V2][JDBC] Only require decoded interior bounds to be folded The guard iterated over the start..end strings, but the first split's start is the literal partitionStart (MIN), which keeps its uppercase I. CI failed with expected i1..i3 but was I1..i3. Walk the splits instead: start of every split except the first, end of every split except the last. --- .../seatunnel/jdbc/JdbcMysqlSplitIT.java | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-7/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/JdbcMysqlSplitIT.java b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-7/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/JdbcMysqlSplitIT.java index 8c1338209ed2..4e2cf82b2d1c 100644 --- a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-7/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/JdbcMysqlSplitIT.java +++ b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-7/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/JdbcMysqlSplitIT.java @@ -951,6 +951,7 @@ public void testFixedStringSplitBoundsAreLocaleIndependent() throws Exception { .partitionNumber(4) .build(); + List rootSplits; List rootBounds; List turkishBounds; Map readCounts = new HashMap<>(); @@ -960,7 +961,8 @@ public void testFixedStringSplitBoundsAreLocaleIndependent() throws Exception { Locale format = Locale.getDefault(Locale.Category.FORMAT); try { Locale.setDefault(Locale.ROOT); - rootBounds = splitBounds(splitter, jdbcSourceTable); + rootSplits = new ArrayList<>(splitter.generateSplits(jdbcSourceTable)); + rootBounds = splitBounds(rootSplits); Locale.setDefault(new Locale("tr", "TR")); Collection turkishSplits = @@ -987,15 +989,13 @@ public void testFixedStringSplitBoundsAreLocaleIndependent() throws Exception { rootBounds, turkishBounds, "split bounds depend on the JVM default locale"); - for (int i = 0; i < rootBounds.size() - 1; i++) { - String interiorBound = rootBounds.get(i); - Assertions.assertEquals( - interiorBound.toLowerCase(Locale.ROOT), - interiorBound, - "interior bound " - + interiorBound - + " is not folded to the case insensitive sequence, so" - + " this fixture no longer exercises the lower-casing path"); + for (int i = 0; i < rootSplits.size(); i++) { + if (i > 0) { + assertFoldedBound(rootSplits.get(i).getSplitStart()); + } + if (i < rootSplits.size() - 1) { + assertFoldedBound(rootSplits.get(i).getSplitEnd()); + } } Assertions.assertTrue( splitCount > 1, "expected more than one split, got " + splitCount); @@ -1028,4 +1028,13 @@ private static List splitBounds(Collection splits) { } return bounds; } + + private static void assertFoldedBound(Object bound) { + Assertions.assertNotNull(bound, "generated interior bound must not be null"); + String text = bound.toString(); + Assertions.assertEquals( + text.toLowerCase(Locale.ROOT), + text, + "interior bound " + text + " is not folded to the case insensitive sequence"); + } }