使用正则表达式解析sql语句

1、前言

使用代码解析sql语法是在语法检查、语言格式化、语法高亮等功能场景中应用广泛,自己也自学了一段时间的sql的语法树的概念,并且使用druid的语法解析也在生产项目上做了实践,于是自己写了一个使用正则解析sql语法的小工具,作为备忘,有兴趣的同学可以一起探讨其他实现方式。

2、主要代码

package com.cloud.server.sql;

import com.alibaba.fastjson.JSON;
import com.cloud.server.sql.bean.ColumnsInfo;
import com.cloud.server.sql.bean.TableInfo;
import com.cloud.server.sql.bean.WhereColumnsInfo;
import com.cloud.server.sql.bean.WhereInfo;
import com.cloud.server.sql.enums.ExpSqlRuleEnum;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Author: chenyaohua
 * @Date: 2021/12/17
 * @Description: sql解析工具
 */
public class SqlAstUtil {

    /**
     * @Author: chenyaohua
     * @Date: 2021/12/17
     * @Description: 取sql参数表达式
     */
    private static final String REG_SQL_VAL_A = "(?<=\\s(%s\\s(>|<|<>|=|!=|like|in|not in|not like)\\s'))[\\s\\S]*?(?=('|$))";
    private static final String REG_SQL_VAL_B = "(?<=(%s\\s(>|<|<>|=|!=|like|in|not in|not like)\\s'))[\\s\\S]*?(?=('|$))";
    private static final String REG_SQL_VAL_C = "(?<=\\s(%s\\s(>|<|<>|=|!=|like|in|not in|not like)'))[\\s\\S]*?(?=('|$))";
    private static final String REG_SQL_VAL_D = "(?<=(%s\\s(>|<|<>|=|!=|like|in|not in|not like)'))[\\s\\S]*?(?=('|$))";

    /**
     * @Author: chenyaohua
     * @Date: 2021/12/17
     * @Description: 获取表名
     */
    public static List<TableInfo> getTableName(String sql){
        String regexA = ExpSqlRuleEnum.TABLE_NAME_A.getDesc();
        List<TableInfo> resA = getTableNameCore(regexA,sql);
        String regexB = ExpSqlRuleEnum.TABLE_NAME_B.getDesc();
        List<TableInfo> resB = getTableNameCore(regexB,sql);
        resA.addAll(resB);
        return resA;
    }

    /**
     * @Author: chenyaohua
     * @Date: 2021/12/17
     * @Description: 获取字段名
     */
    public static List<ColumnsInfo> getColumn(String sql){
        String regex = ExpSqlRuleEnum.COLUMNS.getDesc();
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(sql);

        List<ColumnsInfo> res = new ArrayList<>(16);
        while (matcher.find()) {
            String[] columns = matcher.group().trim().split(",");

            // 获取表名+字段
            for (String column : columns) {
                String name = column.trim();
                String[] ci = name.split("[.]");
                if (ci.length > 1) {
                    List<String> cloumns = new ArrayList<>(16);
                    ColumnsInfo columnsInfo = new ColumnsInfo();
                    columnsInfo.setColumns(cloumns);
                    columnsInfo.setTableName(ci[0]);
                    if (res.stream().noneMatch(item -> item.getTableName().equals(ci[0]))) {
                        res.add(columnsInfo);
                    }
                }
            }

            // 去重
            Set<String> cloumns = new HashSet<String>(16);
            res.forEach(item -> {
                for (String column : columns) {
                    String name = column.trim();
                    String[] ci = name.split("[.]");

                    if (ci.length > 1) {
                        if (ci[0].equals(item.getTableName())) {
                            item.getColumns().add(ci[1]);
                        }
                    } else {
                        cloumns.add(ci[0]);
                    }
                }
            });
            if(!CollectionUtils.isEmpty(cloumns)){
                ColumnsInfo columnsInfo = new ColumnsInfo();
                List<String> finalList = new ArrayList<>(cloumns);
                columnsInfo.setColumns(finalList);
                res.add(columnsInfo);
            }

        }
        return res;
    };

