diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeFactory.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeFactory.java index f1d54c1ce4846..79c136b9053a7 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeFactory.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeFactory.java @@ -296,7 +296,11 @@ else if (type instanceof IgniteCustomType) return customType; } - return toSql(this, type); + RelDataType sqlType = toSql(this, type); + + // Java types without a built-in SQL mapping must use Ignite's OTHER semantics, including casts. + return sqlType.getSqlTypeName() == SqlTypeName.OTHER + ? createCustomType(Object.class, sqlType.isNullable()) : sqlType; } /** Converts Java types, including Java time types, to SQL types using the supplied factory. */ diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DataTypesTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DataTypesTest.java index b26572cdec8c3..2838fe2d1d2c1 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DataTypesTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DataTypesTest.java @@ -1125,6 +1125,23 @@ public void testCharToVarcharConversion() { .check(); } + /** */ + @Test + public void testOtherTypeCastToVarchar() { + sql("CREATE TABLE t(id INT PRIMARY KEY, oth OTHER) WITH " + atomicity()); + + sql("INSERT INTO t VALUES (1, 'str'), (2, 42), (3, NULL)"); + + assertQuery("SELECT CAST(id AS VARCHAR), CAST(oth AS VARCHAR) FROM t ORDER BY id") + .ordered() + .returns("1", "str") + .returns("2", "42") + .returns("3", null) + .check(); + + assertQuery("SELECT id FROM t WHERE CAST(oth AS VARCHAR) = '42'").returns(2).check(); + } + /** */ private void assumeNoTransactions() { assumeTrue(sqlTxMode == SqlTransactionMode.NONE, "Test use queries that doesn't touch any data. Skip for tx modes"); diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SystemViewsIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SystemViewsIntegrationTest.java index c34ac41eb5921..333d69aa67531 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SystemViewsIntegrationTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SystemViewsIntegrationTest.java @@ -17,6 +17,7 @@ package org.apache.ignite.internal.processors.query.calcite.integration; +import java.math.BigDecimal; import java.sql.Date; import java.util.Collection; import org.apache.ignite.cluster.ClusterNode; @@ -59,6 +60,46 @@ public void testMetricsView() { assertQuery("SELECT value FROM sys.metrics WHERE name = 'cluster.TotalClientNodes'").returns("1").check(); } + /** */ + @Test + public void testClassColumn() { + sql("CREATE TABLE t(id INT PRIMARY KEY, name VARCHAR, amount DECIMAL(10, 2))"); + + assertQuery("SELECT CAST(\"TYPE\" AS VARCHAR) AS TYPE_NAME FROM SYS.TABLE_COLUMNS;") + .returns(Object.class.toString()) + .returns(Integer.class.toString()) + .returns(Integer.class.toString()) + .returns(String.class.toString()) + .returns(BigDecimal.class.toString()) + .check(); + + assertQuery("SELECT COLUMN_NAME, \"TYPE\" FROM SYS.TABLE_COLUMNS") + .returns("_VAL", Object.class) + .returns("_KEY", Integer.class) + .returns("ID", Integer.class) + .returns("NAME", String.class) + .returns("AMOUNT", BigDecimal.class) + .check(); + + assertQuery("SELECT COLUMN_NAME FROM SYS.TABLE_COLUMNS WHERE CAST(\"TYPE\" AS VARCHAR) = ?") + .withParams(String.class.toString()) + .returns("NAME") + .check(); + + // Creating Ignite's OTHER type must not affect Class-valued system view columns. + sql("CREATE TABLE other_seed(id INT PRIMARY KEY, val OTHER)"); + sql("INSERT INTO other_seed VALUES (1, 'seed')"); + assertQuery("SELECT val FROM other_seed").returns("seed").check(); + + assertQuery("SELECT CAST(\"TYPE\" AS VARCHAR) AS TYPE_NAME FROM SYS.TABLE_COLUMNS WHERE TABLE_NAME = 'T'") + .returns(Object.class.toString()) + .returns(Integer.class.toString()) + .returns(Integer.class.toString()) + .returns(String.class.toString()) + .returns(BigDecimal.class.toString()) + .check(); + } + /** */ @Test public void testFiltrableView() { diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/type/OtherTypeTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/type/OtherTypeTest.java index 91107a2c34066..2c34c0de785e7 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/type/OtherTypeTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/type/OtherTypeTest.java @@ -65,4 +65,20 @@ public void testTypeInterning() { calciteFactory.createSqlType(SqlTypeName.OTHER), nullable)); } } + + /** */ + @Test + public void testJavaTypeConversion() { + IgniteTypeFactory factory = new IgniteTypeFactory(); + + for (boolean nullable : new boolean[] {false, true}) { + RelDataType javaType = factory.createTypeWithNullability(factory.createJavaType(Class.class), nullable); + RelDataType sqlType = factory.toSql(javaType); + + assertEquals(OtherType.class, sqlType.getClass()); + assertEquals(SqlTypeName.ANY, sqlType.getSqlTypeName()); + assertEquals(nullable, sqlType.isNullable()); + assertSame(factory.createCustomType(Object.class, nullable), sqlType); + } + } }