【问题标题】:Kotlin infers JOOQ methods wrongKotlin 推断 JOOQ 方法错误
【发布时间】:2020-06-08 13:39:18
【问题描述】:

给定一个使用 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&lt;*&gt;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 方法?

【问题讨论】:

  • 我的 Kotlin-fu 不是很好,所以我无法解释为什么会发生这种情况,但是推断函数返回类型而不是显式提供它们真的是最佳实践吗?特别是当涉及到像 jOOQ 这样的复杂类型系统时(a jOOQ ResultQuery extends Iterable)?据我回忆,在 Scala 中,人们通常会显式声明函数返回类型
  • @LukasEder 已经尝试过了,有许多不同的返回类型。他们都在某个时候推断出Iterable
  • 我已将您的代码复制到我的项目中,但没有看到所描述的行为,在我的情况下执行了 Select.union。您是否在运行时遇到问题或如何检测到它?您是否尝试执行代码并检查执行了哪个联合?
  • @MaximPopov 我的代码甚至无法编译,因为 Kotlin 将变形解析为 Kotlin 标准库。你是如何运行它的?
  • 我没有做任何特别的事情,只是复制了你的代码。请在此处查看我的测试示例 github.com/makcpop/sof-kotlin-jooq 。它可以在您的 PC 上运行吗?

标签: kotlin jooq


【解决方案1】:

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

方法buildSelect 应该返回Select,而不是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() 返回 SelectQuery 并且都是变量 queryunionQuery 具有相同的泛型类型并且正确确定了联合方法。

在第二个实现中,queryquery.union(...) 的参数具有 Select 类型。我猜编译器认为这两个变量具有不同的泛型(因为它没有确定并且可以不同)并且编译器不能使用 Select 中的 union,但是两个变量都实现了 Iterable 并且编译器选择了 Iterable.union 因为它适合。

【讨论】:

  • 优秀。与private fun buildSelect(selectCommonPart: Field&lt;*&gt;, whereCommonPart: Condition, date: LocalDate): SelectQuery&lt;Record&gt; { ... } 合作。谢谢!
【解决方案2】:

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

我认为buildSelect 可以更精确地输入为

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

然后由于两个调用具有相同的第一个参数,它应该可以解决。

编辑:我只是假设 selectCommonPart 的类型和返回的查询必须匹配,但 they don'tdsl.selectQuery() 返回 Select&lt;Record&gt;,因此 Maxim Popov 答案中的签名是正确的。

【讨论】:

  • 这里唯一的问题是我没有通过Field&lt;Record&gt; 这个特殊情况:我通过了Field&lt;BigDecimal!&gt;!,这让Kotlin 抱怨数据类型。对于我只是将Field&lt;Record&gt; 用于selectCommonPart 的情况,这个答案很棒!
猜你喜欢
  • 1970-01-01
  • 2017-12-10
  • 1970-01-01
  • 2019-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-15
  • 1970-01-01
相关资源
最近更新 更多