Search code examples
jooq

Why is there a difference between using `field(select(...))` and `select(...).asField()`?


In the following code, my intuition is that the variables x and y get the same type:

var x = field(select(T.A).from(T));
var y = select(T.A).from(T).asField();

If I ask IntelliJ to replace var with explicit type I get:

Field<String> x = field(select(T.A).from(T));
Field<Object> y = select(T.A).from(T).asField();

I trust IntelliJ over my intuition, so now I wonder why. Is this a limitation in Java? A bug in jOOQ? Something else?

Here's the method signatures for field and asField for reference:

public static <T> Field<T> field(Select<? extends Record1<T>> select);
public <T> Field<T> asField();

Solution

  • DSL.field() is a generic method that can capture its type variable T. FieldLike.asField() cannot capture a type variable based on whatever subtype of FieldLike you're using, including Select<R>. Due to a historic mistake, it's still a generic record, which allows you to unsafely cast the T type at the use-site. But that's just an unsafe cast, not a correct capture of your desired type String

    There's no way the asField() method could be fixed in Java as the jOOQ API is designed right now, because the type variable R in Select<R> is not aware of the fact whether it is effectively a Record1<T> (a scalar subquery), or anything else.

    Other languages can add additional "constraints" on their generic type bounds, or they allow for using extension methods to give the feeling of a .asField() "method", which in fact is really just a static function like DSL.field(), but with a suffix notation on their first parameter.

    The only valid solution in Java would have been ot "overload" the Select type with:

    • Select1<T1, R extends Record1<T1>> extends Select<R>
    • Select2<T1, T2, R extends Record2<T1, T2>> extends Select<R>
    • Select3<T1, T2, T3, R extends Record3<T1, T2, T3>> extends Select<R>
    • ...

    But that would have tons of additional, undesirable side effects thoughout the jOOQ API, which is why the idea was rejected and DSL.field() was introduced.

    Logically, the two methods are equivalent, but DSL.field() is superior because it captures the correct type.