繁体   English   中英

使用 jOOQ 创建自定义聚合函数

[英]Create a custom Aggregate Function with jOOQ

语境

我正在针对PostgreSQL数据库使用jOOQ
我想将jsonb_object_agg(name, value)用于LEFT OUTER JOIN的结果集。

问题

在加入作为一个OUTER一个,有时name聚集功能的部分仅仅是null :那是行不通的。 然后我会去:

COALESCE(
    json_object_agg(table.name, table.value) FILTER (WHERE table.name IS NOT NULL),
    '{}'
)::json

到目前为止,我用来调用jsonb_object_agg的代码(不完全是,但归结为)如下:

public static Field<?> jsonbObjectAgg(final Field<?> key, final Select<?> select) {
    return DSL.field("jsonb_object_agg({0}, ({1}))::jsonb", JSON_TYPE, key, select);
}

...其中JSON_TYPE是:

private static final DataType<JsonNode> JSON_TYPE = SQLDataType.VARCHAR.asConvertedDataType(/* a custom Converter */);

不完整的解决方案

我很想利用jOOQAggregateFilterStep接口,特别是能够使用它的AggregateFilterStep#filterWhere(Condition... conditions)

但是, implements AggregateFilterSteporg.jooq.impl.Function类(间接通过AgregateFunctionArrayAggOrderByStep )在其package可见性方面受到限制,因此我不能盲目地回收DSL#ArrayAggOrderByStep的实现:

public static <T> ArrayAggOrderByStep<T[]> arrayAgg(Field<T> field) {
    return new org.jooq.impl.Function<T[]>(Term.ARRAY_AGG, field.getDataType().getArrayDataType(), nullSafe(field));
}

尝试

我最接近合理的是......构建我自己的coalesceAggregation函数,专门合并聚合字段:

//                                  Can't quite use AggregateFunction there
//                                                   v   v
public static <T> Field<T> coalesceAggregation(final Field<T> agg, final Condition coalesceWhen, @NonNull final T coalesceTo) {
    return DSL.coalesce(DSL.field("{0} FILTER (WHERE {1})", agg.getType(), agg, coalesceWhen), coalesceTo);
}

public static <T> Field<T> coalesceAggregation(final Field<T> agg, @NonNull final T coalesceTo) {
    return coalesceAggregation(agg, agg.isNotNull(), coalesceTo);
}

...但我然后遇到了问题,我T类型为JsonNode ,其中DSL#coalesce似乎CASTcoalesceTovarchar

或者,你知道:

DSL.field("COALESCE(jsonb_object_agg({0}, ({1})) FILTER (WHERE {0} IS NOT NULL), '{}')::jsonb", JSON_TYPE, key, select)

但这将是最后的手段:感觉就像我离让用户将他们想要的任何 SQL 注入我的数据库只差一步了🙄

简而言之

jOOQ有没有办法“正确”实现自己的聚合函数,作为实际的org.jooq.AgregateFunction
我想尽可能避免让jooq-codegen生成它(不是我不喜欢它——这只是我们的管道太可怕了)。

从 jOOQ 3.14.0 开始

现在JSON_OBJECTAGG原生支持JSON_OBJECTAGG聚合函数

DSL.jsonObjectAgg(TABLE.NAME, TABLE.VALUE).filterWhere(TABLE.NAME.isNotNull());

在 jOOQ 3.14.8 中添加了对FILTER子句的支持

从 jOOQ 3.14.8 和 3.15.0 开始

如果 jOOQ 没有实现特定的聚合函数,您现在可以指定DSL.aggregate()来使用自定义聚合函数。

DSL.aggregate("json_object_agg", SQLDataType.JSON, TABLE.NAME, TABLE.VALUE)
   .filterWhere(TABLE.NAME.isNotNull());

这是通过https://github.com/jOOQ/jOOQ/issues/1729实现的

预 jOOQ 3.14.0

jOOQ DSL API 中缺少一个功能,即创建纯 SQL 聚合函数 这还不可用的原因(从 jOOQ 3.11 开始)是因为有很多微妙的内部结构来指定支持所有供应商特定选项的供应商不可知聚合函数,包括:

  • FILTER (WHERE ...)子句(如您在问题中提到的),必须使用CASE进行模拟
  • OVER (...)子句将聚合函数转换为窗口函数
  • WITHIN GROUP (ORDER BY ...)子句支持有序集合聚合函数
  • DISTINCT子句,如果支持
  • 其他特定于供应商的聚合函数扩展

在您的特定情况下,简单的解决方法是一直使用纯 SQL 模板,正如您在问题中提到的:

DSL.field("COALESCE(jsonb_object_agg({0}, ({1})) FILTER (WHERE {0} IS NOT NULL), '{}')::jsonb", JSON_TYPE, key, select)

或者你做你之前提到的事情。 关于这种担忧:

...但随后我遇到了 T 类型为 JsonNode 的问题,其中 DSL#coalesce 似乎将我的 coalesceTo 转换为 varchar。

这可能是因为你使用agg.getType()返回Class<?>而不是agg.getDataType()返回DataType<?>

但这将是最后的手段:感觉就像我离让用户将他们想要的任何 SQL 注入我的数据库仅一步之遥

我不确定为什么这是一个问题。 您仍然可以自己控制普通 SQL API 的使用,并且用户将无法将任意内容注入keyselect因为您也控制这些元素。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM