从sql中提取表名称,删除sql中的某个查询条件

本文更详细的介绍可移步:从sql中提取表名称,删除sql中的某个查询条件_苏尔伯特的博客-CSDN博客

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

/**
 * @Author 苏尔伯特
 * @Date 2021/11/20 13:09
 */
public class SqlParser {

    /**
     * 关键词列表,可以根据自己的业务场景添加所需判断的关键词
     * 为了减少不必要的操作,关键词应当越少越好,但也不能过少从而影响判断
     */
    private final List keywordList = new ArrayList<>();

    {
        keywordList.add("select");
        keywordList.add("case");
        keywordList.add("when");
        keywordList.add("then");
        keywordList.add("else");
        keywordList.add("end");
        keywordList.add("join");
        keywordList.add("on");
        keywordList.add("where");
    }

    /*-------------------------------解析出sql中的表名--------------------------------------------*/

    private List getSqlLines(String originSql) {
        String[] lines = originSql.split("\\v");
        List newLines = new ArrayList<>();
        for (String line : lines) {
            String trimLine = line.trim();
            if ("".equals(trimLine) || trimLine.startsWith("--") || trimLine.startsWith("#")) {
                continue;
            }
            newLines.add(trimLine);
        }
        return newLines;
    }

    private String getInlineSql(String originSql) {
        List newLines = getSqlLines(originSql);
        StringBuilder sql = new StringBuilder();
        newLines.forEach(str -> sql.append(str).append(" "));
        return sql.toString();
    }

    public Set getTablesFromSql(String originSql) {
        Set tableSet = new TreeSet<>();
        String sql = getInlineSql(originSql);
        // 首先,表名肯定是跟在这些关键词后面的
        String[] sqlFrag = sql.split("from|join|FROM|JOIN");
        for (int i = 1; i < sqlFrag.length; i++) {
            String trimFrag = sqlFrag[i].trim();
            // 如果有子查询的话,那么from后面就是跟着 '(' 但这不是我们想要的,因此pass
            if (!trimFrag.startsWith("(")) {
                String tableName = trimFrag.split(" ")[0];
                if (tableName.contains("`")) {
                    tableName = tableName.replaceAll("`", "");
                }
                // 这里是将表名前面的限定名去掉了,可以根据自己的需求来
                if (tableName.contains(".")) {
                    tableName = tableName.substring(tableName.indexOf('.') + 1);
                }
                tableSet.add(tableName);
            }
        }
        return tableSet;
    }

    /*-------------------------------删除指定查询条件--------------------------------------------*/

    /**
     * 主逻辑:
     * 1. 匹配关键字
     * 2. 找到了关键字 是->3, 否->6
     * 3. 判断是否在查询条件语句中: 是->4, 否->5
     * 4. 删除该条件,返回删除条件后的sql, 然后继续匹配下一个可能出现的地方 -> 1
     * 5. 从当前关键字之后继续查询下一次出现的位置 -> 1
     * 6. 返回当前sql
     *
     * @param sql           当前sql
     * @param customKeyword 自关键词
     * @param offSet        索引偏移量
     * @return 删除了存在关键字之后的sql
     */
    public String removeCondition(String sql, String customKeyword, int offSet) {
        Matcher matcher = Pattern.compile(customKeyword).matcher(sql);
        if (offSet == 0 ? matcher.find() : matcher.find(offSet)) {
            int customKeywordStartIndex = matcher.start();
            Map prevKeywordMap = inCondition(sql, customKeywordStartIndex).get(true);
            if (prevKeywordMap != null && !prevKeywordMap.isEmpty()) {
                return removeCondition(removeExecute(sql, customKeywordStartIndex, prevKeywordMap), customKeyword, offSet);
            } else {
                return removeCondition(sql, customKeyword, customKeywordStartIndex + 1);
            }
        } else {
            return sql;
        }
    }

    /**
     * 已知关键字所在位置是在条件中,要删除该关键字所在的查询条件
     *
     * @param sql                给定sql
     * @param customKeywordIndex 自关键词索引
     * @param prevKeywordMap     sql关键词
     * @return 删除了当前关键字索引所在的条件后的sql
     */
    private String removeExecute(String sql, int customKeywordIndex, Map prevKeywordMap) {
        List indexList = currentConditionInfo(sql, customKeywordIndex, prevKeywordMap);
        StringBuilder sqlBuilder = new StringBuilder(sql);
        StringBuilder newSql = sqlBuilder.delete(indexList.get(0), indexList.get(1));
        return newSql.toString();
    }

