H2兼容DB2 STRIP函数UDF时参数B被识别为列的报错问题
H2适配DB2 STRIP函数问题解决方案
环境信息
- Java 8 JDK 321 64位
- 生产数据库:DB2 12,DSN 12015
- 单元测试数据库:H2 2.1.212,连接配置参数为
MODE=DB2;DEFAULT_NULL_ORDERING=HIGH;
问题现象
在单元测试环境通过自定义UDF做DB2到H2的函数适配时,需要兼容DB2的STRIP函数,业务SQL中固定调用写法为STRIP(T2.ITEM_TYPE_NAME_GER, B, ' ')。已实现可接收第一、第三个入参的Java映射方法,可配置为H2 ALIAS调用,但第二个入参是不带引号的B/L/T等关键字,始终被H2引擎识别为表列,抛出列不存在的异常:
org.h2.jdbc.JdbcSQLSyntaxErrorException: Feld "B" nicht gefunden Column "B" not found; SQL statement: SELECT T1.ITEM_TYPE_KEY,T1.SUPER_ITM_TYPE_KEY,T2.ITM_TYPE_KEY_TRANS,T2.ITEM_TYPE_NAME,T2.COMPLEX_FLAG,T2.ITEM_CATEGORY,T2.HEADER_FLAG,T2.HEADER_NO ,T2.LEVEL_NO,strip(T2.ITEM_TYPE_NAME_GER, B, ' ') ,T2.LEVEL1_DISPLAY FROM public.AA752T T1, public.AA743T T2 WHERE T1.ITEM_TYPE_KEY NOT IN ('F4CO', 'F4CB', 'F4RB', 'F4SO', 'F4SB', 'F4RO') AND T1.ITEM_TYPE_KEY = T2.ITEM_TYPE_KEY ORDER BY T2.HEADER_NO,T2.HEADER_FLAG DESC,T2.LEVEL_NO,T1.SUPER_ITM_TYPE_KEY,T2.LEVEL_PRIORITY [42122-212] at org.h2.message.DbException.getJdbcSQLException(DbException.java:502) at org.h2.message.DbException.getJdbcSQLException(DbException.java:477) at org.h2.message.DbException.get(DbException.java:223) at org.h2.message.DbException.get(DbException.java:199) at org.h2.expression.ExpressionColumn.getColumnException(ExpressionColumn.java:244) at org.h2.expression.ExpressionColumn.optimizeOther(ExpressionColumn.java:226) at org.h2.expression.ExpressionColumn.optimize(ExpressionColumn.java:213) at org.h2.expression.function.JavaFunction.optimize(JavaFunction.java:59) at org.h2.command.query.Select.prepareExpressions(Select.java:1170) at org.h2.command.query.Query.prepare(Query.java:218) at org.h2.command.Parser.prepareCommand(Parser.java:574) at org.h2.engine.SessionLocal.prepareLocal(SessionLocal.java:631) at org.h2.engine.SessionLocal.prepareCommand(SessionLocal.java:554) at org.h2.jdbc.JdbcConnection.prepareCommand(JdbcConnection.java:1116) at org.h2.jdbc.JdbcPreparedStatement.(JdbcPreparedStatement.java:92) at org.h2.jdbc.JdbcConnection.prepareStatement(JdbcConnection.java:288) at com.db.cib.gbd.gps.pbs.pricing.StaticItemDetails.retriveItemDisplayDetails(StaticItemDetails.java:920)
原有UDF实现存在三个明显问题:
- 第二个参数使用H2内部类
Expression作为类型,UDF应使用标准Java类型做参数,避免内部API版本兼容性问题 - Java字符串比较使用
==而非equals,会出现逻辑判断失效 - BOTH分支的尾部裁剪规则写死匹配空格制表符,未使用传入的裁剪字符参数,不符合DB2原生行为
原有错误代码如下:
public static String strip(String s, Expression loc, String trimConstant) { if (loc.toLowerCase() == "b" || loc.toLowerCase() == "both") { s = s.replaceAll("^[" + trimConstant + "]+|[ \t]+$", ""); } else if (loc.toLowerCase() == "l" || loc.toLowerCase() == "leading") { s = s.replaceAll("^[" + trimConstant + "]+", ""); } else if (loc.toLowerCase() == "t" || loc.toLowerCase() == "trailing") { s = s.replaceAll("[" + trimConstant + "]+$", ""); } return s; }
约束条件:不能修改任何业务SQL,必须通过函数别名配置的方式实现兼容。
可行解决方案
方案1:为位置关键字定义无参常量别名(实现成本最低)
H2支持无参ALIAS直接以函数名调用、不需要加括号,利用这个特性,在单元测试数据源初始化阶段(建表之后、执行业务SQL之前),先执行以下SQL把裸写的B/L/T/BOTH/LEADING/TRAILING关键字定义为返回对应字符串的无参函数,H2解析到这些标识符时会优先匹配别名,不会再去查找表列:
-- 定义位置关键字常量别名 CREATE ALIAS IF NOT EXISTS B AS $$ String b() { return "B"; } $$; CREATE ALIAS IF NOT EXISTS L AS $$ String l() { return "L"; } $$; CREATE ALIAS IF NOT EXISTS T AS $$ String t() { return "T"; } $$; CREATE ALIAS IF NOT EXISTS BOTH AS $$ String both() { return "BOTH"; } $$; CREATE ALIAS IF NOT EXISTS LEADING AS $$ String leading() { return "LEADING"; } $$; CREATE ALIAS IF NOT EXISTS TRAILING AS $$ String trailing() { return "TRAILING"; } $$; -- 注册修正后的STRIP函数 DROP ALIAS IF EXISTS STRIP; CREATE ALIAS STRIP FOR "你自己的类全限定名.strip";
同时修正UDF的Java实现,全部使用标准Java类型,修复原有逻辑bug:
import java.util.regex.Pattern; public class H2Udf { public static String strip(String s, String loc, String trimConstant) { // 空值快速返回,和DB2行为对齐 if (s == null || loc == null || trimConstant == null) { return s; } String locFlag = loc.toLowerCase(); // 转义裁剪字符,避免正则特殊字符导致匹配异常 String trimPattern = Pattern.quote(trimConstant); if ("b".equals(locFlag) || "both".equals(locFlag)) { return s.replaceAll("^" + trimPattern + "+|" + trimPattern + "+$", ""); } else if ("l".equals(locFlag) || "leading".equals(locFlag)) { return s.replaceAll("^" + trimPattern + "+", ""); } else if ("t".equals(locFlag) || "trailing".equals(locFlag)) { return s.replaceAll(trimPattern + "+$", ""); } // 非法位置参数直接返回原字符串 return s; } }
这个方案不需要修改H2内核,所有逻辑都通过标准ALIAS配置实现,适配工作量最小,适合单元测试场景使用。
方案2:自定义H2解析器规则(稳定性更高)
如果方案1的别名和业务表字段存在命名冲突,可以在H2初始化时通过自定义Parser实现,在词法解析阶段把裸的B/L/T/BOTH/LEADING/TRAILING直接转为字符串字面量,不需要单独定义函数别名。这个方案需要依赖H2的内部Parser API,适合对H2适配层有统一封装的场景,不会存在别名冲突问题。
内容的提问来源于stack exchange,提问作者christian s
相关产品推荐
相关产品推荐

