Skip to content

[Bug][Connector-V2][JDBC] JDBC dialect type converters depend on the JVM default locale #13079

Open

Description

@sharpca

Search before asking

  • I had searched in the issues and found no similar issues.
  • I searched the open and closed pull requests for the affected classes and for locale dependent type mapping.

What happened

The JDBC dialect type converters match the source column type against the dialect type constants with String.toUpperCase() / String.toLowerCase() without Locale.ROOT. The result therefore depends on the JVM default locale.

In a Turkish or Azerbaijani locale "i" is upper-cased to "İ" (U+0130), so a type name such as INTEGER becomes İNTEGER and no longer matches the constant. The converter then falls through to its default branch and the job fails with an unsupported type error instead of mapping the column.

The following converters are affected in dev:

Dialect Converter Type name that breaks
DB2 DB2TypeConverter INTEGER, BIGINT, SMALLINT, BINARY
DaMeng DmdbTypeConverter INT, INTEGER, BIGINT, SMALLINT
IRIS IrisTypeConverter INT, INTEGER, BIGINT, SERIAL
Kingbase KingbaseTypeConverter INT, INTEGER, BIGINT
Oracle OracleTypeConverter INTEGER, INTERVAL, BINARY_FLOAT
PostgreSQL PostgresTypeConverter INT4, INT8, BIGSERIAL
Redshift RedshiftTypeConverter INT, BIGINT, SMALLINT
SAP HANA SapHanaTypeConverter INT, INTEGER, BIGINT
Xugu XuguTypeConverter INT, INTEGER, BIGINT, TINYINT
YashanDB YashanDbTypeConverter INT, INTEGER, BIGINT, SMALLINT

PostgresTypeConverter lower-cases the type name, so it only breaks when the type name contains an upper-case I (INT4 -> ınt4).

The MySQL, OceanBase and SQL Server converters are already covered by #13076 and #13077, and JdbcDialect / CatalogUtils by #13063, so they are not part of this report.

What you expected to happen

A JDBC dialect type converter maps the same source type to the same SeaTunnel type on every worker, regardless of the JVM default locale of that worker.

SeaTunnel Version

dev

SeaTunnel Config

Not configuration dependent; the reproduction uses the affected type converters directly.

Running Command

./mvnw -pl seatunnel-connectors-v2/connector-jdbc \
  -Dtest='Db2TypeConverterTest,DmdbTypeConverterTest,IrisTypeConverterTest,\
KingbaseTypeConverterTest,OracleTypeConverterTest,PostgresTypeConverterTest,\
RedshiftTypeConverterTest,SapHanaTypeConverterTest,XuguTypeConverterTest,\
YashanDbTypeConverterTest' test

Error Exception

java.lang.RuntimeException: Unsupported type INT
	at org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.db2.DB2TypeConverter.convert(DB2TypeConverter.java:...)

Zeta or Flink or Spark Version

Not engine specific. The failure is in the JDBC connector.

Java or Scala Version

Not version specific. Reproduced on JDK 17 with -Duser.language=tr -Duser.country=TR.

Are you willing to submit PR?

  • Yes I am willing to submit a PR!

Code of Conduct

  • I agree to follow this project's Code of Conduct.

