searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

初探jsqlparser-sql解析器(二)

2023-07-27 06:55:30
49
0

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

0条评论
作者已关闭评论
冼****铭
4文章数
0粉丝数
冼****铭
4 文章 | 0 粉丝
冼****铭
4文章数
0粉丝数
冼****铭
4 文章 | 0 粉丝
原创

初探jsqlparser-sql解析器(二)

2023-07-27 06:55:30
49
0

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

文章来自个人专栏
大数据组件学习
4 文章 | 1 订阅
0条评论
作者已关闭评论
作者已关闭评论
0
0