This is an automated email from the ASF dual-hosted git repository.
terrymanu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git
The following commit(s) were added to refs/heads/master by this push:
new 4877c93a7d4 Fix incorrect return value of
SQLFederationResultSetMetaData#getColumnClassName (#39217)
4877c93a7d4 is described below
commit 4877c93a7d44fa1fb94548d8745ad475b63a870c
Author: Prahlad Bhakat <[email protected]>
AuthorDate: Sun Aug 23 08:47:29 2026 +0530
Fix incorrect return value of
SQLFederationResultSetMetaData#getColumnClassName (#39217)
* Fix getColumnClassName return value
* Format SQL federation result set metadata
* Fix SQL federation column class name
* Fix code style
* Fix SQL federation column class name
* Add representative decimal/temporal/binary/fallback coverage and a real
ShardingSphere-JDBC SQL Federation regression test for #39121
* Fix checkstyle newline
* Remove generated effective-pom.xml
---
.../resultset/SQLFederationResultSetMetaData.java | 18 ++-
.../DialectSQLFederationColumnTypeConverter.java | 12 ++
.../SQLFederationResultSetMetaDataTest.java | 70 +++++++++++-
.../resultset/SQLFederationResultSetTest.java | 45 ++++++++
.../MySQLSQLFederationColumnTypeConverter.java | 7 ++
.../MySQLSQLFederationColumnTypeConverterTest.java | 9 ++
test/e2e/driver/pom.xml | 6 +
.../SQLFederationResultSetColumnClassNameTest.java | 127 +++++++++++++++++++++
8 files changed, 284 insertions(+), 10 deletions(-)
diff --git
a/kernel/sql-federation/core/src/main/java/org/apache/shardingsphere/sqlfederation/resultset/SQLFederationResultSetMetaData.java
b/kernel/sql-federation/core/src/main/java/org/apache/shardingsphere/sqlfederation/resultset/SQLFederationResultSetMetaData.java
index f1794a6f197..625a71afbe5 100644
---
a/kernel/sql-federation/core/src/main/java/org/apache/shardingsphere/sqlfederation/resultset/SQLFederationResultSetMetaData.java
+++
b/kernel/sql-federation/core/src/main/java/org/apache/shardingsphere/sqlfederation/resultset/SQLFederationResultSetMetaData.java
@@ -17,9 +17,10 @@
package org.apache.shardingsphere.sqlfederation.resultset;
+import org.apache.calcite.adapter.java.JavaTypeFactory;
import org.apache.calcite.avatica.SqlType;
+import org.apache.calcite.linq4j.tree.Primitive;
import org.apache.calcite.rel.type.RelDataType;
-import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypeFactoryImpl.JavaType;
import org.apache.calcite.schema.Schema;
import org.apache.calcite.schema.Table;
@@ -31,6 +32,7 @@ import
org.apache.shardingsphere.infra.binder.context.segment.select.projection.
import
org.apache.shardingsphere.sqlfederation.compiler.sql.type.SQLFederationDataTypeFactory;
import
org.apache.shardingsphere.sqlfederation.resultset.converter.DialectSQLFederationColumnTypeConverter;
+import java.lang.reflect.Type;
import java.math.BigInteger;
import java.sql.ResultSetMetaData;
import java.util.List;
@@ -44,7 +46,7 @@ public final class SQLFederationResultSetMetaData extends
SQLFederationWrapperAd
private final Schema sqlFederationSchema;
- private final RelDataTypeFactory typeFactory;
+ private final JavaTypeFactory typeFactory;
private final List<Projection> expandProjections;
@@ -59,7 +61,7 @@ public final class SQLFederationResultSetMetaData extends
SQLFederationWrapperAd
public SQLFederationResultSetMetaData(final Schema sqlFederationSchema,
final List<Projection> expandProjections, final DatabaseType databaseType,
final RelDataType resultColumnType,
final Map<Integer, String>
indexAndColumnLabels, final DialectSQLFederationColumnTypeConverter
columnTypeConverter) {
this.sqlFederationSchema = sqlFederationSchema;
- typeFactory = SQLFederationDataTypeFactory.getInstance();
+ typeFactory = (JavaTypeFactory)
SQLFederationDataTypeFactory.getInstance();
this.expandProjections = expandProjections;
this.databaseType = databaseType;
this.resultColumnType = resultColumnType;
@@ -180,7 +182,15 @@ public final class SQLFederationResultSetMetaData extends
SQLFederationWrapperAd
@Override
public String getColumnClassName(final int column) {
- return resultColumnType.getFieldList().get(column -
1).getType().getSqlTypeName().getClass().getName();
+ RelDataType relDataType = resultColumnType.getFieldList().get(column -
1).getType();
+ Optional<Class<?>> convertedClass = null == columnTypeConverter
+ ? Optional.empty()
+ :
columnTypeConverter.convertColumnValueClass(relDataType.getSqlTypeName());
+ if (convertedClass.isPresent()) {
+ return convertedClass.get().getName();
+ }
+ Type javaType = Primitive.box(typeFactory.getJavaClass(relDataType));
+ return javaType instanceof Class ? ((Class<?>) javaType).getName() :
Object.class.getName();
}
private Optional<String> findTableName(final int column) {
diff --git
a/kernel/sql-federation/core/src/main/java/org/apache/shardingsphere/sqlfederation/resultset/converter/DialectSQLFederationColumnTypeConverter.java
b/kernel/sql-federation/core/src/main/java/org/apache/shardingsphere/sqlfederation/resultset/converter/DialectSQLFederationColumnTypeConverter.java
index 1426639ff2d..075d77fa9b3 100644
---
a/kernel/sql-federation/core/src/main/java/org/apache/shardingsphere/sqlfederation/resultset/converter/DialectSQLFederationColumnTypeConverter.java
+++
b/kernel/sql-federation/core/src/main/java/org/apache/shardingsphere/sqlfederation/resultset/converter/DialectSQLFederationColumnTypeConverter.java
@@ -21,6 +21,8 @@ import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPI;
import org.apache.shardingsphere.infra.spi.annotation.SingletonSPI;
+import java.util.Optional;
+
/**
* Dialect SQL federation column type converter.
*/
@@ -42,4 +44,14 @@ public interface DialectSQLFederationColumnTypeConverter
extends DatabaseTypedSP
* @return converted column type
*/
int convertColumnType(SqlTypeName sqlTypeName);
+
+ /**
+ * Convert column value class.
+ *
+ * @param sqlTypeName original SQL type name
+ * @return actual Java class of the converted value
+ */
+ default Optional<Class<?>> convertColumnValueClass(final SqlTypeName
sqlTypeName) {
+ return Optional.empty();
+ }
}
diff --git
a/kernel/sql-federation/core/src/test/java/org/apache/shardingsphere/sqlfederation/resultset/SQLFederationResultSetMetaDataTest.java
b/kernel/sql-federation/core/src/test/java/org/apache/shardingsphere/sqlfederation/resultset/SQLFederationResultSetMetaDataTest.java
index 0e23a03cf9c..94edb42aed7 100644
---
a/kernel/sql-federation/core/src/test/java/org/apache/shardingsphere/sqlfederation/resultset/SQLFederationResultSetMetaDataTest.java
+++
b/kernel/sql-federation/core/src/test/java/org/apache/shardingsphere/sqlfederation/resultset/SQLFederationResultSetMetaDataTest.java
@@ -18,6 +18,7 @@
package org.apache.shardingsphere.sqlfederation.resultset;
import org.apache.calcite.avatica.SqlType;
+import org.apache.calcite.avatica.util.ByteString;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypeFactoryImpl.JavaType;
@@ -31,23 +32,31 @@ import
org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import
org.apache.shardingsphere.infra.binder.context.segment.select.projection.Projection;
import
org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.ColumnProjection;
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
+import
org.apache.shardingsphere.sqlfederation.compiler.sql.type.SQLFederationDataTypeFactory;
import
org.apache.shardingsphere.sqlfederation.resultset.converter.DialectSQLFederationColumnTypeConverter;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.ResultSetMetaData;
import java.sql.Types;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
-import static org.hamcrest.Matchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -352,12 +361,61 @@ class SQLFederationResultSetMetaDataTest {
@Test
void assertGetColumnClassName() {
- JavaType varcharType = mock(JavaType.class);
- when(varcharType.getJavaClass()).thenReturn(SqlTypeName.class);
- when(varcharType.getSqlTypeName()).thenReturn(SqlTypeName.VARCHAR);
+ RelDataType varcharType =
SQLFederationDataTypeFactory.getInstance().createSqlType(SqlTypeName.VARCHAR);
+ SQLFederationResultSetMetaData metaData = new
SQLFederationResultSetMetaData(
+ mock(), Collections.emptyList(), databaseType,
+ createResultType(new String[]{"foo_col"}, varcharType),
+ Collections.singletonMap(1, "foo_label"), null);
+ assertThat(metaData.getColumnClassName(1), is(String.class.getName()));
+ }
+
+ @Test
+ void assertGetColumnClassNameForBigInteger() {
+ JavaType javaBigIntegerType = mock(JavaType.class);
+ when(javaBigIntegerType.getJavaClass()).thenReturn(BigInteger.class);
+
when(javaBigIntegerType.getSqlTypeName()).thenReturn(SqlTypeName.DECIMAL);
+ RelDataType resultType = createResultType(new String[]{"foo_col"},
javaBigIntegerType);
+ SQLFederationResultSetMetaData metaData = new
SQLFederationResultSetMetaData(
+ mock(), Collections.emptyList(), databaseType, resultType,
Collections.singletonMap(1, "foo_label"), mock());
+ assertThat(metaData.getColumnClassName(1),
is(BigInteger.class.getName()));
+ }
+
+ @Test
+ void assertGetColumnClassNameForConvertedValueClass() {
+ RelDataType booleanType = mock(RelDataType.class);
+ when(booleanType.getSqlTypeName()).thenReturn(SqlTypeName.BOOLEAN);
+ DialectSQLFederationColumnTypeConverter converter =
mock(DialectSQLFederationColumnTypeConverter.class);
+
doReturn(Optional.of(Integer.class)).when(converter).convertColumnValueClass(SqlTypeName.BOOLEAN);
+ RelDataType resultType = createResultType(new String[]{"foo_col"},
booleanType);
+ SQLFederationResultSetMetaData metaData = new
SQLFederationResultSetMetaData(
+ mock(), Collections.emptyList(), databaseType, resultType,
Collections.singletonMap(1, "foo_label"), converter);
+ assertThat(metaData.getColumnClassName(1),
is(Integer.class.getName()));
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("columnClassNameSource")
+ void assertGetColumnClassNameByType(final String name, final SqlTypeName
sqlTypeName, final String expectedClassName) {
+ RelDataType relDataType =
SQLFederationDataTypeFactory.getInstance().createSqlType(sqlTypeName);
+ RelDataType resultType = createResultType(new String[]{"foo_col"},
relDataType);
SQLFederationResultSetMetaData metaData = new
SQLFederationResultSetMetaData(
- mock(), Collections.emptyList(), databaseType,
createResultType(new String[]{"foo_col"}, varcharType),
Collections.singletonMap(1, "foo_label"), mock());
- assertThat(metaData.getColumnClassName(1),
is(SqlTypeName.VARCHAR.getClass().getName()));
+ mock(), Collections.emptyList(), databaseType, resultType,
Collections.singletonMap(1, "foo_label"), null);
+ assertThat(metaData.getColumnClassName(1), is(expectedClassName));
+ }
+
+ private static Iterable<Arguments> columnClassNameSource() {
+ return Arrays.asList(
+ Arguments.of("tinyint", SqlTypeName.TINYINT,
Byte.class.getName()),
+ Arguments.of("smallint", SqlTypeName.SMALLINT,
Short.class.getName()),
+ Arguments.of("integer", SqlTypeName.INTEGER,
Integer.class.getName()),
+ Arguments.of("bigint", SqlTypeName.BIGINT,
Long.class.getName()),
+ Arguments.of("float", SqlTypeName.FLOAT,
Double.class.getName()),
+ Arguments.of("real", SqlTypeName.REAL, Float.class.getName()),
+ Arguments.of("double", SqlTypeName.DOUBLE,
Double.class.getName()),
+ Arguments.of("decimal", SqlTypeName.DECIMAL,
BigDecimal.class.getName()),
+ Arguments.of("date", SqlTypeName.DATE,
Integer.class.getName()),
+ Arguments.of("timestamp", SqlTypeName.TIMESTAMP,
Long.class.getName()),
+ Arguments.of("varbinary", SqlTypeName.VARBINARY,
ByteString.class.getName()),
+ Arguments.of("any_fallback", SqlTypeName.ANY,
Object.class.getName()));
}
private RelDataType createRowType(final boolean nullable, final int
precision, final int scale) {
diff --git
a/kernel/sql-federation/core/src/test/java/org/apache/shardingsphere/sqlfederation/resultset/SQLFederationResultSetTest.java
b/kernel/sql-federation/core/src/test/java/org/apache/shardingsphere/sqlfederation/resultset/SQLFederationResultSetTest.java
index 5793386f7ee..d3af1da0cda 100644
---
a/kernel/sql-federation/core/src/test/java/org/apache/shardingsphere/sqlfederation/resultset/SQLFederationResultSetTest.java
+++
b/kernel/sql-federation/core/src/test/java/org/apache/shardingsphere/sqlfederation/resultset/SQLFederationResultSetTest.java
@@ -19,11 +19,14 @@ package org.apache.shardingsphere.sqlfederation.resultset;
import org.apache.calcite.linq4j.Enumerator;
import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import
org.apache.shardingsphere.infra.binder.context.segment.select.projection.Projection;
import
org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.ColumnProjection;
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import
org.apache.shardingsphere.sqlfederation.compiler.metadata.schema.SQLFederationSchema;
+import
org.apache.shardingsphere.sqlfederation.compiler.sql.type.SQLFederationDataTypeFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -46,6 +49,7 @@ import java.sql.SQLXML;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
@@ -580,6 +584,47 @@ class SQLFederationResultSetTest {
assertThat(federationResultSet.getObject("order_id"), is(10));
}
+ @Test
+ void assertColumnClassNameMatchesGetObjectValueClass() throws SQLException
{
+ assertColumnClassNameMatchesGetObjectValueClass(SqlTypeName.BIGINT,
1L);
+ assertColumnClassNameMatchesGetObjectValueClass(SqlTypeName.TINYINT,
(byte) 1);
+ assertColumnClassNameMatchesGetObjectValueClass(SqlTypeName.SMALLINT,
(short) 1);
+ assertColumnClassNameMatchesGetObjectValueClass(SqlTypeName.FLOAT, 1D);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void assertColumnClassNameMatchesGetObjectValueClass(final
SqlTypeName sqlTypeName, final Object value) throws SQLException {
+ Enumerator<Object> testEnumerator = mock(Enumerator.class);
+ when(testEnumerator.moveNext()).thenReturn(true, false);
+ when(testEnumerator.current()).thenReturn(new Object[]{value});
+ RelDataType columnType =
SQLFederationDataTypeFactory.getInstance().createSqlType(sqlTypeName);
+ RelDataType resultType = createResultType(new String[]{"value"},
columnType);
+ SQLFederationResultSet resultSet = new SQLFederationResultSet(
+ testEnumerator, mock(SQLFederationSchema.class),
+ Collections.singletonList(new ColumnProjection(null, "value",
null, databaseType)),
+ databaseType, resultType, "contract");
+ try {
+ assertTrue(resultSet.next());
+ Object actualValue = resultSet.getObject(1);
+ assertThat(resultSet.getMetaData().getColumnClassName(1),
is(actualValue.getClass().getName()));
+ } finally {
+ resultSet.close();
+ }
+ }
+
+ private RelDataType createResultType(final String[] names, final
RelDataType... types) {
+ List<RelDataTypeField> fields = new ArrayList<>(names.length);
+ for (int i = 0; i < names.length; i++) {
+ RelDataTypeField field = mock(RelDataTypeField.class);
+ when(field.getName()).thenReturn(names[i]);
+ when(field.getType()).thenReturn(types[i]);
+ fields.add(field);
+ }
+ RelDataType result = mock(RelDataType.class);
+ when(result.getFieldList()).thenReturn(fields);
+ return result;
+ }
+
@Test
void assertGetCalendarValueNullSetsWasNull() throws SQLException {
when(enumerator.current()).thenReturn(new Object[]{null, 1, "OK", 1});
diff --git
a/kernel/sql-federation/dialect/mysql/src/main/java/org/apache/shardingsphere/sqlfederation/mysql/MySQLSQLFederationColumnTypeConverter.java
b/kernel/sql-federation/dialect/mysql/src/main/java/org/apache/shardingsphere/sqlfederation/mysql/MySQLSQLFederationColumnTypeConverter.java
index d8d5a38c5ae..4e11060ba6b 100644
---
a/kernel/sql-federation/dialect/mysql/src/main/java/org/apache/shardingsphere/sqlfederation/mysql/MySQLSQLFederationColumnTypeConverter.java
+++
b/kernel/sql-federation/dialect/mysql/src/main/java/org/apache/shardingsphere/sqlfederation/mysql/MySQLSQLFederationColumnTypeConverter.java
@@ -20,6 +20,8 @@ package org.apache.shardingsphere.sqlfederation.mysql;
import org.apache.calcite.sql.type.SqlTypeName;
import
org.apache.shardingsphere.sqlfederation.resultset.converter.DialectSQLFederationColumnTypeConverter;
+import java.util.Optional;
+
/**
* SQL federation column type converter for MySQL.
*/
@@ -42,6 +44,11 @@ public final class MySQLSQLFederationColumnTypeConverter
implements DialectSQLFe
return result;
}
+ @Override
+ public Optional<Class<?>> convertColumnValueClass(final SqlTypeName
sqlTypeName) {
+ return SqlTypeName.BOOLEAN == sqlTypeName ? Optional.of(Integer.class)
: Optional.empty();
+ }
+
@Override
public String getDatabaseType() {
return "MySQL";
diff --git
a/kernel/sql-federation/dialect/mysql/src/test/java/org/apache/shardingsphere/sqlfederation/mysql/MySQLSQLFederationColumnTypeConverterTest.java
b/kernel/sql-federation/dialect/mysql/src/test/java/org/apache/shardingsphere/sqlfederation/mysql/MySQLSQLFederationColumnTypeConverterTest.java
index e63aec9cd98..84a4f768702 100644
---
a/kernel/sql-federation/dialect/mysql/src/test/java/org/apache/shardingsphere/sqlfederation/mysql/MySQLSQLFederationColumnTypeConverterTest.java
+++
b/kernel/sql-federation/dialect/mysql/src/test/java/org/apache/shardingsphere/sqlfederation/mysql/MySQLSQLFederationColumnTypeConverterTest.java
@@ -22,11 +22,13 @@ import
org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoa
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import
org.apache.shardingsphere.sqlfederation.resultset.converter.DialectSQLFederationColumnTypeConverter;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.Arrays;
+import java.util.Optional;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@@ -37,6 +39,13 @@ class MySQLSQLFederationColumnTypeConverterTest {
private final DialectSQLFederationColumnTypeConverter converter =
DatabaseTypedSPILoader.getService(DialectSQLFederationColumnTypeConverter.class,
databaseType);
+ @Test
+ void assertConvertColumnValueClass() {
+ assertThat(converter.convertColumnValueClass(SqlTypeName.BOOLEAN),
is(Optional.of(Integer.class)));
+ assertThat(converter.convertColumnValueClass(SqlTypeName.INTEGER),
is(Optional.empty()));
+ assertThat(converter.convertColumnValueClass(SqlTypeName.ANY),
is(Optional.empty()));
+ }
+
@ParameterizedTest(name = "{0}")
@MethodSource("convertValueSource")
void assertConvertColumnValue(final String name, final Object input, final
Object expected) {
diff --git a/test/e2e/driver/pom.xml b/test/e2e/driver/pom.xml
index 5cf6d4bc00b..b6e68a4e6ff 100644
--- a/test/e2e/driver/pom.xml
+++ b/test/e2e/driver/pom.xml
@@ -75,6 +75,12 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.shardingsphere</groupId>
+ <artifactId>shardingsphere-sql-federation-mysql</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-broadcast-core</artifactId>
diff --git
a/test/e2e/driver/src/test/java/org/apache/shardingsphere/test/e2e/driver/federation/SQLFederationResultSetColumnClassNameTest.java
b/test/e2e/driver/src/test/java/org/apache/shardingsphere/test/e2e/driver/federation/SQLFederationResultSetColumnClassNameTest.java
new file mode 100644
index 00000000000..a2c684f0ed8
--- /dev/null
+++
b/test/e2e/driver/src/test/java/org/apache/shardingsphere/test/e2e/driver/federation/SQLFederationResultSetColumnClassNameTest.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.test.e2e.driver.federation;
+
+import
org.apache.shardingsphere.driver.api.yaml.YamlShardingSphereDataSourceFactory;
+import
org.apache.shardingsphere.driver.jdbc.core.datasource.ShardingSphereDataSource;
+import org.apache.shardingsphere.test.e2e.driver.AbstractDriverTest;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import javax.sql.DataSource;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Regression test for <a
href="https://github.com/apache/shardingsphere/issues/39121">#39121</a>.
+ *
+ * <p>Runs a real query through ShardingSphere-JDBC with SQL federation forced
on for every {@code SELECT}
+ * ({@code allQueryUseSQLFederation: true}), so the {@code ResultSet} returned
to the caller is the actual
+ * production {@code SQLFederationResultSet} produced by the compiler and
dialect SPI, rather than a manually
+ * constructed one. Asserts that {@code
getMetaData().getColumnClassName(column)} always agrees with the
+ * runtime class of {@code getObject(column)}, both for a plain numeric column
and for a column whose value
+ * class is rewritten by {@code DialectSQLFederationColumnTypeConverter}
(MySQL {@code BOOLEAN -> Integer}).</p>
+ */
+class SQLFederationResultSetColumnClassNameTest extends AbstractDriverTest {
+
+ private static final String YAML_CONFIG = String.join("\n",
+ "databaseName: federation_db",
+ "",
+ "rules:",
+ "- !SINGLE",
+ " tables:",
+ " - \"*.*\"",
+ "",
+ "sqlFederation:",
+ " sqlFederationEnabled: true",
+ " allQueryUseSQLFederation: true",
+ " executionPlanCache:",
+ " initialCapacity: 2000",
+ " maximumSize: 65535",
+ "",
+ "props:",
+ " proxy-frontend-database-protocol-type: MySQL");
+
+ private static ShardingSphereDataSource dataSource;
+
+ @BeforeAll
+ static void initShardingSphereDataSource() throws SQLException,
IOException {
+ if (null == dataSource) {
+ DataSource actualDataSource =
getActualDataSources().get("single_jdbc");
+ dataSource = (ShardingSphereDataSource)
YamlShardingSphereDataSourceFactory.createDataSource(actualDataSource,
YAML_CONFIG.getBytes(StandardCharsets.UTF_8));
+ }
+ try (Connection connection = dataSource.getConnection(); Statement
statement = connection.createStatement()) {
+ statement.execute("CREATE TABLE IF NOT EXISTS
t_federation_column_type (id BIGINT NOT NULL, enabled BOOLEAN NOT NULL, PRIMARY
KEY (id))");
+ }
+ }
+
+ @AfterAll
+ static void closeShardingSphereDataSource() throws Exception {
+ if (null == dataSource) {
+ return;
+ }
+ dataSource.close();
+ dataSource = null;
+ }
+
+ @BeforeEach
+ void initData() throws SQLException {
+ try (
+ Connection connection = dataSource.getConnection();
+ PreparedStatement deleteStatement =
connection.prepareStatement("DELETE FROM t_federation_column_type");
+ PreparedStatement insertStatement =
connection.prepareStatement("INSERT INTO t_federation_column_type (id, enabled)
VALUES (?, ?)")) {
+ deleteStatement.executeUpdate();
+ insertStatement.setLong(1, 1L);
+ insertStatement.setBoolean(2, true);
+ insertStatement.executeUpdate();
+ }
+ }
+
+ @Test
+ void assertColumnClassNameMatchesGetObjectValueClassForBigint() throws
SQLException {
+ assertColumnClassNameMatchesGetObjectValueClass("id");
+ }
+
+ @Test
+ void
assertColumnClassNameMatchesGetObjectValueClassForDialectConvertedBoolean()
throws SQLException {
+ assertColumnClassNameMatchesGetObjectValueClass("enabled");
+ }
+
+ private void assertColumnClassNameMatchesGetObjectValueClass(final String
columnLabel) throws SQLException {
+ try (
+ Connection connection = dataSource.getConnection();
+ Statement statement = connection.createStatement();
+ ResultSet resultSet = statement.executeQuery("SELECT id,
enabled FROM t_federation_column_type WHERE id = 1")) {
+ assertTrue(resultSet.next());
+ int columnIndex = resultSet.findColumn(columnLabel);
+ Object actualValue = resultSet.getObject(columnIndex);
+
assertThat(resultSet.getMetaData().getColumnClassName(columnIndex),
is(actualValue.getClass().getName()));
+ }
+ }
+}