Activity

  1. SEZ9 commented on Nov 1, 2026

    @SEZ9
    Collaborator

    Thanks for the detailed report. This looks like a valid follow-up to #13063, #13076 and #13077, and the table of affected converters makes the scope clear.

    Since you're willing to submit a PR, one PR covering all ten converters is fine. In that PR, please pass Locale.ROOT to the toUpperCase() / toLowerCase() calls and add a test that exercises the converters under a Turkish locale (as in your reproduction with -Duser.language=tr -Duser.country=TR) so this does not regress. A quick scan of the rest of connector-jdbc for any remaining locale-dependent case conversions would also be welcome.

  2. sharpca commented on Nov 1, 2026

    @sharpca
    Author

    Thanks @SEZ9. I opened #13083, which passes Locale.ROOT to all ten converters listed in the report and adds JdbcTypeConverterLocaleTest, which runs those converters under tr-TR with lower-case type names (integer / int, and upper-case INT4 for PostgreSQL) and asserts the resulting SeaTunnel type. The test fails against the unpatched DB2TypeConverter with Unsupported type integer, so it guards the regression.

  3. sharpca commented on Nov 1, 2026

    @sharpca
    Author

    Thanks for the guidance. #13083 now covers all ten converters plus the scan you asked for.

    Locale.ROOT in the ten converters — every toUpperCase() / toLowerCase() in the converters listed in the report now passes Locale.ROOT (34 call sites in the PR).

    Turkish locale test — JdbcTypeConverterLocaleTest sets new Locale("tr", "TR"), converts a type name through each affected converter, and restores the previous default in @AfterEach, so the locale cannot leak into other tests sharing the JVM. It covers INTEGER, BIGINT, INT4 and the other names from the table.

    Result of the scan of the rest of connector-jdbc — three more places lower-cased with the default locale:

    Location Call
    MysqlDialect, OceanBaseMysqlDialect, OracleDialect, RedshiftDialect, SqlServerDialect, YashanDbDialect getQuery().toLowerCase() in the row count WHERE check
    CollationBasedSplitter s.toLowerCase() in the case insensitive collation normalisation
    JdbcSourceFactory value.toLowerCase().startsWith("where") in the where_condition prefix validator

    These are fixed in the same PR. The six dialects now follow the pattern PostgresDialect already used here. CollationBasedSplitter is included because the locale changes the comparison result there, not only the spelling of a constant.

    JdbcDialect / CatalogUtils are left to #13063 and the MySQL / OceanBase / SQL Server converters to #13076 and #13077, as noted in the report, so the PRs stay disjoint.

    Verification (JDK 17, ./mvnw -pl seatunnel-connectors-v2/connector-jdbc):

    • JdbcTypeConverterLocaleTest — 1 test, 0 failures
    • MysqlDialectTest, OracleDialectTest, SqlServerDialectTest, YashanDbDialectTest, JdbcSourceFactoryTest — 46 tests, 0 failures
    • spotless:check — passed

    The PR workflows are still waiting for approval (action_required), so none of this has run in CI yet.

  4. SEZ9 commented on Nov 2, 2026

    @SEZ9
    Collaborator

    Thanks @sharpca for opening the PR rather than just confirming the analysis.

    Passing Locale.ROOT to every toUpperCase() / toLowerCase() in the ten converters from the report is exactly the fix I was after, and JdbcTypeConverterLocaleTest running under tr-TR with lower-case integer / int plus upper-case INT4 for PostgreSQL is the right shape of guard. That it fails against the unpatched DB2TypeConverter with Unsupported type integer is good evidence the test actually bites.

    Two small asks on the test:

    1. You report it as 1 test covering ten converters. Could you split it per converter (parameterized over the converter instances, or one method each)? If a regression slips into a single dialect, the failure should name that converter directly.
    2. Please confirm the previous default locale is captured before it is switched to tr-TR and restored even if the converter call throws. You describe it as restored, so this is likely already fine; a leaked tr-TR default would make unrelated tests in the same JVM fail in confusing ways.

    On the scan results: the six getQuery().toLowerCase() dialect sites, CollationBasedSplitter, and the JdbcSourceFactory where_condition check are the same class of bug, so bundling them with the converter fix is fine with me, and keeping JdbcDialect / CatalogUtils and the MySQL / OceanBase / SQL Server converters in their separate PRs as you describe is the right call.

    Once the PR workflows have run, ping me here and I'll do the full review.

  5. sharpca commented on Nov 2, 2026

    @sharpca
    Author

    Both asks are addressed in cabfb80d.

    1. One case per converter. JdbcTypeConverterLocaleTest is now a @ParameterizedTest over a @MethodSource of (converter, source type, expected type) pairs — ten invocations, one per dialect, with the naming pattern "{0} maps {2}". The dialect name is also carried into the assertion message, so a regression reads DB2TypeConverter folded integer to the wrong type under tr-TR instead of an invocation index.

    2. Locale capture and restore. The capture moved into @BeforeEach: the base, DISPLAY and FORMAT defaults are read first, and only then is the default switched to tr-TR. The restore moved into @AfterEach, which JUnit runs even when the test body throws — so the guarantee is structural now rather than a finally inside one combined test method. @ResourceLock("java.util.Locale") is retained in case parallel execution is enabled later.

    Local evidence on JDK 17 (-pl seatunnel-connectors-v2/connector-jdbc, current head): the targeted set is 68 tests, 0 failures, 1 skipped (the container-backed Oracle test), with the locale test itself reporting 10/0/0/0. spotless:check passes.

    On the ping — I cannot send it yet. CI still has not executed on this head: the check-runs count is 0 and the workflow run for this PR stays action_required, which means it is waiting for a maintainer to approve the run. A fork contributor cannot trigger that gate. I will post the CI results here the moment it executes.

    To be explicit about what is and is not established: everything above, like the earlier numbers, is local execution by me, not CI. The integration case added in 704af8840 has not been compiled or run at all here (no Docker, and the e2e module's SNAPSHOT prerequisites are absent from this machine), so it depends entirely on that CI run.

  6. DanielLeens commented on Nov 3, 2026

    @DanielLeens
    Contributor

    Hi @sharpca, thanks for addressing both test points so quickly.

    On the CI status: action_required here does not mean the run is waiting for a maintainer approval. For apache/seatunnel the build for a PR runs in the contributor's fork, and the Build check on the PR only reports the result of that fork run. The check on #13083 currently reports "Workflow run detection failed", and your fork sharpca/seatunnel has no workflow runs at all, so there is nothing for the check to pick up yet. There is no approval gate a maintainer can clear for this. The only action_required run on the apache side is Label-when-reviewed, a review-labeling workflow that does not build or test anything.

    To get CI running:

    1. Open the Actions tab of your fork and enable GitHub Actions if it shows the enable button.
    2. In the fork's Actions tab, select the Build workflow, click Run workflow and choose the PR branch (or push an empty commit to the branch).
    3. The PR's Build check picks the fork run up automatically within about 15 minutes.

    The same steps apply to #13081.

    One small request for the review: the PR now touches more files than the thread lists (for example Gbase8aTypeMapper, HiveTypeMapper, PrestoTypeMapper, PhoenixTypeConverter, SnowflakeTypeConverter, the DuckDB/Redshift/Xugu/YashanDB catalogs, DamengDataTypeConvertor, OceanBaseOracleCreateTableSqlBuilder, and the new JdbcMysqlSplitIT case). Please list all of them in the PR description with the call site and the reason each one is locale sensitive, so the review can be checked against the full scope. The JdbcDialect / CatalogUtils and MySQL / OceanBase / SQL Server boundaries with #13063, #13076 and #13077 look clean on the main sources.

    Once the fork build has run, further review continues on #13083 itself.

  7. SEZ9 commented on Nov 4, 2026

    @SEZ9
    Collaborator

    Thanks @sharpca — cabfb80d addresses both test points: one parameterized invocation per converter with the dialect name carried into the assertion message, and the locale capture/restore moved into the before/after hooks so the restore is structural.

    Two things remain before review continues on #13083 itself:

    1. CI has not run on the fork yet. As described above, action_required on the apache side is only the Label-when-reviewed workflow, which does not build or test anything, and the Build check on [Fix][Connector-V2] Make JDBC dialect type converters locale independent #13083 cannot pick anything up until a workflow run exists in sharpca/seatunnel. There is no maintainer approval gate to clear. Please enable Actions in your fork and run the Build workflow against the PR branch (or push an empty commit); the PR check should pick it up within about 15 minutes. The same applies to [Fix][Core] Make CLI enum argument parsing locale independent #13081. This matters most for the integration case added in 704af8840, which you noted has not been compiled or run locally.

    2. PR description vs. actual scope. The Locale.ROOT change touches more files than this thread lists (for example Gbase8aTypeMapper, HiveTypeMapper, PrestoTypeMapper, PhoenixTypeConverter, SnowflakeTypeConverter, the DuckDB/Redshift/Xugu/YashanDB catalogs, DamengDataTypeConvertor, OceanBaseOracleCreateTableSqlBuilder, and the new JdbcMysqlSplitIT case). Please list every touched file in the PR description with the call site and why it is locale sensitive, in the same style as the table you posted above, so the review can be checked against the full set of call sites.

    The boundaries with #13063 (JdbcDialect / CatalogUtils) and #13076 / #13077 (MySQL / OceanBase / SQL Server converters) look clean on the main sources — nothing needed there.

    Once the fork build has executed and the description is complete, I will continue on #13083.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions