Uploaded image for project: 'Calcite'
  1. Calcite
  2. CALCITE-3364

ClassCastException if group by is used on the result of scalar valued table function

    XMLWordPrintableJSON

Details

    • Bug
    • Status: Closed
    • Major
    • Resolution: Fixed
    • 1.21.0
    • 1.22.0
    • None

    Description

      I was not able to find a suitable test, so I'll describe the issue using a custom minimal table function returning a row with a single value as an example. I believe, that it should be reproducible against any table function.

      There is a simple table function my_dummy() that just prints numbers 1 to 5. Simple select works as expected:

      SQL:
      select val from table(my_dummy())
      Result:
      val : INTEGER
      5
      4
      3
      2
      1
      

      However, when trying to make an aggregation on top of it, there is a class cast error:

      SQL:
      select val from table(my_dummy())
      group by val
      
      Error:
      java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to java.lang.Integer
      	at org.apache.calcite.avatica.util.AbstractCursor$IntAccessor.getInt(AbstractCursor.java:541)
      	at org.apache.calcite.avatica.AvaticaSite.get(AvaticaSite.java:340)
      	at org.apache.calcite.avatica.AvaticaResultSet.getObject(AvaticaResultSet.java:393)
      

      Actually, this array Object[] contains a single integer produced by the table function. Also I faced similar class cast errors in a generated code making hashJoin (in a complex production code), I believe, the cause should be the same as in this minimal example.

      Here is this my_dummy() table function implementation (single file):

      package com.myapp.tf;
      
      import org.apache.calcite.DataContext;
      import org.apache.calcite.linq4j.AbstractEnumerable;
      import org.apache.calcite.linq4j.Enumerable;
      import org.apache.calcite.linq4j.Enumerator;
      import org.apache.calcite.linq4j.tree.Types;
      import org.apache.calcite.rel.type.RelDataType;
      import org.apache.calcite.rel.type.RelDataTypeFactory;
      import org.apache.calcite.schema.ScannableTable;
      import org.apache.calcite.schema.TableFunction;
      import org.apache.calcite.schema.impl.AbstractTable;
      import org.apache.calcite.schema.impl.TableFunctionImpl;
      import org.apache.calcite.sql.SqlIdentifier;
      import org.apache.calcite.sql.SqlOperatorBinding;
      import org.apache.calcite.sql.parser.SqlParserPos;
      import org.apache.calcite.sql.type.SqlTypeName;
      import org.apache.calcite.sql.validate.SqlUserDefinedTableFunction;
      import java.util.Collections;
      import java.util.concurrent.atomic.AtomicInteger;
      import static org.apache.calcite.sql.type.OperandTypes.family;
      
      public class DummyFunction extends SqlUserDefinedTableFunction {
      
          public static String DUMMY_FUNCTION_NAME = "my_dummy";
      
          public static final TableFunction DUMMY_TABLE_FUNCTION = TableFunctionImpl.create(Types.lookupMethod(
              DummyFunction.class,
              "createDummyTable"
          ));
      
          public static DummyTable createDummyTable() {
              return new DummyTable();
          }
      
          public DummyFunction() {
              super(new SqlIdentifier(DUMMY_FUNCTION_NAME, SqlParserPos.ZERO),
                  null,
                  null,
                  family(),
                  Collections.emptyList(),
                  DUMMY_TABLE_FUNCTION
              );
          }
      
          @Override
          public RelDataType inferReturnType(SqlOperatorBinding opBinding) {
              RelDataTypeFactory typeFactory = opBinding.getTypeFactory();
      
              return typeFactory.builder()
                  .add("val", SqlTypeName.INTEGER)
                  .build();
          }
      }
      
      class DummyTable extends AbstractTable implements ScannableTable {
      
          private final AtomicInteger cnt = new AtomicInteger(6);
      
          @Override
          public Enumerable<Object[]> scan(DataContext root) {
              return new AbstractEnumerable<Object[]>() {
                  public Enumerator<Object[]> enumerator() {
                      return new Enumerator<Object[]> () {
                          @Override
                          public Object[] current() {
                              return new Object[] { cnt.intValue() };
                          }
      
                          @Override
                          public boolean moveNext() {
                              return cnt.decrementAndGet() > 0;
                          }
      
                          @Override
                          public void reset() {
      
                          }
      
                          @Override
                          public void close() {
      
                          }
                      };
                  }
              };
          }
      
          @Override
          public RelDataType getRowType(RelDataTypeFactory typeFactory) {
              return typeFactory.builder()
                  .add("val", SqlTypeName.INTEGER)
                  .build();
          }
      }
      

      And adding it into a schema:

      mySchemaPlus.add(DUMMY_FUNCTION_NAME, DummyFunction.DUMMY_TABLE_FUNCTION);
      

      Attachments

        Issue Links

          Activity

            People

              donnyzone Feng Zhu
              anha Anton Haidai
              Votes:
              0 Vote for this issue
              Watchers:
              6 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved:

                Time Tracking

                  Estimated:
                  Original Estimate - Not Specified
                  Not Specified
                  Remaining:
                  Remaining Estimate - 0h
                  0h
                  Logged:
                  Time Spent - 20m
                  20m