Skip to content

【SQL Federation】SQL Federation ResultSetMetaData#getColumnClassName returns SqlTypeName enum class instead of actual Java column class #39121

Description

@YM-Sky

Bug Report

Which version of ShardingSphere did you use?

Apache ShardingSphere-JDBC 5.5.3.

Related modules:

  • shardingsphere-jdbc: 5.5.3
  • shardingsphere-jdbc-dialect-mysql: 5.5.3
  • shardingsphere-sharding-mysql: 5.5.3

Environment:

  • Java 17
  • MySQL dialect
  • SQL Federation enabled
  • Apache Calcite resolved as 1.40.0

Which project did you use? ShardingSphere-JDBC or ShardingSphere-Proxy?

ShardingSphere-JDBC.

Expected behavior

For SQL Federation query results, ResultSetMetaData#getColumnClassName(column) should return the Java class name corresponding to the actual result column type.

For example:

  • BIGINT id column: java.lang.Long
  • VARCHAR name column: java.lang.String
  • DECIMAL column: java.math.BigDecimal

Actual behavior

ResultSetMetaData#getColumnClassName(column) returns:

org.apache.calcite.sql.type.SqlTypeName

This happens for normal result columns such as id BIGINT.

When a mapper framework maps results to Map<String, Object>, it may use the reported column class name to select a type handler. Since ShardingSphere reports the enum class SqlTypeName, MyBatis/MyBatis-Plus chooses an enum type handler and tries to parse the actual database value as a SqlTypeName enum constant.

Stack trace excerpt:

Caused by: org.apache.ibatis.executor.result.ResultMapException:
Error attempting to get column 'id' from result set.
Cause: java.lang.IllegalArgumentException:
No enum constant org.apache.calcite.sql.type.SqlTypeName.2076978610812633090

at org.apache.ibatis.type.BaseTypeHandler.getResult(BaseTypeHandler.java:88)
at com.baomidou.mybatisplus.core.handlers.CompositeEnumTypeHandler.getResult(CompositeEnumTypeHandler.java:62)
at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.applyAutomaticMappings(DefaultResultSetHandler.java:582)

Caused by: java.lang.IllegalArgumentException:
No enum constant org.apache.calcite.sql.type.SqlTypeName.2076978610812633090
at java.lang.Enum.valueOf(Enum.java:273)
at org.apache.ibatis.type.EnumTypeHandler.getNullableResult(EnumTypeHandler.java:49)

Reason analyze (If you can)

I checked the ShardingSphere 5.5.3 source locally.

Source location:

D:\source\shardingsphere\shardingsphere-5.5.3\kernel\sql-federation\core\src\main\java\org\apache\shardingsphere\sqlfederation\resultset\SQLFederationResultSetMetaData.java

Current implementation:

@Override
public String getColumnClassName(final int column) {
    return resultColumnType.getFieldList().get(column - 1).getType().getSqlTypeName().getClass().getName();
}

getSqlTypeName() returns a Calcite enum value. Calling getClass().getName() on that enum value always returns:

org.apache.calcite.sql.type.SqlTypeName

It does not return the actual Java class for the result column.

Steps to reproduce the behavior, such as: SQL to execute, sharding rule configuration, when exception occur etc.

Use ShardingSphere-JDBC 5.5.3 with MySQL dialect and SQL Federation enabled.

Sanitized YAML, preserving the real rule structure:

databaseName: logic_db

mode:
  type: Standalone
  repository:
    type: JDBC

transaction:
  defaultType: LOCAL

dataSources:
  ds_0:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://127.0.0.1:3306/demo_master?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: password
  ds_1:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://127.0.0.1:3306/demo_sharding_1?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: password

sqlFederation:
  sqlFederationEnabled: true
  allQueryUseSQLFederation: false
  executionPlanCache:
    initialCapacity: 2000
    maximumSize: 65535

props:
  sql-show: true

rules:
  - !SINGLE
    tables:
      - ds_0.mnt_base_user

  - !SHARDING
    tables: {}
    defaultDatabaseStrategy:
      complex:
        shardingColumns: tenant_id
        shardingAlgorithmName: custom_database_tenant_sharding_algorithm
    defaultTableStrategy:
      complex:
        shardingColumns: tenant_id
        shardingAlgorithmName: tenant_sharding_algorithm
    shardingAlgorithms:
      custom_database_tenant_sharding_algorithm:
        type: CLASS_BASED
        props:
          strategy: COMPLEX
          algorithmClassName: com.example.sharding.CustomDatabaseTenantIdShardingAlgorithm
          dbPrefix: ds_
          tenantIdFiled: tenant_id
      tenant_sharding_algorithm:
        type: CLASS_BASED
        props:
          strategy: COMPLEX
          algorithmClassName: com.example.sharding.CustomTableTenantIdShardingAlgorithm
          tenantIdFiled: tenant_id

Run a SQL Federation query:

SELECT so.id AS id, u.name AS record_user_name
FROM ims_sales_order so
LEFT JOIN mnt_base_user u ON so.record_user_id = u.id
WHERE so.tenant_id = 1
ORDER BY so.create_time DESC
LIMIT 20

Then inspect metadata:

try (ResultSet rs = ps.executeQuery()) {
    ResultSetMetaData md = rs.getMetaData();
    System.out.println(md.getColumnClassName(1));
}

Expected:

java.lang.Long

Actual:

org.apache.calcite.sql.type.SqlTypeName

Example codes for reproduce this issue (such as a github link).

try (Connection connection = dataSource.getConnection();
     PreparedStatement ps = connection.prepareStatement("""
         SELECT so.id AS id, u.name AS record_user_name
         FROM ims_sales_order so
         LEFT JOIN mnt_base_user u ON so.record_user_id = u.id
         WHERE so.tenant_id = 1
         ORDER BY so.create_time DESC
         LIMIT 20
         """);
     ResultSet rs = ps.executeQuery()) {

    ResultSetMetaData md = rs.getMetaData();

    // Actual in SQL Federation: org.apache.calcite.sql.type.SqlTypeName
    // Expected for BIGINT id: java.lang.Long
    System.out.println(md.getColumnClassName(1));
}

Suggested fix

SQLFederationResultSetMetaData#getColumnClassName should map Calcite SqlTypeName or RelDataType to the correct JDBC Java class name.

Possible mapping examples:

  • BIGINT -> java.lang.Long
  • INTEGER -> java.lang.Integer
  • VARCHAR/CHAR -> java.lang.String
  • DECIMAL -> java.math.BigDecimal
  • BOOLEAN -> java.lang.Boolean
  • DATE/TIME/TIMESTAMP -> corresponding JDBC date/time classes
  • Unknown fallback -> java.lang.Object

The method should not return getSqlTypeName().getClass().getName(), because that returns the enum implementation class, not the result column Java class.

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