jsqlparser:基于语法分析实现SQL中的CAST函数替换
创始人
2024-03-14 22:35:35
0

最近遇到一个问题,应用层提供的SQL语句中有CAST(local_time AS DATE)这样的语句,在MySQL中执行肯定是没问题的,但是后台数据库切换到了HBase,使用apache phoenix 提供的JDBC驱动访问时却报错了,按照phoenix官方的文档,CAST函数是支持,但现实就是报错过不了,应该是我使用的phoenix版本问题,应该是个BUG,暂时无法通过升级版本解决。

解决方案也不复杂就是用phoenix的Native函数TO_DATE,TO_CHAR函数来代替,将CAST(local_time AS DATE)替换为TO_DATE(TO_CHAR("local_time"), 'yyyy-MM-dd')

那么问题来了,如果让应用层来替换这事很方便,但是我们希望数据存储对应用层是透明的,应用层不需要知道存储是MySQL还是HBase,如果让应用层修改,那应用层就需要知道数据库的类型是MySQL还是HBase,根据不同的数据库使用不同的SQL语句,这太麻烦了----这是下下策。

有没有可能在服务端自动替换呢?有了jsqlparser这个神器,这个想法就可以实现。
jsqlparser是一个java的SQL语句解析器,在我之前的博客:《jsqlparser:基于抽象语法树(AST)遍历SQL语句的语法元素》以及《jsqlparser:实现基于SQL语法分析的SQL注入攻击检查》介绍了如何通过jsqlparser来遍历SQL语句中所有的语法单元实现自己的需求。
那么基于jsqlparser解析出的对象,修改部分语法也是可以实现的。所以一个基本的解决思路就有了:

遍历jsqlparser解析的语法树对象,找到所有CAST函数(Function对象),将其替换为需要的函数。

以下是实现代码:

import net.sf.jsqlparser.expression.CastExpression;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Function;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
import net.sf.jsqlparser.util.TablesNamesFinder;import java.util.function.Consumer;
/*** 基于SQL语法对象实现对SQL的修改
* 对PHOENIX支持有问题的CAST日期函数转换为使用PHOENIX的Native函数TO_DATE,TO_TIME,TO_TIMESTAMP* @author guyadong**/ public class PhoenixNormalizer extends TablesNamesFinder{public PhoenixNormalizer() {}/*** 根据输入参数创建TO_DATE,TO_TIME,TO_TIMESTAMP函数* @param castLeftExpression CAST的值参数* @param format 时间格式参数* @param targetFunctionName 要创建的Function对象的函数名*/private Expression castToFunction(Expression castLeftExpression,String format,String targetFunctionName){Function toChar = new Function().withName("TO_CHAR").withParameters(new ExpressionList().addExpressions(castLeftExpression));Function targetFunction = new Function().withName(targetFunctionName).withParameters(new ExpressionList().addExpressions(toChar,new StringValue(format)));return targetFunction;}/*** 从 Cast 函数中获取对应的参数,将其转为对应的TO_DATE,TO_TIME,TO_TIMESTAMP函数*/private void onCastExpression(CastExpression castExpression,Consumerconsumer){Expression newExp = null;switch (castExpression.getType().toString().toLowerCase()) {case "date":{newExp = castToFunction(castExpression.getLeftExpression(),"yyyy-MM-dd","TO_DATE");break;}case "time":{newExp = castToFunction(castExpression.getLeftExpression(),"yyyy-MM-dd HH:mm:ss","TO_TIME");break;}case "timestamp":{newExp = castToFunction(castExpression.getLeftExpression(),"yyyy-MM-dd'T'HH:mm:ss.SSSZ","TO_TIMESTAMP");break;}default:break;}if(null != newExp){consumer.accept(newExp);}}@Overridepublic void visit(SelectExpressionItem item) {/*** 不同于其他函数,jsqlparser对于CAST函数是单独处理的,定义了一个单独的类 CastExpression*/if(item.getExpression() instanceof CastExpression){onCastExpression((CastExpression)item.getExpression(),item::setExpression);}super.visit(item);} }

调用示例

Statement stmt;String sql = "SELECT count(1) AS count, CAST(\"local_time\" AS date) AS datastr, \"device_id\", \"media_id\" FROM \"dc_play_log_hbase\" WHERE \"local_time\" < TO_TIMESTAMP(\'2022-11-30 23:59:59\') AND \"local_time\" >= TO_TIMESTAMP(\'2022-11-22 00:00:00\') GROUP BY datastr,\"device_id\", \"media_id\""
stmt = CCJSqlParserUtil.parse(sql);
// PhoenixNormalizer遍历所有节点,执行替换
stmt.accept(new PhoenixNormalizer());
// 输出替换后的SQL
System.printf("sql = %s\n",stmt);

