[ 
https://issues.apache.org/jira/browse/CALCITE-4708?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17925358#comment-17925358
 ] 

Artem Simeshin commented on CALCITE-4708:
-----------------------------------------

I also encountered this problem when using ReflectiveSchema. 

If the problem at the moment is just to define the Type for generic, I'd like 
to propose the solution for the Field instead of Class from the Field with 
[https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/ParameterizedType.html]
 

Simple example:
{code:java}
package example;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

public class Example {

    public static void main(String[] args) throws NoSuchFieldException {
        final CustomSchema customSchema = new CustomSchema();
        final Field tableField = 
customSchema.getClass().getDeclaredField("custom_table");

        //The solution is to use java.lang.reflect.ParameterizedType
        final Type actualTypeArgument = ((ParameterizedType) 
tableField.getGenericType()).getActualTypeArguments()[0];

        //Will print 'example.Example$CustomEntity'
        System.out.println(actualTypeArgument.getTypeName());
    }

    public static class CustomSchema {
        public List<CustomEntity> custom_table = new ArrayList<>();
    }

    public static class CustomEntity {
        private String id;
    }
} {code}
 

Actually the javadoc for the ParamterizedType class states that 
ParameterizedType represents a parameterized type such as Collection<String>, 
but the functionality works great for any custom Iterable, for example I want 
to create my custom Iterable and work in the ReflectiveSchema:

 
{code:java}
package example;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.Spliterator;
import java.util.function.Consumer;

public class Example {

    public static void main(String[] args) throws NoSuchFieldException {
        final CustomSchema customSchema = new CustomSchema();
        final Field tableField = 
customSchema.getClass().getDeclaredField("custom_table");

        //The solution is to use java.lang.reflect.ParameterizedType
        final Type actualTypeArgument = ((ParameterizedType) 
tableField.getGenericType()).getActualTypeArguments()[0];

        //Will print 'example.Example$CustomEntity'
        System.out.println(actualTypeArgument.getTypeName());
    }

    public static class CustomSchema {
        public MyCollection<CustomEntity> custom_table = new MyCollection<>();
    }

    public static class CustomEntity {
        private String id;
    }

    public static class MyCollection<T> implements Iterable<T> {

        @Override
        public Iterator<T> iterator() {
            return null;
        }

        @Override
        public void forEach(Consumer<? super T> action) {
            Iterable.super.forEach(action);
        }

        @Override
        public Spliterator<T> spliterator() {
            return Iterable.super.spliterator();
        }
    }
} {code}
 

 

Based on all this information, I propose to change the getElementType method in 
the ReflectiveSchema class.
At the moment code looks like this:
{code:java}
  /** Deduces a collection's element type;
   * same logic as {@link #toEnumerable}. */
  private static @Nullable Type getElementType(Class<?> clazz) {
    if (clazz.isArray()) {
      return clazz.getComponentType();
    }
    if (Iterable.class.isAssignableFrom(clazz)) {
      return Object.class;
    }
    return null; // not a collection/array/iterable
  } {code}
 

I propose to change argument type from Class to Field, than to use 
ParameterizedType for Iterable:

 
{code:java}
/** Deduces a collection's element type;
 * same logic as {@link #toEnumerable}. */
