基于jOOQ+DuckDB的Parquet列动态聚合类型适配问题
解决jOOQ操作DuckDB时SUM聚合的类型映射问题
问题核心
jOOQ默认会将数值类型的SUM结果统一映射为BigDecimal,但DuckDB对整数列SUM返回HUGEINT(对应Java的BigInteger)、浮点列SUM返回Double——直接用JDBC查询能正确获取对应类型,但jOOQ的默认映射覆盖了这一行为,且直接强制转换Number会因方言未指定报错。
解决方案
1. 自定义数据类型Binding
针对SUM结果的动态类型需求,自定义Binding来适配DuckDB的原生返回类型:
import org.jooq.*; import org.jooq.impl.DSL; import java.sql.*; import java.math.BigInteger; import java.math.BigDecimal; public class DuckDbSumBinding implements Binding<Object, Number> { @Override public Converter<Object, Number> converter() { return new Converter<>() { @Override public Number from(Object databaseObject) { if (databaseObject instanceof BigInteger) { return (BigInteger) databaseObject; } else if (databaseObject instanceof Double) { return (Double) databaseObject; } else if (databaseObject instanceof BigDecimal) { BigDecimal bd = (BigDecimal) databaseObject; return bd.scale() == 0 ? bd.toBigInteger() : bd.doubleValue(); } return null; } @Override public Object to(Number userObject) { return userObject; } @Override public Class<Object> fromType() { return Object.class; } @Override public Class<Number> toType() { return Number.class; } }; } @Override public void sql(BindingSQLContext<Number> ctx) throws SQLException { ctx.render().visit(DSL.val(ctx.convert(converter()).value())); } @Override public void register(BindingRegisterContext<Number> ctx) throws SQLException { ctx.statement().registerOutParameter(ctx.index(), Types.OTHER); } @Override public void set(BindingSetStatementContext<Number> ctx) throws SQLException { ctx.statement().setObject(ctx.index(), ctx.convert(converter()).value()); } @Override public void set(BindingSetSQLOutputContext<Number> ctx) throws SQLException { throw new SQLFeatureNotSupportedException(); } @Override public void get(BindingGetResultSetContext<Number> ctx) throws SQLException { ctx.convert(converter()).value(ctx.resultSet().getObject(ctx.index())); } @Override public void get(BindingGetStatementContext<Number> ctx) throws SQLException { ctx.convert(converter()).value(ctx.statement().getObject(ctx.index())); } @Override public void get(BindingGetSQLInputContext<Number> ctx) throws SQLException { throw new SQLFeatureNotSupportedException(); } }
2. 绑定自定义Binding执行查询
在动态表场景下,通过coerce+bind方法指定自定义Binding,让jOOQ按DuckDB原生类型处理结果:
// 加载Parquet表 Table<?> parquetTable = DSL.table("parquet_scan('path/to/your/file.parquet')"); // 为SUM聚合绑定自定义Binding Field<Number> sumKI = DSL.sum(parquetTable.field("kI")) .coerce(Number.class) .bind(new DuckDbSumBinding()); Field<Number> sumKD = DSL.sum(parquetTable.field("kD")) .coerce(Number.class) .bind(new DuckDbSumBinding()); // 执行查询(必须指定DUCKDB方言) Result<?> result = DSL.using(connection, SQLDialect.DUCKDB) .select(sumKI, sumKD) .from(parquetTable) .fetch(); // 处理结果 Number kiSum = result.getValue(sumKI); Number kdSum = result.getValue(sumKD); if (kiSum instanceof BigInteger) { // 整数列SUM逻辑 } else if (kdSum instanceof Double) { // 浮点列SUM逻辑 }
3. 全局类型映射配置(可选)
若需全局生效,可在jOOQ配置中为DuckDB的HUGEINT类型指定映射:
Configuration configuration = new DefaultConfiguration() .set(connection) .set(SQLDialect.DUCKDB) .set(new DefaultDataTypeProvider() { @Override public DataType<?> defaultDataType(int sqlType) { if (sqlType == Types.OTHER && "HUGEINT".equals(getTypeName(sqlType))) { return SQLDataType.BIGINT.asConvertedDataType(new Converter<>() { @Override public BigInteger from(Object o) { return (BigInteger) o; } @Override public Object to(BigInteger bigInteger) { return bigInteger; } @Override public Class<Object> fromType() { return Object.class; } @Override public Class<BigInteger> toType() { return BigInteger.class; } }); } return super.defaultDataType(sqlType); } });
关键注意点
- 必须显式指定
SQLDialect.DUCKDB,否则会触发“Type class java.lang.Number is not supported in dialect null”报错。 - 自定义Binding的核心是在
converter中根据DuckDB返回的实际类型动态转换,避免jOOQ统一转为BigDecimal。
内容的提问来源于stack exchange,提问作者blacelle
相关产品推荐
相关产品推荐

