Stamatis, Vladimir, thanks for your comments.

The empty constructor Stamatis is suggesting would probably be enough, but
what Vladimir is suggesting would also improve on the annotation front (in
particular, it's easier to annotate just the constructor rather than each
field in the whole hierarchy).

I am using Jackson from time to time but I did not know about that feature,
I will dig more and try to improve the current implementation I have. Would
you mind writing a small example of what you have in mind?

Googling the topic a bit more, I also found ConstructorParameters
<https://docs.oracle.com/javase/9/docs/api/javax/management/ConstructorParameters.html>
annotation (Java7+) which might help:

> @ConstructorParameters({"aid", "name", "birthPlace", "books"})

public Author(int aid, String name, Place birthPlace, List<Book> books) {
>       this.aid = aid;
>       this.name = name;
>       this.birthPlace = birthPlace;
>       this.books = books;
>     }


Regarding "newExpression", any test making use of a JavaRowType is affected
(e.g., ReflectiveSchemaTest.testSelectWithFieldAccessOnSecondLevelRecordType
<https://github.com/apache/calcite/blob/c475124ef8c61eb398ee119f27dfe21a14eca32f/core/src/test/java/org/apache/calcite/test/ReflectiveSchemaTest.java#L336>),
but the dependency on the field order does not come from the test itself,
but it's tied to the way expressions representing rows are built for
enumerable tables.

The aforementioned test runs the following query:

"select au.\"birthPlace\".\"coords\".\"latitude\" as lat\n"
    + "from \"bookstore\".\"authors\" au\n"

While building rows for "Authors" table (backed up by a Java object),
JavaRowFormat.record()
<https://github.com/apache/calcite/blob/c475124ef8c61eb398ee119f27dfe21a14eca32f/core/src/main/java/org/apache/calcite/adapter/enumerable/JavaRowFormat.java#L61>
is invoked, and it builds an expression of this kind, which invokes
Authors's constructor with the appropriate parameters:

new org.apache.calcite.test.BookstoreSchema.Author(
>   row.aid,
>   row.name,
>   row.birthPlace,
>   org.apache.calcite.linq4j.Linq4j.asEnumerable(row.books).select(new
> org.apache.calcite.linq4j.function.Function1() {
>     public java.util.List
> apply(org.apache.calcite.test.BookstoreSchema.Book o) {
>       return (java.util.List)
> org.apache.calcite.runtime.FlatLists.of(o.title, o.publishYear, o.pages);
>     }
>     public Object apply(Object o) {
>       return apply(
>         (org.apache.calcite.test.BookstoreSchema.Book) o);
>     }
>   }


In this case, since my "default" ordering was different from (sorting
alphabetically) the "declaration order" used by the JVM, I had to provide
the same ordering via the CalciteField annotation, in order to still match
the constructor:

public static class Author {
  @CalciteField(order = 1)
  public final int aid;
  @CalciteField(order = 2)
  public final String name;
  @CalciteField(order = 3)
  public final Place birthPlace;
  @CalciteField(order = 4)
  @org.apache.calcite.adapter.java.Array(component = Book.class)
  public final List<Book> books;

  public Author(int aid, String name, Place birthPlace, List<Book> books) {
    this.aid = aid;
    this.name = name;
    this.birthPlace = birthPlace;
    this.books = books;
  }
}

If you start from master, and you swap any two parameters in Author (either
in the field declaration, or in the constructor), all tests related to
Author will fail since the constructor invocation part won't compile due to
signature mismatch (the same apply to any other such class used in tests).

For instance, I moved "name" field as the last declared field in Author,
and below you can see that the constructor invocation expects it as last
parameter:

java.sql.SQLException: Error while executing SQL "select
> au."birthPlace"."coords"."latitude" as lat
> from "bookstore"."authors" au
> ": Error while compiling generated Java code:
> public org.apache.calcite.linq4j.Enumerable bind(final
> org.apache.calcite.DataContext root) {
>   final org.apache.calcite.linq4j.Enumerable _inputEnumerable =
> org.apache.calcite.linq4j.Linq4j.asEnumerable(((org.apache.calcite.test.BookstoreSchema)
> ((org.apache.calcite.adapter.java.ReflectiveSchema)
> root.getRootSchema().getSubSchema("bookstore").unwrap(org.apache.calcite.adapter.java.ReflectiveSchema.class)).getTarget()).authors).select(new
> org.apache.calcite.linq4j.function.Function1() {
>     public org.apache.calcite.test.BookstoreSchema.Author
> apply(org.apache.calcite.test.BookstoreSchema.Author row) {
>       return new org.apache.calcite.test.BookstoreSchema.Author(
>           row.aid,
>           row.birthPlace,
>
> org.apache.calcite.linq4j.Linq4j.asEnumerable(row.books).select(new
> org.apache.calcite.linq4j.function.Function1() {
>             public java.util.List
> apply(org.apache.calcite.test.BookstoreSchema.Book o) {
>               return org.apache.calcite.runtime.FlatLists.of(o.title,
> o.publishYear, o.pages);
>             }
>             public Object apply(Object o) {
>               return apply(
>                 (org.apache.calcite.test.BookstoreSchema.Book) o);
>             }
>           }
>           ).toList(),
>           row.name); <----- should be the second param
>     }
>     public Object apply(Object row) {
>       return apply(
>         (org.apache.calcite.test.BookstoreSchema.Author) row);
>     }
>   }
>   );

[...]
>

It's NewExpression.accept() (NewExpression.java#L63
<https://github.com/apache/calcite/blob/c475124ef8c61eb398ee119f27dfe21a14eca32f/linq4j/src/main/java/org/apache/calcite/linq4j/tree/NewExpression.java#L63>)
generating that string, which indirectly receives the parameter order
from JavaTypeFactoryImpl.createStructType(Class
type)
<https://github.com/apache/calcite/blob/c475124ef8c61eb398ee119f27dfe21a14eca32f/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java#L78>,
which it was using Class.getFields(), so in the end the output of that
method has always been matching the declaration order, which is also the
order used for the constructor of all the Java objects used in Calcite
codebase (that's why in the diff you can see I had to put annotations in
all of them to keep the same expected test results).

Best regards,
Alessandro

On Tue, 9 Feb 2021 at 11:35, Vladimir Sitnikov <sitnikov.vladi...@gmail.com>
wrote:

> Alessandro, thanks for pushing this.
>
> Frankly speaking, I don't like @CalciteField(order=42) approach. It looks
> like annotating the constructor parameters makes more sense, and it is
> more flexible.
> A similar case is covered in Jackson with "parameter names" module:
>
> https://github.com/FasterXML/jackson-modules-java8/tree/master/parameter-names
> Just in case, Java8+ can include parameter names to the reflection info in
> case user supplies `-parameters` option for the compiler.
>
> >NewExpression
>
> It might indeed be a too low-level API for that.
> Could you please highlight which test relies on the order of constructor
> arguments?
>
> By the way, SQL structs rely on the order of the fields rather than field
> names.
> So it might be ok to rely on the order of the constructor arguments.
>
> Vladimir
>

Reply via email to