    /**
     * 获取当前位置所在条件语句的信息
     *
     * @param sql                给定sql
     * @param customKeywordIndex 自关键词索引
     * @param prevKeywordMap     sql关键词Map
     * @return indexList: [0]——表示起始索引 [1]——表示结束索引
     */
    private List currentConditionInfo(String sql, int customKeywordIndex, Map prevKeywordMap) {
        List indexList = new ArrayList<>(2);
        int keyOfSingleMap = keyOfSingleMap(prevKeywordMap);
        String sqlKeyword = prevKeywordMap.get(keyOfSingleMap);
        int prevAndIndex = prevAndIndex(sql, customKeywordIndex, sqlKeyword);
        int nextAndIndex = nextAndIndex(sql, customKeywordIndex, sqlKeyword);

        // 根据前一个and和后一个and的索引【它们不一定都存在】判断出当前条件语句的起末位置
        if (prevAndIndex != 0) {
            indexList.add(0, prevAndIndex);
            if (nextAndIndex != 0) {
                indexList.add(1, nextAndIndex);
            } else {
                // 在where语句中,直接到sql结尾;否则查找下一个sql关键词
                if ("where".equalsIgnoreCase(sqlKeyword)) {
                    indexList.add(1, sql.length());
                } else {
                    Map nextKeywordMap = nextKeywordMap(sql, customKeywordIndex);
                    int nextKeywordIndex = keyOfSingleMap(nextKeywordMap);
                    indexList.add(1, nextKeywordIndex);
                }
            }
        } else {
            // 当前条件语句是查询语句中第一个条件
            if (nextAndIndex != 0) {
                // 改条件语句后面还有其他条件语句
                indexList.add(0, keyOfSingleMap + sqlKeyword.length());
                indexList.add(1, nextAndIndex + 3);
            } else {
                // 该条件语句是唯一的条件语句,where语句整个删除,join语句不做操作
                if ("where".equalsIgnoreCase(sqlKeyword)) {
                    indexList.add(0, keyOfSingleMap);
                    indexList.add(1, sql.length());
                }
            }
        }
        return indexList;
    }

    /**
     * 获取当前条件语句中前一个 and 关键词的索引
     */
    private int prevAndIndex(String sql, int customKeywordIndex, String sqlKeyword) {
        // 往前查询,直到sql关键词之处。若存在”and“,说明这不是第一个条件语句,否则这是第一个条件语句
        Map prevWordMap = prevWordMap(sql, customKeywordIndex);
        int prevWordIndex = prevWordMap.keySet().iterator().next();
        int prevAndIndex = 0;
        while (!sqlKeyword.equalsIgnoreCase(prevWordMap.get(prevWordIndex))) {
            if ("and".equalsIgnoreCase(prevWordMap.get(prevWordIndex))) {
                prevAndIndex = prevWordIndex;
                break;
            } else {
                prevWordMap = prevWordMap(sql, prevWordIndex);
                prevWordIndex = prevWordMap.keySet().iterator().next();
            }
        }
        return prevAndIndex;
    }

    /**
     * 获取当前条件语句中下一个 and 关键词的索引
     */
    private int nextAndIndex(String sql, int customKeywordIndex, String sqlKeyword) {
        // 往后查询,如果当前是在join语句中,则设定循环截至到where关键词;否则循环截至到最后。判断这里面有没有出现“and”
        Map nextWordMap = nextWordMap(sql, customKeywordIndex);
        int nextWordIndex = keyOfSingleMap(nextWordMap);
        int nextAndIndex = 0;
        while (nextWordIndex < sql.length()) {
            if ("and".equalsIgnoreCase(nextWordMap.get(nextWordIndex))) {
                nextAndIndex = nextWordIndex;
                break;
            } else {
                nextWordMap = nextWordMap(sql, nextWordIndex);
                nextWordIndex = keyOfSingleMap(nextWordMap);
            }
            // join语句中则以下一个where关键词为截止点
            if ("on".equalsIgnoreCase(sqlKeyword) && "where".equalsIgnoreCase(nextWordMap.get(nextWordIndex))) {
                break;
            }
        }
        return nextAndIndex;
    }

    /**
     * 前一个单词或后一个单词是关系运算符则判定当前索引在条件语句或者case-when语句中
     * 通过前面的第一个关键词,排除其在case-when语句中的情况
     */
    private Map> inCondition(String sql, int currentIndex) {
        Map> result = new HashMap<>(1);
        Map prevKeywordMap = prevKeywordMap(sql, currentIndex);
        String prevKeyword = valueOfSingleMap(prevKeywordMap);
        if ((isRelationalOperator(prevWord(sql, currentIndex)) || isRelationalOperator(nextWord(sql, currentIndex)))
                && ("join".equalsIgnoreCase(prevKeyword) || "on".equalsIgnoreCase(prevKeyword) || "where".equalsIgnoreCase(prevKeyword))) {
            result.put(true, prevKeywordMap);
        }
        return result;
    }

    /**
     * 当前”单词“是否是关系运算符
     * 其他复杂的情况,包括大小写问题可自行扩展
     */
    private boolean isRelationalOperator(String word) {
        return word.contains("=")
                || word.contains(">")
                || word.contains("<")
                || word.contains("between")
                || word.contains("and")
                || word.contains("or");
    }

    private String nextWord(String str, int currentIndex) {
        Map wordMap = nextWordMap(str, currentIndex);
        return valueOfSingleMap(wordMap);
    }

