diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysTypeImpl.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysTypeImpl.java index 7464a273eae7..406322d96de7 100644 --- a/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysTypeImpl.java +++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/PhysTypeImpl.java @@ -636,13 +636,13 @@ public Expression fieldReference( public Expression fieldReference( Expression expression, int field, Type storageType) { - Type fieldType; + Class fieldType; if (storageType == null) { storageType = fieldClass(field); fieldType = null; } else { fieldType = fieldClass(field); - if (fieldType != java.sql.Date.class) { + if (!java.util.Date.class.isAssignableFrom(fieldType)) { fieldType = null; } } diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java index 1a63068e9a3d..166fcc0be75a 100644 --- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java +++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java @@ -1020,6 +1020,18 @@ public static Expression convert(Expression operand, Type fromType, } else { return Expressions.convert_(operand, toType); } + } else if (fromType == java.sql.Time.class) { + if (toBox == Primitive.INT) { + return Expressions.call(BuiltInMethod.TIME_TO_INT.method, operand); + } else { + return Expressions.convert_(operand, toType); + } + } else if (fromType == java.sql.Timestamp.class) { + if (toBox == Primitive.LONG) { + return Expressions.call(BuiltInMethod.TIMESTAMP_TO_LONG.method, operand); + } else { + return Expressions.convert_(operand, toType); + } } else if (toType == java.sql.Date.class) { // E.g. from "int" or "Integer" to "java.sql.Date", // generate "SqlFunctions.internalToDate". diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java index 7644d0a3eb33..6844df794a72 100644 --- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java +++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java @@ -1610,6 +1610,7 @@ public static long toLong(Object o) { return o instanceof Long ? (Long) o : o instanceof Number ? toLong((Number) o) : o instanceof String ? toLong((String) o) + : o instanceof java.util.Date ? toLong((java.util.Date) o) : (Long) cannotConvert(o, long.class); } diff --git a/core/src/test/java/org/apache/calcite/test/ObjectArrayTableTest.java b/core/src/test/java/org/apache/calcite/test/ObjectArrayTableTest.java new file mode 100644 index 000000000000..dd5e2772f4d7 --- /dev/null +++ b/core/src/test/java/org/apache/calcite/test/ObjectArrayTableTest.java @@ -0,0 +1,93 @@ +package org.apache.calcite.test; + +import org.apache.calcite.adapter.java.AbstractQueryableTable; +import org.apache.calcite.jdbc.CalciteConnection; +import org.apache.calcite.jdbc.JavaTypeFactoryImpl; +import org.apache.calcite.linq4j.Enumerable; +import org.apache.calcite.linq4j.EnumerableDefaults; +import org.apache.calcite.linq4j.Linq4j; +import org.apache.calcite.linq4j.QueryProvider; +import org.apache.calcite.linq4j.Queryable; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeFactory; +import org.apache.calcite.rel.type.RelDataTypeSystem; +import org.apache.calcite.schema.SchemaPlus; +import org.apache.calcite.schema.Table; +import org.junit.Test; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ObjectArrayTableTest { + + /** Test case for + * [CALCITE-1703] + * Functions on TIME and TIMESTAMP column can throw ClassCastException. */ + @Test public void testJavaSqlDateColumnReference() throws SQLException { + + try ( + Connection connection = DriverManager.getConnection("jdbc:calcite:"); + final CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class) + ) { + final SchemaPlus rootSchema = calciteConnection.getRootSchema(); + + final JavaTypeFactoryImpl typeFactory = + new JavaTypeFactoryImpl(RelDataTypeSystem.DEFAULT); + + final RelDataType rowType =typeFactory.createStructType( + Arrays.asList( + typeFactory.createJavaType(java.sql.Date.class), + typeFactory.createJavaType(java.sql.Time.class), + typeFactory.createJavaType(java.sql.Timestamp.class) + ), + Arrays.asList("dt", "tm", "ts") + ); + + final Enumerable enumerable = + Linq4j.asEnumerable( + Collections.singletonList(new Object[]{ + java.sql.Date.valueOf("2018-12-14"), + java.sql.Time.valueOf("18:29:34"), + java.sql.Timestamp.valueOf("2018-12-14 18:29:34.123")})); + + final Table table = new AbstractQueryableTable(Object[].class) { + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + public Queryable asQueryable(QueryProvider queryProvider, SchemaPlus schema, String tableName) { + return EnumerableDefaults.asOrderedQueryable(enumerable); + } + + @Override + public RelDataType getRowType(RelDataTypeFactory typeFactory) { + return rowType; + } + }; + + rootSchema.add("java_sql_date_types", table); + + // 'extract' function expects numeric date representation. + // RexToLixTranslator.convert() handles Java type to numeric conversion. + final Statement statement = connection.createStatement(); + String sql = "select extract(year from \"dt\")," + + " extract(hour from \"tm\")," + + " extract(day from \"ts\") from \"java_sql_date_types\""; + + ResultSet resultSet = statement.executeQuery(sql); + assertTrue(resultSet.next()); + assertEquals(2018, resultSet.getInt(1)); + // Although using 'extract' function against time value doesn't make sense, this confirms time column can be converted to int. + assertEquals(0, resultSet.getInt(2)); + assertEquals(14, resultSet.getInt(3)); + + } + } + +}