    /**
     * @Author: chenyaohua
     * @Date: 2021/12/17
     * @Description: 获取where条件字段
     */
    public static List<WhereInfo> getWhereColumns(String sql){
        List<WhereInfo> res = new ArrayList<>(16);
        String[] regex = new String[]{ExpSqlRuleEnum.WHERE_COLUMNS_BEFORE.getDesc(),ExpSqlRuleEnum.WHERE_COLUMNS_AFTER.getDesc()};

        String regexOperator = ExpSqlRuleEnum.WHERE_COLUMNS_OPERATOR.getDesc();

        Pattern patternOperator = Pattern.compile(regexOperator);
        Matcher matcherOperator = patternOperator.matcher(sql);
        List<String> opList = new ArrayList<>(16);
        while (matcherOperator.find()) {
            String operator = matcherOperator.group().trim();
            opList.add(operator);
        }

        // 取表达式前后表别名+字段名
        List<WhereColumnsInfo> beforeList = new ArrayList<>(16);
        List<WhereColumnsInfo> afterList = new ArrayList<>(16);
        for (int i = 0; i < regex.length; i++) {
            Pattern pattern = Pattern.compile(regex[i]);
            Matcher matcher = pattern.matcher(sql);
            int idx = 0;
            while (matcher.find()) {
                String str = matcher.group().trim();
                String[] ti = str.split("[.]");
                if (ti.length > 1) {
                    WhereColumnsInfo whereColumnsInfo = new WhereColumnsInfo();
                    whereColumnsInfo.setTableName(ti[0]);
                    whereColumnsInfo.setColumns(ti[1]);
                    if(regex[i].equals(ExpSqlRuleEnum.WHERE_COLUMNS_BEFORE.getDesc())){
                        beforeList.add(idx,whereColumnsInfo);
                        afterList.add(idx,new WhereColumnsInfo());
                    } else {
                        afterList.add(idx,whereColumnsInfo);
                    }
                    idx++;
                }
            }
        }
        for (int i = 0; i < beforeList.size(); i++) {
            WhereInfo whereInfo = new WhereInfo();
            whereInfo.setColumnBefore(beforeList.get(i));
            whereInfo.setColumnAfter(afterList.get(i));
            whereInfo.setOperator(opList.get(i));
            res.add(whereInfo);
        }

        // 取sql 字段名+对应参数值
        String[] regSqlVal = new String[]{REG_SQL_VAL_A,REG_SQL_VAL_B,REG_SQL_VAL_C,REG_SQL_VAL_D};
        res.forEach(item -> {
            if(!StringUtils.isEmpty(item.getColumnBefore().getTableName()) &&
                    StringUtils.isEmpty(item.getColumnAfter().getTableName())){
                String suffix = item.getColumnBefore().getTableName()+"."+item.getColumnBefore().getColumns();
                for (String s : regSqlVal) {
                    String expReg = String.format(s, suffix);
                    Pattern sqlParamOperator = Pattern.compile(expReg);
                    Matcher qlParamOperator = sqlParamOperator.matcher(sql);
                    while (qlParamOperator.find()) {
                        String sqlParam = qlParamOperator.group();
                        item.setParamValue(sqlParam);
                    }
                }


            }
        });
        return res;
    }

    /**
     * @Author: chenyaohua
     * @Date: 2021/12/17
     * @Description: 获取表名
     */
    private static List<TableInfo> getTableNameCore(String regex,String sql){
        List<TableInfo> res = new ArrayList<>(16);
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(sql);
        while (matcher.find()) {
            String[] tableNames = matcher.group().split(",");
            for (String tableName : tableNames) {
                TableInfo tableInfo = new TableInfo();
                String name = tableName.trim();
                String[] ti = name.split(" ");
                if (ti.length > 1) {
                    tableInfo.setName(ti[0]);
                    tableInfo.setAlias(ti[1]);
                } else {
                    tableInfo.setName(ti[0]);
                }
                res.add(tableInfo);
            }
        }
        return res;
    }

    public static void main(String[] args) {
        String sql = "select t1.name,t1.code,t2.age,t2.address,school " +
                "from person t1 left join person_info t2 on t1.id = t2.id and t1.code > t2.age and t1.name = '12321'";
        List<TableInfo> tableNameList = SqlAstUtil.getTableName(sql);
        System.out.println(JSON.toJSON(tableNameList));

        List<ColumnsInfo> columnsInfos = SqlAstUtil.getColumn(sql);
        System.out.println(JSON.toJSON(columnsInfos));

        List<WhereInfo> whereInfo = SqlAstUtil.getWhereColumns(sql);
        System.out.println(JSON.toJSON(whereInfo));


    }
}


3、正则表达式枚举类

package com.cloud.server.sql.enums;

import lombok.Getter;
import lombok.Setter;

public enum ExpSqlRuleEnum {

    TABLE_NAME_A("TABLE_NAME_A",
            "(?<=\\bfrom\\b)[\\s\\S]*?(?=\\b(where|left|join|inner)\\b)"),
    TABLE_NAME_B("TABLE_NAME_B",
            "(?<=\\bjoin\\b)[\\s\\S]*?(?=\\bon\\b)"),
    COLUMNS("COLUMNS",
            "(?<=\\bselect\\b)[\\s\\S]*?(?=\\bfrom\\b)"),
    WHERE_COLUMNS_BEFORE("WHERE_COLUMNS_BEFORE",
            "(?<=\\b(where|and|or|on)\\b)[\\s\\S]*?(?=\\s(>|<|<>|=|!=|like|in|not in|not like)\\s)"),
    WHERE_COLUMNS_OPERATOR("WHERE_COLUMNS_OPERATOR",
            "(\\s(>|<|<>|=|!=|like|in|not in|not like)\\s)"),
    WHERE_COLUMNS_AFTER("WHERE_COLUMNS_AFTER",
            "(?<=\\s(>|<|<>|=|!=|like|in|not in|not like)\\s)[\\s\\S]*?(?=(and|or|;|$))");

    @Getter
    @Setter
    private String code;

    @Getter
    @Setter
    private String desc;

    ExpSqlRuleEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }
}

4、运行结果

[{"name":"person","alias":"t1"},{"name":"person_info","alias":"t2"}]
[{"columns":["name","code"],"tableName":"t1"},{"columns":["age","address"],"tableName":"t2"},{"columns":["school"]}]
[{"columnAfter":{"columns":"id","tableName":"t2"},"columnBefore":{"columns":"id","tableName":"t1"},"operator":"="},{"columnAfter":{"columns":"age","tableName":"t2"},"columnBefore":{"columns":"code","tableName":"t1"},"operator":">"},{"columnAfter":{},"columnBefore":{"columns":"name","tableName":"t1"},"operator":"=","paramValue":"12321"}]

5、代码地址

https://github.com/chenyaohua007/SqlParser

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容