最近有个Es查询的需求,用户在前端输入sql语句直接拼条件,然后后台去查询。
因为es本身带有类sql查询,刚开始打算用sql查的,但是分页的limit只有一个查询条数,没有from和size,比如es可以通过类sql 的 limit 1000 一次查出来1000条数据,但是没法通过limit 900,100查出来第900到1000的数据,想实现就得先limit 1000再去截取后100条,没办法了,只能解析sql再去拼接条件了。这块的解析实现需要感谢以下两篇贴子,
灵感来源
灵感来源
https://elasticsearch.cn/article/114
用二叉树简直是神设计,当时为了最终结构困扰了我半天
https://blog.csdn.net/qzshiyongjie123/article/details/54341472
这篇关于具体代码实现讲的挺详细的,唯一的缺点是代码不全,我也是在这篇的基础上修改来的
代码实现
实体结构
这块本质上还是二叉树,但是新加了几个属性,以如下语句举例说明
a = 1 or (b = 2 and not c != 3)
left: 英文字段名 // a、b、c
right: 字段对应值 // 1、2、3
inRelat: 字段和值的关系 // = 、!=
outRelat (字段-值)之间的连接关系 // and、or
hasNot 当前(字段-值)是否包含not
sqlSon 括号圈起来的算一个整体
实体属性如下:
@Data
public class SqlNode {
// 字段名
private String left;
// 值
private String right;
// 内关系符 > < =
private String inRelat;
// 外关系符 and or
private String outRelat;
// 是否not
private boolean hasNot;
// 子关系式
private List<SqlNode> sqlSon;
public SqlNode() {
}
public SqlNode(String left, String right, String inRelat, boolean hasNot) {
this.left = left;
this.right = right;
this.inRelat = inRelat;
this.hasNot = hasNot;
}
}
1.将sql语句分解成数组
public static String[] handSpace(String sql) {
sql = sql.trim();
String tempStr = "";
boolean hasFirstSpace = false;
boolean hasDoublePoint = false;
List<String> list = new ArrayList<>();
for (int i = 0; i < sql.length(); i++) {
char curChar = sql.charAt(i);
String charStr = Convert.toStr(curChar);
switch (curChar) {
case '"':
hasDoublePoint = hasDoublePoint == false ? true : false;
if (i == sql.length() - 1) {
list.add(tempStr);
}
break;
case '(':
if (hasDoublePoint == false) {
// 不在字符串内直接 添加 (
list.add(charStr);
} else {
// 字符串内继续拼接
tempStr += charStr;
}
break;
case ')':
// 不在字符串内
if (hasDoublePoint == false) {
if (StringUtils.isNotBlank(tempStr)) {
// 前面有字符串
list.add(tempStr);
tempStr = "";
}
// 遍历到最后一位 -> 直接添加
if (i == sql.length() - 1) {
list.add(")");
} else {
list.add(")");
}
} else {
// 字符串内继续拼接
tempStr += charStr;
}
break;
case ' ':
// 不在字符串内
if (hasDoublePoint == false) {
// 第一个空格 -> 添加前一个词组
if (hasFirstSpace == false) {
if (StringUtils.isNotBlank(tempStr)) {
list.add(tempStr);
tempStr = "";
hasFirstSpace = true;
}
}
} else {
// 字符串内继续拼接
tempStr += curChar;
}
break;
default:
// 普通字符继续拼接
tempStr += curChar;
hasFirstSpace = false;
// 遍历到最后一位 -> 直接添加
if (i == sql.length() - 1) {
list.add(tempStr);
}
break;
}
}
String[] array = new String[list.size()];
return list.toArray(array);
}
这步主要是为了拆分数据,便于下一步更好进行条件转换,写个sql看下实际效果
2.对数组完成树结构的转换
public static void paraseSql(String[] arr, List<SqlNode> nodes) throws Exception {
Queue<String> queue = new ArrayDeque<String>();
int bracketsStart = 0;
int bracketsEnd = 0;
for (int i = 0; i < arr.length; i++) {
String left;
String right = null;
String outRelat;
String inRelat;
boolean hasNot;
String curStr = arr[i];
if (curStr.equals("(")) {
if (queue.size() == 0) {
// 只记录首次下标
bracketsStart = i;
}
queue.add("(");
} else if (curStr.equals(")")) {
queue.poll();
if (queue.size() == 0) {
// 括号闭环了 才能处理
// 判断and not / or not
hasNot = bracketsStart - 1 > 0 ? (arr[bracketsStart - 1].matches(NOT)) : false;
int andOrIndex;
// 前面是否有 and / or
if (bracketsStart - 1 > 0) {
andOrIndex = hasNot ? bracketsStart - 2 : bracketsStart - 1;
outRelat = arr[andOrIndex].matches(AND) ? AND : OR;
} else {
// 后面 and / or
outRelat = i + 1 < arr.length ? (arr[i + 1].matches(AND) ? AND : OR) : AND;
}
bracketsEnd = i;
SqlNode sqlNode = new SqlNode();
List<SqlNode> sonNodes = new ArrayList<>();
String[] noBracketsArr = Arrays.copyOfRange(arr, bracketsStart + 1, bracketsEnd);
// 接着递归处理
paraseSql(noBracketsArr, sonNodes);
sqlNode.setSqlSon(sonNodes);
sqlNode.setOutRelat(outRelat);
sqlNode.setHasNot(hasNot);
nodes.add(sqlNode);
}
} else if (curStr.matches("(" + AND + ")|(" + OR + ")")) {
// 括号遍历完了才能加
if (queue.size() == 0) {
if (!arr[i - 1].equals(")")) {
boolean hasExist = (arr[i - 1].equals(EXIST) || arr[i - 1].equals(NOT_EXIST)) ? true : false;
if (hasExist) {
hasNot = i - 3 >= 0 ? (arr[i - 3].equals(NOT) ? true : false) : false;
// 第一个条件以当前and / or 为准,语句中间的以前面的条件为准
outRelat = i - 3 > 0 ? (arr[i - 3].matches("(" + AND + ")|(" + OR + ")") ? arr[i - 3] : arr[i - 4]) : curStr;
left = arr[i - 2];
inRelat = arr[i - 1];
} else {
hasNot = i - 4 >= 0 ? (arr[i - 4].equals(NOT) ? true : false) : false;
outRelat = i - 4 > 0 ? (arr[i - 4].matches("(" + AND + ")|(" + OR + ")") ? arr[i - 4] : arr[i - 5]) : curStr;
left = arr[i - 3];
inRelat = arr[i - 2];
right = arr[i - 1];
}
SqlNode sqlNode = new SqlNode();
sqlNode.setLeft(left);
sqlNode.setHasNot(hasNot);
sqlNode.setRight(right);
sqlNode.setInRelat(inRelat);
sqlNode.setOutRelat(outRelat);
nodes.add(sqlNode);
}
}
} else if (i == arr.length - 1) {
// 最后一个了
// 判断是不是 exist
boolean hasExist = (arr[i].equals(EXIST) || arr[i].equals(NOT_EXIST)) ? true : false;
if (hasExist) {
hasNot = i - 2 > 0 ? (arr[i - 2].equals(NOT) ? true : false) : false;
outRelat = i - 2 > 0 ? (arr[i - 2].matches("(" + AND + ")|(" + OR + ")") ? arr[i - 2] : arr[i - 3]) : AND;
left = arr[i - 1];
inRelat = arr[i];
} else {
// 有 and / or
hasNot = i - 3 > 0 ? (arr[i - 3].equals(NOT) ? true : false) : false;
outRelat = i - 3 > 0 ? (arr[i - 3].matches("(" + AND + ")|(" + OR + ")") ? arr[i - 3] : arr[i - 4]) : AND;
left = arr[i - 2];
right = arr[i];
inRelat = arr[i - 1];
}
if (!outRelat.matches("(" + AND + ")|(" + OR + ")")) {
throw new Exception("sql语法有误,请修改后重试!");
}
SqlNode sqlNode = new SqlNode();
sqlNode.setLeft(left);
sqlNode.setRight(right);
sqlNode.setInRelat(inRelat);
sqlNode.setOutRelat(outRelat);
sqlNode.setHasNot(hasNot);
nodes.add(sqlNode);
}
}
}
传一个空list看下实际效果
成功分离出来了,现在只要遍历SqlNode集合就能按照关系组装出想要的数据了。
下面贴上全部代码
package com.event.entity.param;
import com.common.util.text.Convert;
import com.event.util.SqlNode;
import lombok.Builder;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
/**
* @author fjl
* @date 2022/9/26
*/
@Data
@Builder
public class util {
private static final String AND = "and";
private static final String OR = "or";
private static final String NOT = "not";
private static final String EXIST = "exist";
private static final String NOT_EXIST = "not_exist";
public static void main(String[] args) throws Exception {
String str = "a = 1 and ( b = 3 or c = 4 )";
String[] fieldsArr = handSpace(str);
List<SqlNode> nodes = new ArrayList<>();
paraseSql(fieldsArr, nodes);
}
public static void paraseSql(String[] arr, List<SqlNode> nodes) throws Exception {
Queue<String> queue = new ArrayDeque<String>();
int bracketsStart = 0;
int bracketsEnd = 0;
for (int i = 0; i < arr.length; i++) {
String left;
String right = null;
String outRelat;
String inRelat;
boolean hasNot;
String curStr = arr[i];
if (curStr.equals("(")) {
if (queue.size() == 0) {
// 只记录首次下标
bracketsStart = i;
}
queue.add("(");
} else if (curStr.equals(")")) {
queue.poll();
if (queue.size() == 0) {
// 括号闭环了 才能处理
// 判断and not / or not
hasNot = bracketsStart - 1 > 0 ? (arr[bracketsStart - 1].matches(NOT)) : false;
int andOrIndex;
// 前面是否有 and / or
if (bracketsStart - 1 > 0) {
andOrIndex = hasNot ? bracketsStart - 2 : bracketsStart - 1;
outRelat = arr[andOrIndex].matches(AND) ? AND : OR;
} else {
// 后面 and / or
outRelat = i + 1 < arr.length ? (arr[i + 1].matches(AND) ? AND : OR) : AND;
}
bracketsEnd = i;
SqlNode sqlNode = new SqlNode();
List<SqlNode> sonNodes = new ArrayList<>();
String[] noBracketsArr = Arrays.copyOfRange(arr, bracketsStart + 1, bracketsEnd);
// 接着递归处理
paraseSql(noBracketsArr, sonNodes);
sqlNode.setSqlSon(sonNodes);
sqlNode.setOutRelat(outRelat);
sqlNode.setHasNot(hasNot);
nodes.add(sqlNode);
}
} else if (curStr.matches("(" + AND + ")|(" + OR + ")")) {
// 括号遍历完了才能加
if (queue.size() == 0) {
if (!arr[i - 1].equals(")")) {
boolean hasExist = (arr[i - 1].equals(EXIST) || arr[i - 1].equals(NOT_EXIST)) ? true : false;
if (hasExist) {
hasNot = i - 3 >= 0 ? (arr[i - 3].equals(NOT) ? true : false) : false;
// 第一个条件以当前and / or 为准,语句中间的以前面的条件为准
outRelat = i - 3 > 0 ? (arr[i - 3].matches("(" + AND + ")|(" + OR + ")") ? arr[i - 3] : arr[i - 4]) : curStr;
left = arr[i - 2];
inRelat = arr[i - 1];
} else {
hasNot = i - 4 >= 0 ? (arr[i - 4].equals(NOT) ? true : false) : false;
outRelat = i - 4 > 0 ? (arr[i - 4].matches("(" + AND + ")|(" + OR + ")") ? arr[i - 4] : arr[i - 5]) : curStr;
left = arr[i - 3];
inRelat = arr[i - 2];
right = arr[i - 1];
}
SqlNode sqlNode = new SqlNode();
sqlNode.setLeft(left);
sqlNode.setHasNot(hasNot);
sqlNode.setRight(right);
sqlNode.setInRelat(inRelat);
sqlNode.setOutRelat(outRelat);
nodes.add(sqlNode);
}
}
} else if (i == arr.length - 1) {
// 最后一个了
// 判断是不是 exist
boolean hasExist = (arr[i].equals(EXIST) || arr[i].equals(NOT_EXIST)) ? true : false;
if (hasExist) {
hasNot = i - 2 > 0 ? (arr[i - 2].equals(NOT) ? true : false) : false;
outRelat = i - 2 > 0 ? (arr[i - 2].matches("(" + AND + ")|(" + OR + ")") ? arr[i - 2] : arr[i - 3]) : AND;
left = arr[i - 1];
inRelat = arr[i];
} else {
// 有 and / or
hasNot = i - 3 > 0 ? (arr[i - 3].equals(NOT) ? true : false) : false;
outRelat = i - 3 > 0 ? (arr[i - 3].matches("(" + AND + ")|(" + OR + ")") ? arr[i - 3] : arr[i - 4]) : AND;
left = arr[i - 2];
right = arr[i];
inRelat = arr[i - 1];
}
if (!outRelat.matches("(" + AND + ")|(" + OR + ")")) {
throw new Exception("sql语法有误,请修改后重试!");
}
SqlNode sqlNode = new SqlNode();
sqlNode.setLeft(left);
sqlNode.setRight(right);
sqlNode.setInRelat(inRelat);
sqlNode.setOutRelat(outRelat);
sqlNode.setHasNot(hasNot);
nodes.add(sqlNode);
}
}
}
public static String[] handSpace(String sql) {
sql = sql.trim();
String tempStr = "";
boolean hasFirstSpace = false;
boolean hasDoublePoint = false;
List<String> list = new ArrayList<>();
for (int i = 0; i < sql.length(); i++) {
char curChar = sql.charAt(i);
String charStr = Convert.toStr(curChar);
switch (curChar) {
case '"':
hasDoublePoint = hasDoublePoint == false ? true : false;
if (i == sql.length() - 1) {
list.add(tempStr);
}
break;
case '(':
if (hasDoublePoint == false) {
// 不在字符串内直接 添加 (
list.add(charStr);
} else {
// 字符串内继续拼接
tempStr += charStr;
}
break;
case ')':
// 不在字符串内
if (hasDoublePoint == false) {
if (StringUtils.isNotBlank(tempStr)) {
// 前面有字符串
list.add(tempStr);
tempStr = "";
}
// 遍历到最后一位 -> 直接添加
if (i == sql.length() - 1) {
list.add(")");
} else {
list.add(")");
}
} else {
// 字符串内继续拼接
tempStr += charStr;
}
break;
case ' ':
// 不在字符串内
if (hasDoublePoint == false) {
// 第一个空格 -> 添加前一个词组
if (hasFirstSpace == false) {
if (StringUtils.isNotBlank(tempStr)) {
list.add(tempStr);
tempStr = "";
hasFirstSpace = true;
}
}
} else {
// 字符串内继续拼接
tempStr += curChar;
}
break;
default:
// 普通字符继续拼接
tempStr += curChar;
hasFirstSpace = false;
// 遍历到最后一位 -> 直接添加
if (i == sql.length() - 1) {
list.add(tempStr);
}
break;
}
}
String[] array = new String[list.size()];
return list.toArray(array);
}
}