初探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的产生,从而加强大数据系统的稳定性。