private static Type getElementType(Field field) {
    final Class<?> clazz = field.getType();
    if (clazz.isArray()) {
        return clazz.getComponentType();
    }
    if (Iterable.class.isAssignableFrom(clazz)) {
        try {
            return ((ParameterizedType) 
field.getGenericType()).getActualTypeArguments()[0];
        } catch (ClassCastException e) {
            return Object.class;
        }
    }
    return null; // not a collection/array/iterable
} {code}
I tested the solution in the local environment and it fully solved my and 
Vladimir Polukeev question without additional Annotation. I`d like to discuss 
and then contribute this solution if its possible.

 

 

The next question is on the same topic, I don’t understand why this case is 
completely legal and not processed at all? New users using ReflectiveSchema are 
guaranteed to sooner or later encounter the usage case a collection instead of 
an array, and not everyone can figure out what is going wrong.

In this case, I suggest notifying users in the logs that something is going 
wrong, in addition to `return Object.class;`, because even in proposed solution 
users can use non-generic version for the Iterable:
{code:java}
public static class CustomSchema {
    //Instead of this
    public List<CustomEntity> custom_table1 = new ArrayList<>();
    //Users can do this, and code will compile
    public List custom_table2 = new ArrayList<CustomEntity>();
} {code}
 

> Infer list generic type while Table instance is created at ReflectiveSchema 
> class
> ---------------------------------------------------------------------------------
>
>                 Key: CALCITE-4708
>                 URL: https://issues.apache.org/jira/browse/CALCITE-4708
>             Project: Calcite
>          Issue Type: Improvement
>          Components: core
>    Affects Versions: 1.27.0
>         Environment: Ubuntu 18.04
> Java 11
> Maven 3.6.0
>            Reporter: Vladimir Polukeev
>            Priority: Major
>              Labels: pull-request-available
>          Time Spent: 10m
>  Remaining Estimate: 0h
>
> I have such code:
> {code:java}
> // 
> public class Main {    
>     public static void main(String[] args) throws SQLException {
>         // 1. Create calcite connection
>         Properties info = new Properties();
>         info.setProperty("lex", "JAVA");
>         try(Connection connection = 
> DriverManager.getConnection("jdbc:calcite:", info)) {
>             Employee employee1 = new Employee("first name 1", "last name 1");
>             Employee employee2 = new Employee("first name 2", "last name 2");
>             Employee employee3 = new Employee("first name 3", "last name 3"); 
>            
>             
>             List<Employee> employeeList = new LinkedList<>();
>             employeeList.add(employee1);
>             employeeList.add(employee2);
>             employeeList.add(employee3);           
>             MyScheme myScheme = new MyScheme();
>             myScheme.employees = employeeList;
>             Schema schema = new ReflectiveSchema(myScheme);           
>             // 2. add scheme with data to root scheme
>             CalciteConnection calciteConnection = 
> connection.unwrap(CalciteConnection.class);
>             SchemaPlus rootSchema = calciteConnection.getRootSchema();
>             rootSchema.add("cache", schema);            
>             // 3. execute sql query against scheme
>             String sql = "select e.* from cache.employees e";                 
>             ResultSet resultSet = 
> calciteConnection.createStatement().executeQuery(sql);            
>             
>             // 4. read data from result set
>             while (resultSet.next()) {
>                 printValues(resultSet);
>             }
>         }
>     }   
>     public static class MyScheme {
>         public List<Employee> employees;
>     }    
>     @AllArgsConstructor
>     public static class Employee {
>         public String firstName;
>         public String lastName;
>     }    
>     public static void printValues(ResultSet resultSet) throws SQLException {
>         System.out.println("--------------- row " + resultSet.getRow() + " 
> ---------------");
>         ResultSetMetaData metaData = resultSet.getMetaData();
>         for (int i = 1; i <= metaData.getColumnCount(); i++) {
>             String columnName = metaData.getColumnName(i);
>             System.out.println(columnName + " : " +  
> resultSet.getObject(columnName));
>         }
>     }
> }
> {code}
>   
> Code execution output:
> {noformat}
> --------------- row 1 ---------------
> --------------- row 2 ---------------
> --------------- row 3 ---------------{noformat}
>  
> Calcite correctly gets 3 rows, but there is no information about columns at 
> output due to item class at Iterable collection infer as Object.class. Here 
> is code from ReflectiveSchema.class:
>  
> {code:java}
> // 
> private static @Nullable Type getElementType(Class clazz) {
>   if (clazz.isArray()) {
>     return clazz.getComponentType();
>   }
>   if (Iterable.class.isAssignableFrom(clazz)) {
>     return Object.class;
>   }
>   return null; // not a collection/array/iterable
> }
> {code}
>  
>  
> Information about columns is retured If I change List<Employee> to Employee[] 
> at MyScheme.class. But I have to use List due to my task requirement.
> I manage this issue such way:
> Java compiler erise information about generic type at compile time. Therefore 
> I supply information about class using annotation 
> org.apache.calcite.adapter.java.Array:
> {code:java}
> // 
> public static class MyScheme {
> +   @Array(component = Employee.class)
>     public List<Employee> employees;
> }
> {code}
> Than I read element type from annotation at runtime. Here is code from 
> improved ReflectiveSchema.class:
> {code:java}
> // 
> private <T> @Nullable Table fieldRelation(final Field field) {
> +    Array arrayAnnotation = field.getAnnotation(Array.class);
> +    Class<?> elementListClass = arrayAnnotation != null ? 
> arrayAnnotation.component() : null;
> +    final Type elementType = getElementType(field.getType(), 
> elementListClass);
>     ... // redundant method code is omitted
> }
> {code}
> Finally, I return type according to information retrieved from Array 
> annotation:
> {code:java}
> //
> + private static @Nullable Type getElementType(Class clazz, Class<?> 
> elementListClass) {
>     if (clazz.isArray()) {
>         return clazz.getComponentType();
>     }
>     if (Iterable.class.isAssignableFrom(clazz)) {
> +        return elementListClass != null ? elementListClass : Object.class;
>     }
>     return null; // not a collection/array/iterable
> }
> {code}
>  
> Now, code execution output:
> {noformat}
> --------------- row 1 ---------------
> firstName : first name 1
> lastName : last name 1
> --------------- row 2 ---------------
> firstName : first name 2
> lastName : last name 2
> --------------- row 3 ---------------
> firstName : first name 3
> lastName : last name 3{noformat}
>  
> Does such improvement fit to framework design? Can I create pull request in 
> order to make such improvement?



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to