输出

sql = SELECT count(1) AS “count”, TO_DATE(TO_CHAR(“local_time”), ‘yyyy-MM-dd’) AS “datastr”, “device_id”, “media_id” FROM “dc_play_log_hbase” WHERE “local_time” < TO_TIMESTAMP(‘2022-11-30 23:59:59’) AND “local_time” >= TO_TIMESTAMP(‘2022-11-22 00:00:00’) GROUP BY “datastr”, “device_id”, “media_id”

完整的代码参见码云仓库:

https://gitee.com/l0km/sql2java/blob/master/sql2java-manager/src/main/java/gu/sql2java/phoenix/PhoenixNormalizer.java

单元测试:

https://gitee.com/l0km/sql2java/blob/master/sql2java-manager/src/test/java/gu/sql2java/pagehelper/parser/JsqlParserTest.java

参考资料:
《apache functions》

相关内容

热门资讯

文物界“出差天团”进京!猜猜这... 文物界“出差天团”进京!猜猜这些青铜界顶流会说啥?_北京时间现在上猫眼、美团、大众点评、微信,搜索“...
“十二星座”手动上新 全球首个... 转自:央视今天(14日)12时12分,我国在酒泉卫星发射中心使用长征二号丁运载火箭,成功将太空计算卫...
黑龙江开展全省跨境电商专题培训 转自:新华财经为提高黑龙江省商务主管部门跨境电商业务能力和跨境电商企业经营水平,推动传统外贸企业向跨...
西安鄠邑法院 | “最后一劝”... 保险公司:你在投保时就应当将被保险人之前出现过高热惊厥的病情如实告知给我们。岳鑫:我当时已经向保险业...
冀东水泥(000401.SZ)... 格隆汇5月14日丨冀东水泥(000401.SZ)公布2025年限制性股票激励计划(草案),本计划拟向...
网约车座椅靠背张贴“差评者得癌... 近日,有网友发帖称自己“五一”假期在杭州打网约车观看演唱会时,网约车前排座椅靠背张贴了一张“差评者得...
甘化科工:公司控股子公司沈阳含... 证券日报网讯甘化科工5月14日在互动平台回答投资者提问时表示,公司控股子公司沈阳含能生产的钨合金预制...
FOMC今年票委发话:4月低通... 芝加哥联储主席古尔斯比泼下冷水:最新CPI数据不一定反映出关税的真实影响,或许只是“尘埃飞扬”。芝加...
工行梧州藤县支行成功落地全辖首... 转自:新华财经近期,工行梧州藤县支行深入贯彻落实金融“五篇大文章”的工作部署,落实乡村振兴工作,将普...
爱迪特:公司保持开放态度,持续... 证券日报网讯爱迪特5月14日在互动平台回答投资者提问时表示,公司保持开放态度,持续关注口腔医疗及相关...
中航西飞:公司及子公司陕飞依托... 证券日报网讯中航西飞5月14日在互动平台回答投资者提问时表示,公司及子公司陕飞依托大中型飞机平台,持...
济宁市交通强国山东示范区建设推... 转自:济宁政务5月14日上午,济宁市交通强国山东示范区建设推进会议召开。济宁市委书记温金荣出席并讲话...
小马智行创始人自愿延长锁定期5... 5月14日,小马智行(NASDAQ:PONY)宣布,董事会主席、联合创始人、CEO彭军,以及联合创始...
北京博物馆头部达人联盟成立 5月14日,在“5·18国际博物馆日”中国主会场活动、北京博物馆季及“看·见殷商”展即将举行的重要时...
赵乐际分别会见拉丁美洲议会议长... 转自:北京日报客户端全国人大常委会委员长赵乐际14日在北京分别会见拉丁美洲议会议长冈萨雷斯、中美洲议...
在古老的意大利科莫歌剧院,廖昌... 转自:上观新闻意大利当地时间5月12日晚,拥有212年历史的意大利科莫歌剧院迎来2024-2025年...
2025 新能源五大标杆车型解... 转自:衡水日报在 2025 年新能源汽车技术呈 "井喷式" 发展的大背景下,消费者对车辆的核心诉求已...
紫光原董事长赵伟国被判死缓,紫... 【#紫光原董事长赵伟国被判死缓#,#紫光原董事长曾被称为并购狂人#,紫光集团破产重组后如何“求生”?...
为什么南方人家里都有这种碗? 作者:敏敏 徐可心编辑:陈燕妮如果你来到南方的街头吃饭,很可能会看到一种印着大公鸡的碗。小红书上就有...