Kotlin infers JOOQ methods wrong

北城以北 提交于 2021-01-28 02:20:16

问题


Given a system using Kotlin version 1.3.61 and JOOQ version 3.13.1, a method like this builds an union query normally:

    val selectCommonPart = coalesce(sum(field(name("amount"), Long::class.java)), ZERO)
            .`as`(field(name("totalAmount")))
    var whereCommonPart: Condition = trueCondition().and(field(name("Id")).eq(Id)) // Id comes as a parameter

    var query = dsl.selectQuery()
    query.addSelect(selectCommonPart)
    query.addFrom(table("${tableName(startDate)}")) // `startDate` is a `LocalDate`, `tableName()` generates the table name as String
    query.addConditions(whereCommonPart)

    // `endDate` is also a `LocalDate`, can be either equal to `startDate` or after
    if (endDate.isAfter(startDate)) {
        for (date in Stream.iterate(startDate.plusDays(1), { d: LocalDate -> d.plusDays(1) })
                .limit(ChronoUnit.DAYS.between(startDate, endDate))
                .collect(Collectors.toList())) {

            val unionQuery = dsl.selectQuery()
            unionQuery.addSelect(selectCommonPart)
            unionQuery.addFrom(table("public.${tableName(date)}"))
            unionQuery.addConditions(whereCommonPart)

            // Here `union` is inferred correctly
            query.union(dsl.select(selectCommonPart)
                    .from("public.public.${tableName(date)}")
                    .where(whereCommonPart))
        }
    }

However, if I isolate the dsl.select(...) part in a method like:

private fun buildSelect(selectCommonPart: Field<*>, whereCommonPart: Condition, date: LocalDate): Select<*> {
    var query = dsl.selectQuery()
    query.addSelect(selectCommonPart)
    query.addFrom(table("public.${tableName(date)}"))
    query.addConditions(whereCommonPart)
    return query
}

And modify the loop to use:

    // Remove this part
    /* var query = dsl.selectQuery()
    query.addSelect(selectCommonPart)
    query.addFrom(table("${tableName(startDate)}")) // `startDate` is a `LocalDate`, `tableName()` generates the table name as String
    query.addConditions(whereCommonPart) */

    // Add this part
    var query = buildSelect(selectCommonPart, whereCommonPart, startDate)

    if (endDate.isAfter(startDate)) {
        for (date in Stream.iterate(startDate.plusDays(1), { d: LocalDate -> d.plusDays(1) })
                .limit(ChronoUnit.DAYS.between(startDate, endDate))
                .collect(Collectors.toList())) {

            // This gives an inference error
            query.union(buildSelect(selectCommonPart, whereCommonPart, date))
        }
    }

I have an inference error. Kotlin resolves union as this method:

/**
 * Returns a set containing all distinct elements from both collections.
 * 
 * The returned set preserves the element iteration order of the original collection.
 * Those elements of the [other] collection that are unique are iterated in the end
 * in the order of the [other] collection.
 * 
 * To get a set containing all elements that are contained in both collections use [intersect].
 */

public infix fun <T> Iterable<T>.union(other: Iterable<T>): Set<T> {
    val set = this.toMutableSet()
    set.addAll(other)
    return set
}

I want to use JOOQ's Select<*>'s union instead:

public interface Select<R extends Record> extends ResultQuery<R>, TableLike<R>, FieldLike {

    /**
     * Apply the <code>UNION</code> set operation.
     */
    @Support
    Select<R> union(Select<? extends R> select);

What should I do to infer the correct union method?


回答1:


Ok, I found out how to fix your issue.

Method buildSelect should returns Select< Record>, not Select< *>.

My suggestion why it happens:

The Select.union method has the following signature

public interface Select<R extends Record> extends ResultQuery<R>, TableLike<R>, FieldLike {
    @Support
    Select<R> union(Select<? extends R> var1);
....

As you can see, var1 should have the same generic( or extended) type as the object on which method was called. In your first implementation, method dsl.selectQuery() returns SelectQuery< Record> and both are variables query and unionQuery have the same generic type and the union method is correctly determined.

In the second implementation, query and the argument of query.union(...) have Select< *> type. I guess that the compiler thinks that both variables have different generic(since it is not determined and can be different) and the compiler can't use union from Select, but both variables implement Iterable and compiler choose Iterable.union since it fits.




回答2:


The problem is that for Select's union the argument's type parameter must be a subtype of the receiver's (? extends R); if both are Select<*>, it could be e.g. Select<Int>.union(Select<String>) which wouldn't type-check. This doesn't happen for Iterable because it's covariant.

I think buildSelect can be typed more precisely as

private fun <T : Record> buildSelect(selectCommonPart: Field<T>, whereCommonPart: Condition, date: LocalDate): Select<T>

and then since both calls have the same first argument it should work out.

EDIT: I simply assumed the types of selectCommonPart and the returned query had to match, but they don't and dsl.selectQuery() returns Select<Record>, so the signature in Maxim Popov's answer is the correct one.



来源:https://stackoverflow.com/questions/60384147/kotlin-infers-jooq-methods-wrong

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!