Kotlin 错误地推断 JOOQ 方法

Kotlin infers JOOQ methods wrong

给定一个使用 Kotlin 版本 1.3.61 和 JOOQ 版本 3.13.1 的系统,这样的方法可以正常构建 union 查询:

    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))
        }
    }

但是,如果我在如下方法中隔离 dsl.select(...) 部分:

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
}

并修改循环以使用:

    // 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))
        }
    }

我有一个推理错误。 Kotlin 将 union 解析为此方法:

/**
 * 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
}

我想改用JOOQ的Select<*>union:

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);

我应该怎么做才能推断出正确的union方法?

好的,我知道如何解决你的问题。

方法构建Select应该returnsSelect<记录>,而不是Select< *>.

我的建议为什么会这样:

Select.union 方法具有以下签名

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

如您所见,var1 应该与调用方法的对象具有相同的泛型(或扩展)类型。在您的第一个实现中,方法 dsl.selectQuery() returns SelectQuery< Record> 两者都是变量 queryunionQuery 具有相同的泛型类型并且正确确定了 union 方法。

在第二个实现中,查询query.union(...)的参数有Select< *>类型。我猜想编译器认为这两个变量具有不同的泛型(因为它未确定并且可以不同)并且编译器不能使用来自 Select 的联合,但是两个变量都实现了 Iterable 和编译器选择 Iterable.union 因为它适合。

问题是对于 Selectunion 参数的类型参数必须是接收者 (? extends R) 的子类型;如果两者都是Select<*>,它可能是例如Select<Int>.union(Select<String>) 不会进行类型检查。 Iterable 不会发生这种情况,因为它是协变的。

我认为 buildSelect 可以更准确地键入

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

然后因为两个调用都有相同的第一个参数,所以应该可以解决。

编辑:我只是假设 selectCommonPart 的类型和返回的查询必须匹配,但是 they don'tdsl.selectQuery() returns Select<Record>,所以Maxim Popov 回答中的签名是正确的。