初探jsqlparser-sql解析器(二)
在大数据系统中,用户错误的sql输入,往往会给后端系统带来压力,更有甚者,会拖垮系统,为防止错误输入,又不想让用户进行过多操作,则需要包容用户的错误,对其输入进行适当的改写。
在《初探jsqlparser》一文中,我们通过使用jsqlparser提取出了sql的信息,那能否在此基础上对sql语句进行改写呢?答案是肯定的。
在把sqlL语句解析成ast(抽象语法树)后,我们通过替换其中的节点元素,便可以实现改写sql语句。
jsqlparser代码结构
jsqlparser源码结构如下:
想要改写select语句,则重点关注schema包下的类,以及statement.select包下的类(其它语句对应具体的包)
-schema包主要包括了SQL语句中的零部件,如库、表、列、参数等元素
-statement.select包则包括了SQL语句中的表达,如order By表达,group By表达等
示例1:替换表名
SQL语句中的表名主要在SelectBody的FromItem表达中(查看源码可知,join表达中也包含FromItem表达),替换表名只需把新的表名set到FromItem即可,如下:
CCJSqlParserManager pm = new CCJSqlParserManager();
Statement statement = pm.parse(new StringReader(sql));
if(statement instanceof Select) {
Select select = (Select) statement;
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
plainSelect.setFromItem(new Table("sc_example","replace_table1").withAlias(new Alias("t1")));
}
System.out.println(statement.toString());
示例2:替换查询列
查询列为SelectBody的SelectItem,通过替换List<SelectItem>,可达到替换查询列的效果,如下,把select * 替换成具体列名:
List<String> fields = new ArrayList<>(); //具体列名
fields.add("a");
fields.add("b");
fields.add("c");
CCJSqlParserManager pm = new CCJSqlParserManager();
Statement statement = pm.parse(new StringReader(sql));
if(statement instanceof Select) {
Select select = (Select) statement;
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
List<SelectItem> selectItems = plainSelect.getSelectItems();
List<SelectItem> newSelectItems = selectItems.stream()
.flatMap(t -> {
if(t instanceof AllColumns) { //*转成具体fields
return fields.stream().map(field ->
new SelectExpressionItem()
.withExpression(new Column(field)));
}else if(t instanceof AllTableColumns){ //table.*转成具体fields
return fields.stream().map(field ->
new SelectExpressionItem()
.withExpression(new Column(((AllTableColumns) t).getTable(), field)));
}else{
return Stream.of(t);
}
})
.collect(Collectors.toList());
plainSelect.setSelectItems(newSelectItems);
}
System.out.println(statement.toString());
示例3:增加where条件
where条件被解析为SelectBody中的Where表达。
若需替换的where条件过长,一一组装where条件较为麻烦,可利用CCJSqlParserUtil.parseExpression(String expression)方法,直接把String转为Expression,如下:
String str = "last_time >= '2023-07-25 00:00:00' and last_time < '2023-07-27 00:00:00'"
+ " AND " +
"first_time <= '2023-07-25 00:00:00' and first_time < '2023-07-27 00:00:00'";
Expression addExp = CCJSqlParserUtil.parseExpression(str);
当语句中已有where条件,不希望替换原有条件,而是想添加条件,则需要用到AndExpression或者OrExpression拼装,注:为防止拼上去的条件影响到源条件,需用()把加上去的条件和原条件分别包起来,此时需用到Parenthesis,如下:
plainSelect.setWhere(new OrExpression(new Parenthesis(addExp), new Parenthesis(where))); //or
plainSelect.setWhere(new AndExpression(new Parenthesis(addExp), new Parenthesis(where))); //and
整体代码如下:
CCJSqlParserManager pm = new CCJSqlParserManager();
Statement statement = pm.parse(new StringReader(sql));
if(statement instanceof Select) {
Select select = (Select) statement;
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
String str = "last_time >= '2023-07-25 00:00:00' and last_time < '2023-07-27 00:00:00'"
+ " AND " +
"first_time <= '2023-07-25 00:00:00' and first_time < '2023-07-27 00:00:00'";
Expression addExp = CCJSqlParserUtil.parseExpression(str);
Expression where = plainSelect.getWhere();
if(null != where){
// plainSelect.setWhere(new OrExpression(new Parenthesis(addExp), new Parenthesis(where))); //or
plainSelect.setWhere(new AndExpression(new Parenthesis(addExp), new Parenthesis(where))); //and
}else{
plainSelect.setWhere(addExp);
}
}
System.out.println(statement.toString());
示例4:增加排序语句
同样,通过修改SelectBody中的orderByElement,可以为SQL语句增加order表达,如下
CCJSqlParserManager pm = new CCJSqlParserManager();
Statement statement = pm.parse(new StringReader(sql));
if(statement instanceof Select) {
Select select = (Select) statement;
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
plainSelect.addOrderByElements(new OrderByElement()
.withExpression(new Column("last_time"))
.withAsc(false));
}
System.out.println(statement.toString());
总结
理解好AST中的各种组成,利用好各种Expression,可以把用户输入的SQL改写成有查询边界,能利用索引的SQL,防止慢SQL的产生,从而加强大数据系统的稳定性。