解析sql,给指定的表名,添加条件
目的:
给sql每个待匹配的table_name,添加自己的条件
问题:
- 需要支持复杂的sql语句
- 需要支持小括号
- 需要支持自连接
- 需要支持单引号
- 暂不支持双引号
思路:
- 找 table_name,顺便找到table_name的别名
- 向后找同层次的where 表达式位置, 添加条件
- 找不到,则,找到where同层次应该在的位置,添加where以及条件
代码实现:
主方法,主流程
/**
* 根据sql和info信息,生成新的sql
* @param sql
* 待处理sql
* @param info
* 数据处理信息
* 两项内容:1,table_name;2,不包含别名的sql条件
* @return
* 处理后的sql
*/
private static String getDataSql(String sql, DataInfo info) {
/*
* 目的:
* 给sql每个待匹配的table_name,添加自己的条件
* 问题:
* 1, 需要支持复杂的sql语句
* 2, 需要支持小括号
* 3, 需要支持自连接
* 4, 需要支持单引号
* 思路:
* 1,找 table_name,顺便找到table_name的别名
* 2,向后找同层次的where 表达式位置, 添加条件
* 3,找不到,则,找到where同层次应该在的位置,添加where以及条件
* 注意:
* 暂不支持双引号!!!
*/
// 判空
if (StrUtil.isBlank(sql)
|| info == null
|| StrUtil.isBlank(info.getTableName())
|| StrUtil.isBlank(info.getSql())){
return sql;
}
// where以及where后面的关键字,以及结尾
Map<String, Integer> whereAfter = new HashMap<>();
whereAfter.put("where", 0);
whereAfter.put("group", 0);
whereAfter.put("having", 0);
whereAfter.put("order", 0);
whereAfter.put("limit", 0);
whereAfter.put("unit", 0);
whereAfter.put(";", 0);
// 初步处理, 转小写
sql = toLowerCaseSql(sql);
info.setTableName(info.getTableName().toLowerCase());
info.setSql(toLowerCaseSql(info.getSql()));
// 准备其他数据
int beginIndex = 0;
char[] sqlChars = sql.toCharArray();
// 不止一个位置需要处理
while (true){
beginIndex = indexOfMy2(sql, info.getTableName(), beginIndex);
if (beginIndex < 0) {
break;
}
// 找到触发点后,顺便获取表名
String tableName = findTableNameWord(sql, beginIndex, info.getTableName());
// 可能where位置的关键词
for (Map.Entry<String, Integer> entry : whereAfter.entrySet()) {
String key = entry.getKey();
int of = indexOfMy(sql, key, beginIndex, 0, false);
whereAfter.put(key, of > 0 ? of : sql.length());
}
// 右括号处理,ps:where一定在多余的右括号之前。
whereAfter.put("rpIndex", sql.length());
int rpn = 0;
for (int i = beginIndex; i < sqlChars.length; i++) {
if (sqlChars[i] == '('){
rpn --;
}
else if (sqlChars[i] == ')'){
rpn ++;
}
if (rpn > 0){
whereAfter.put("rpIndex", i);
break;
}
}
// 拿到最靠前的位置,进行处理
int afterIndex = whereAfter.values().stream().min(Integer::compareTo).orElse(sql.length());
/*
* 拿到位置之后,可能分为三种情况
* 1,到结尾了
* 2,找到了where语句
* 3,没找到where语句,找到了where语句应该在的位置
*/
if (afterIndex == sql.length()){
sql = sql + " where " + tableName + "."+ info.getSql();
}else if(sql.startsWith("where", afterIndex)){
sql = sql.substring(0, afterIndex + 5) + " " + tableName + "." + info.getSql() + " and " + sql.substring(afterIndex + 5);
}else {
sql = sql.substring(0, afterIndex) + " where " + tableName + "." + info.getSql() + " " + sql.substring(afterIndex);
}
// beginIndex 向前移动
beginIndex ++;
}
return sql;
}
sql转小写处理
/**
* sql语句转小写
* @param sql
* 待处理sql
* @return
* 处理完的sql
*/
private static String toLowerCaseSql(String sql) {
if (StrUtil.isBlank(sql)){
return null;
}
char[] chars = sql.toCharArray();
boolean toLower = true;
for (int i = 0; i < chars.length; i++) {
if (chars[i] == '\''){
toLower = !toLower;
}
if (toLower){
if (chars[i] >= 'A' && chars[i] <= 'Z'){
chars[i] = (char) (chars[i] - ('A' - 'a'));
}
}
}
return String.valueOf(chars);
}
sql内拿到表名
/**
* sql查找的时候不进行单引号内部都的扫描匹配
* @param sql
* 待处理sql
* @param tableName
* 表名
* @param beginIndex
* 开始位置
* @return
*
*/
private static int indexOfMy2(String sql, String tableName, int beginIndex) {
int of = sql.indexOf(tableName, beginIndex);
int of1 = sql.indexOf("'");
if (of < 0){
return -1;
}
if (of1 < 0){
return of;
}
// 去掉 table_name被引用的情况
int off = sql.indexOf(tableName + ".", beginIndex);
int offf = sql.indexOf(tableName + "\n.", beginIndex);
if (of == off){
return indexOfMy2(sql, tableName, ++ beginIndex);
}
if (of == offf){
return indexOfMy2(sql, tableName, ++ beginIndex);
}
char[] chars = sql.toCharArray();
boolean isLive = false;
boolean isOk = false;
for (int i = 0; i < chars.length; i++) {
if (chars[i] == '\''){
isLive = !isLive;
}
if (i == of){
if (isLive){
isOk = true;
}else {
return of;
}
}
if (!isLive && isOk){
return indexOfMy2(sql, tableName, ++i);
}
}
return -1;
}
获取表的别名,没有就拿表名
/**
* 拿到表名或者表的别名
* @param sql
* sql语句
* @param beginIndex
* 表名点
* @param tableName
* 表名, 也是当前字符串起始位置
* @return
* 表的全名,或者别名
*/
private static String findTableNameWord(String sql, int beginIndex, String tableName){
/*
* 目标:
* 拿表的别名,没有则返回表名。
* 思路:
* 给了sql,以及表名的起始位置,以及表名
* 基本去掉表名之后,看一下后面有什么就行,大概有几种情况:
* 1,sql结束了,肯定没有别名
* 2,sql没有结束,进行trim之后,肯定可以取到值的
* 3,看看取值是否是关键字,不是就是别名,是的话,就没有别名
* 4,其他值的处理:右括号,逗号等,符号处理。
*/
String s = sql.substring(beginIndex).replaceFirst(tableName, "").trim();
if (s.length() == 0) {
return tableName;
}
char[] chars = s.toCharArray();
StringBuilder nextWord = new StringBuilder();
for (char aChar : chars) {
if (aChar >= 'a' && aChar <= 'z'){
nextWord.append(aChar);
}else {
break;
}
}
String next = nextWord.toString();
Set<String> set = new HashSet<>();
set.add("inner");
set.add("full");
set.add("left");
set.add("right");
set.add("join");
set.add("where");
set.add("group");
set.add("having");
set.add("order");
set.add("limit");
set.add("unit");
if (set.contains(next) || StrUtil.isBlank(nextWord)){
return tableName;
}
return next;
}
sql中拿到同级别关键字的位置
/**
* indexOf 添加更多处理
* 解决小括号问题。
* 解决单引号问题。
* @param sql
* 待处理sql
* @param key
* 待匹配值(String)
* @param beginIndex
* 开始位置
* @param lpn
* 已经匹配到的左括号数量
* @param isLive
* 是否已经匹配到单引号
* @return
* 最后匹配到的值
*/
private static int indexOfMy(String sql, String key, int beginIndex, int lpn, boolean isLive) {
/*
* 目的:
* 1,解决匹配sql的时候,不进行小括号中的匹配
* 2,解决匹配sql的时候,不进行单引号中的匹配
* 思路:
* 1,截至条件:后面的sql找不到key,不用处理,返回-1即可
* 2,小括号问题:向后扫描匹配括号,左括号+1,右括号-1,
* 多出的左括号可能为下一层,此时不能判定处理,只能接着向后找。
* 多出的右括号不需要处理,匹配括号的时候,已经单独拿出来处理。
* 小括号初始值设置为0,表示,没有括号的意思
* 3,单引号问题:向后赛马匹配单引号,有则置为相反的,因为相邻的单引号,一定会组成一对,进行抵消操作
* 若是多出一个,则没有抵消的地方,则此时关键词可能在小括号中,不能进行匹配,只能向后继续查找,
* 单引号初始值设置为false,表示没有单引号的意思
*
*/
// 这次匹配到的位置
int indexOf = sql.indexOf(key, beginIndex);
// 找不到的情况,不用特殊处理
if (indexOf < 0){
return -1;
}
// 小括号问题
int rIndex = sql.indexOf("(", beginIndex);
// 单引号问题
int sIndex = sql.indexOf("'", beginIndex);
// 若是一个条件成立则进入循环操作
if (lpn > 0 || rIndex > 0 && rIndex < indexOf
|| isLive || sIndex > 0 && sIndex < indexOf
){
char[] chars = sql.toCharArray();
for (int i = beginIndex; i < indexOf; i++) {
if (chars[i] == '(') {
lpn ++;
}
if (chars[i] == ')') {
lpn --;
}
if (chars[i] == '\'') {
isLive = !isLive;
}
}
// 若是,有一个条件成立,则递归处理,即向后继续查找
if (lpn > 0){
return indexOfMy(sql, key, ++ indexOf, lpn, isLive);
}
if (isLive){
return indexOfMy(sql, key, ++ indexOf, lpn, true);
}
}
return indexOf;
}