    private String prevWord(String str, int currentIndex) {
        Map wordMap = prevWordMap(str, currentIndex);
        return valueOfSingleMap(wordMap);
    }

    /**
     * 获取单元素map中的key
     *
     * @param singleMap 单元素Map
     * @return 该map中唯一的key
     */
    private int keyOfSingleMap(Map singleMap) {
        return singleMap.keySet().iterator().next();
    }

    /**
     * 获取单元素map中的value
     *
     * @param singleMap 单元素Map
     * @return 该map中唯一的value
     */
    private String valueOfSingleMap(Map singleMap) {
        return singleMap.get(keyOfSingleMap(singleMap));
    }

    private Map prevKeywordMap(String sql, int currentIndex) {
        Map wordMap = prevWordMap(sql, currentIndex);
        Integer index = wordMap.keySet().iterator().next();
        if (!keywordList.contains(wordMap.get(index).toLowerCase())) {
            return prevKeywordMap(sql, index);
        } else {
            return wordMap;
        }
    }

    private Map nextKeywordMap(String sql, int currentIndex) {
        Map wordMap = nextWordMap(sql, currentIndex);
        Integer index = wordMap.keySet().iterator().next();
        if (!keywordList.contains(wordMap.get(index).toLowerCase())) {
            return nextKeywordMap(sql, index);
        } else {
            return wordMap;
        }
    }

    /**
     * 下一个单词【右侧】
     *
     * @param str          所在语句
     * @param currentIndex 当前单词的起始索引【自左向右】
     * @return 右侧下一个单词的索引和字符串
     */
    private Map nextWordMap(String str, int currentIndex) {
        int firstIndex;
        int endIndex;
        // 当前索引位置是 ‘ ’则找出下一个单词的起始,否则找到本单词起始位置
        Map wordMap = new HashMap<>();
        String word;
        if (str.charAt(currentIndex) == ' ') {
            firstIndex = nextWordBeginIndex(str, currentIndex);
        } else {
            endIndex = currentWordEndIndex(str, currentIndex);
            firstIndex = nextWordBeginIndex(str, endIndex + 1);
        }
        endIndex = currentWordEndIndex(str, firstIndex);
        word = str.substring(firstIndex, endIndex + 1);
        wordMap.put(firstIndex, word);
        return wordMap;
    }

    /**
     * 前一个单词【左侧】
     *
     * @param str          所在语句
     * @param currentIndex 当前单词的起始索引【自左向右】
     * @return 左侧前一个单词的索引和字符串
     */
    private Map prevWordMap(String str, int currentIndex) {
        int firstIndex;
        int endIndex;
        // 当前索引位置是' ', 则找出前一个单词的结尾,否则找到本单词的开头
        Map wordMap = new HashMap<>();
        String word;
        if (str.charAt(currentIndex) == ' ') {
            endIndex = prevWordEndIndex(str, currentIndex);
        } else {
            firstIndex = currentWordBeginIndex(str, currentIndex);
            endIndex = prevWordEndIndex(str, firstIndex - 1);
        }
        firstIndex = currentWordBeginIndex(str, endIndex);
        word = str.substring(firstIndex, endIndex + 1);
        wordMap.put(firstIndex, word);
        return wordMap;
    }

    private int prevWordEndIndex(String str, int currentIndex) {
        for (int i = currentIndex; i >= 0; i--) {
            if (str.charAt(i) == ' ') {
                continue;
            }
            if (isEndOfWord(str, i)) {
                return i;
            }
        }
        return 0;
    }

    private int currentWordBeginIndex(String str, int currentIndex) {
        for (int i = currentIndex; i >= 0; i--) {
            if (isBeginOfWord(str, i)) {
                return i;
            }
        }
        return 0;
    }

    private int nextWordBeginIndex(String str, int currentIndex) {
        for (int i = currentIndex; i < str.length(); i++) {
            if (str.charAt(i) == ' ') {
                continue;
            }
            if (isBeginOfWord(str, i)) {
                return i;
            }
        }
        return 0;
    }

    private int currentWordEndIndex(String str, int currentIndex) {
        for (int i = currentIndex; i < str.length(); i++) {
            if (isEndOfWord(str, i)) {
                return i;
            }
        }
        return 0;
    }

    /**
     * 当前索引指向单词的最后,但不超过该单词
     */
    private boolean isEndOfWord(String str, int index) {
        if (index == str.length() - 1) {
            return true;
        }
        return (str.charAt(index + 1) == ' ' || str.charAt(index + 1) == ')') && str.charAt(index) != ' ';
    }

    /**
     * 当前索引指向单词的开始,但不超过该单词
     */
    private boolean isBeginOfWord(String str, int index) {
        if (index == 0) {
            return true;
        }
        return (str.charAt(index - 1) == ' ' || str.charAt(index - 1) == '(') && str.charAt(index) != ' ';
    }
}

你可能感兴趣的:(从sql中提取表名称,删除sql中的某个查询条件)