fix(crud): 修复TermExpressionParser解析逻辑,支持更多条件运算符和空格处理

This commit is contained in:
zhouhao
2026-01-26 12:35:54 +08:00
parent 314abfc043
commit 76cb89f56e
2 changed files with 249 additions and 46 deletions

View File

@@ -9,6 +9,7 @@ import org.hswebframework.ezorm.core.param.Term;
import org.hswebframework.ezorm.core.param.TermType;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -82,7 +83,7 @@ public class TermExpressionParser {
@SneakyThrows
public static List<Term> parse(String expression) {
try {
expression = URLDecoder.decode(expression, "utf-8");
expression = URLDecoder.decode(expression, StandardCharsets.UTF_8);
} catch (Throwable ignore) {
}
@@ -91,15 +92,13 @@ public class TermExpressionParser {
NestConditional<?> nest = null;
// 字符容器
char[] buf = new char[128];
// 记录词项的长度, Arrays.copyOf使用
byte len = 0;
StringBuilder buf = new StringBuilder(128);
// 空格数量?
byte spaceLen = 0;
// 当前列
char[] currentColumn = null;
String currentColumn = null;
// 当前列对应的值
char[] currentValue = null;
String currentValue = null;
// 当前条件类型 eq btw in ...
String currentTermType = null;
// 当前链接类型 and / or
@@ -124,24 +123,24 @@ public class TermExpressionParser {
nest = (nest == null ?
(currentType.equals("or") ? conditional.orNest() : conditional.nest()) :
(currentType.equals("or") ? nest.orNest() : nest.nest()));
len = 0;
buf.setLength(0);
continue;
} else if (c == ')') {
if (nest == null) {
continue;
}
if (null != currentColumn) {
currentValue = Arrays.copyOf(buf, len);
nest.accept(new String(currentColumn), convertTermType(currentTermType), new String(currentValue));
currentValue = buf.toString();
nest.accept(currentColumn, convertTermType(currentTermType), currentValue);
currentColumn = null;
currentTermType = null;
}
Object end = nest.end();
nest = end instanceof NestConditional ? ((NestConditional) end) : null;
len = 0;
nest = end instanceof NestConditional ? ((NestConditional<?>) end) : null;
buf.setLength(0);
spaceLen++;
continue;
} else if (c == '=' || c == '>' || c == '<') {
} else if (c == '=' || c == '>' || c == '<' || c == '!') {
if (currentTermType != null) {
currentTermType += String.valueOf(c);
//spaceLen--;
@@ -150,44 +149,44 @@ public class TermExpressionParser {
}
if (currentColumn == null) {
currentColumn = Arrays.copyOf(buf, len);
currentColumn = buf.toString();
}
spaceLen++;
len = 0;
buf.setLength(0);
continue;
} else if (c == ' ') {
if (len == 0) {
if (buf.isEmpty()) {
continue;
}
if (quotationMarks != 0) {
// 如果当前字符是空格,并且前面迭代时碰到过单/双引号, 不处理并且添加到buf中
buf[len++] = c;
buf.append(c);
continue;
}
spaceLen++;
if (currentColumn == null && (spaceLen == 1 || spaceLen % 5 == 0)) {
currentColumn = Arrays.copyOf(buf, len);
len = 0;
currentColumn = buf.toString();
buf.setLength(0);
continue;
}
if (null != currentColumn) {
if (null == currentTermType) {
currentTermType = new String(Arrays.copyOf(buf, len));
len = 0;
currentTermType = buf.toString();
buf.setLength(0);
continue;
}
currentValue = Arrays.copyOf(buf, len);
currentValue = buf.toString();
if (nest != null) {
nest.accept(new String(currentColumn), convertTermType(currentTermType), new String(currentValue));
nest.accept(currentColumn, convertTermType(currentTermType), currentValue);
} else {
conditional.accept(new String(currentColumn), convertTermType(currentTermType), new String(currentValue));
conditional.accept(currentColumn, convertTermType(currentTermType), currentValue);
}
currentColumn = null;
currentTermType = null;
len = 0;
buf.setLength(0);
continue;
} else if (len == 2 || len == 3) {
String type = new String(Arrays.copyOf(buf, len));
} else if (buf.length() == 2 || buf.length() == 3) {
String type = buf.toString();
if (type.equalsIgnoreCase("or")) {
currentType = "or";
if (nest != null) {
@@ -195,7 +194,7 @@ public class TermExpressionParser {
} else {
conditional.or();
}
len = 0;
buf.setLength(0);
continue;
} else if (type.equalsIgnoreCase("and")) {
currentType = "and";
@@ -204,29 +203,29 @@ public class TermExpressionParser {
} else {
conditional.and();
}
len = 0;
buf.setLength(0);
continue;
} else {
currentColumn = Arrays.copyOf(buf, len);
len = 0;
currentColumn = buf.toString();
buf.setLength(0);
spaceLen++;
}
} else {
currentColumn = Arrays.copyOf(buf, len);
len = 0;
currentColumn = buf.toString();
buf.setLength(0);
spaceLen++;
}
continue;
}
buf[len++] = c;
buf.append(c);
}
if (null != currentColumn) {
currentValue = Arrays.copyOf(buf, len);
currentValue = buf.toString();
if (nest != null) {
nest.accept(new String(currentColumn), convertTermType(currentTermType), new String(currentValue));
nest.accept(currentColumn, convertTermType(currentTermType), currentValue);
} else {
conditional.accept(new String(currentColumn), convertTermType(currentTermType), new String(currentValue));
conditional.accept(currentColumn, convertTermType(currentTermType), currentValue);
}
}
return conditional.getParam().getTerms();
@@ -270,6 +269,8 @@ public class TermExpressionParser {
return TermType.gte;
case "<=":
return TermType.lte;
case "!=":
return TermType.not;
default:
return termType;
}

View File

@@ -16,13 +16,13 @@ public class TermExpressionParserTest {
public void testUrl(){
List<Term> terms = TermExpressionParser.parse("type=email%20and%20provider=test");
assertEquals(terms.get(0).getTermType(), TermType.eq);
assertEquals(terms.get(0).getColumn(), "type");
assertEquals(terms.get(0).getValue(), "email");
assertEquals(TermType.eq, terms.get(0).getTermType());
assertEquals("type", terms.get(0).getColumn());
assertEquals("email", terms.get(0).getValue());
assertEquals(terms.get(1).getTermType(), TermType.eq);
assertEquals(terms.get(1).getColumn(), "provider");
assertEquals(terms.get(1).getValue(), "test");
assertEquals(TermType.eq, terms.get(1).getTermType());
assertEquals("provider", terms.get(1).getColumn());
assertEquals("test", terms.get(1).getValue());
}
@@ -31,16 +31,16 @@ public class TermExpressionParserTest {
{
List<Term> terms = TermExpressionParser.parse("name = 我");
assertEquals(terms.get(0).getTermType(), TermType.eq);
assertEquals(terms.get(0).getValue(),"");
assertEquals(TermType.eq, terms.get(0).getTermType());
assertEquals("", terms.get(0).getValue());
}
{
List<Term> terms = TermExpressionParser.parse("name like %我%");
assertEquals(terms.get(0).getTermType(), TermType.like);
assertEquals(terms.get(0).getValue(),"%我%");
assertEquals(TermType.like, terms.get(0).getTermType());
assertEquals("%我%", terms.get(0).getValue());
}
}
@@ -115,4 +115,206 @@ public class TermExpressionParserTest {
}
}
@Test
public void testLessThan() {
List<Term> terms = TermExpressionParser.parse("age < 18");
assertEquals(1, terms.size());
assertEquals(TermType.lt, terms.get(0).getTermType());
assertEquals("age", terms.get(0).getColumn());
assertEquals("18", terms.get(0).getValue());
}
@Test
public void testLessThanOrEqual() {
List<Term> terms = TermExpressionParser.parse("price <= 100");
assertEquals(1, terms.size());
assertEquals(TermType.lte, terms.get(0).getTermType());
assertEquals("price", terms.get(0).getColumn());
assertEquals("100", terms.get(0).getValue());
}
@Test
public void testNotEqual() {
List<Term> terms = TermExpressionParser.parse("status != active");
assertEquals(1, terms.size());
assertEquals(TermType.not, terms.get(0).getTermType());
assertEquals("status", terms.get(0).getColumn());
assertEquals("active", terms.get(0).getValue());
}
@Test
public void testInOperator() {
List<Term> terms = TermExpressionParser.parse("status in active,inactive,pending");
assertEquals(1, terms.size());
assertEquals(TermType.in, terms.get(0).getTermType());
assertEquals("status", terms.get(0).getColumn());
}
@Test
public void testNotInOperator() {
List<Term> terms = TermExpressionParser.parse("type nin admin,root");
assertEquals(1, terms.size());
assertEquals(TermType.nin, terms.get(0).getTermType());
assertEquals("type", terms.get(0).getColumn());
}
@Test
public void testBetweenOperator() {
List<Term> terms = TermExpressionParser.parse("age btw 18,60");
assertEquals(1, terms.size());
assertEquals(TermType.btw, terms.get(0).getTermType());
assertEquals("age", terms.get(0).getColumn());
}
@Test
public void testIsNull() {
List<Term> terms = TermExpressionParser.parse("deletedTime isnull 1");
assertEquals(1, terms.size());
assertEquals(TermType.isnull, terms.get(0).getTermType());
assertEquals("deletedTime", terms.get(0).getColumn());
}
@Test
public void testNotNull() {
List<Term> terms = TermExpressionParser.parse("createTime notnull 1");
assertEquals(1, terms.size());
assertEquals(TermType.notnull, terms.get(0).getTermType());
assertEquals("createTime", terms.get(0).getColumn());
}
@Test
public void testIsEmpty() {
List<Term> terms = TermExpressionParser.parse("description empty 1");
assertEquals(1, terms.size());
assertEquals(TermType.empty, terms.get(0).getTermType());
assertEquals("description", terms.get(0).getColumn());
}
@Test
public void testNotEmpty() {
List<Term> terms = TermExpressionParser.parse("name nempty 1");
assertEquals(1, terms.size());
assertEquals(TermType.nempty, terms.get(0).getTermType());
assertEquals("name", terms.get(0).getColumn());
}
@Test
public void testMultipleAndConditions() {
List<Term> terms = TermExpressionParser.parse("name = test and age > 18 and status = active");
assertEquals(3, terms.size());
assertEquals(TermType.eq, terms.get(0).getTermType());
assertEquals("name", terms.get(0).getColumn());
assertEquals(TermType.gt, terms.get(1).getTermType());
assertEquals("age", terms.get(1).getColumn());
assertEquals(TermType.eq, terms.get(2).getTermType());
assertEquals("status", terms.get(2).getColumn());
}
@Test
public void testMultipleOrConditions() {
List<Term> terms = TermExpressionParser.parse("status = active or status = pending or status = approved");
assertEquals(3, terms.size());
assertEquals(Term.Type.or, terms.get(1).getType());
assertEquals(Term.Type.or, terms.get(2).getType());
}
@Test
public void testNestedMultipleLevels() {
List<Term> terms = TermExpressionParser.parse("age > 18 and (name = test or (status = active and type = user))");
assertEquals(2, terms.size());
assertEquals(TermType.gt, terms.get(0).getTermType());
assertNotNull(terms.get(1).getTerms());
assertEquals(2, terms.get(1).getTerms().size());
}
@Test
public void testSpecialCharactersInValue() {
List<Term> terms = TermExpressionParser.parse("email = user@example.com");
assertEquals(1, terms.size());
assertEquals("email", terms.get(0).getColumn());
assertEquals("user@example.com", terms.get(0).getValue());
}
@Test
public void testNumericValues() {
{
List<Term> terms = TermExpressionParser.parse("price = 99.99");
assertEquals("99.99", terms.get(0).getValue());
}
{
List<Term> terms = TermExpressionParser.parse("count = -10");
assertEquals("-10", terms.get(0).getValue());
}
}
@Test
public void testEmptyStringValue() {
List<Term> terms = TermExpressionParser.parse("description = \"\"");
assertEquals(1, terms.size());
assertEquals("description", terms.get(0).getColumn());
}
@Test
public void testWhitespaceHandling() {
List<Term> terms = TermExpressionParser.parse(" name = test and age > 18 ");
assertEquals(2, terms.size());
assertEquals("name", terms.get(0).getColumn());
assertEquals("test", terms.get(0).getValue());
assertEquals("age", terms.get(1).getColumn());
assertEquals("18", terms.get(1).getValue());
}
@Test
public void testMapWithComplexNestedConditions() {
Map<String, Object> map = new LinkedHashMap<>();
map.put("$nest", "(name = test or name = demo) and age > 18");
List<Term> terms = TermExpressionParser.parse(map);
assertEquals(1, terms.size());
assertNotNull(terms.get(0).getTerms());
assertTrue(terms.get(0).getTerms().size() > 0);
}
@Test
public void testMapWithMultipleTermTypes() {
Map<String, Object> map = new LinkedHashMap<>();
map.put("name$like", "%test%");
map.put("age$gt", "18");
map.put("status$in", "active,pending");
map.put("deletedTime$isnull", "");
List<Term> terms = TermExpressionParser.parse(map);
assertEquals(4, terms.size());
assertEquals("like", terms.get(0).getTermType());
assertEquals("gt", terms.get(1).getTermType());
assertEquals("in", terms.get(2).getTermType());
assertEquals("isnull", terms.get(3).getTermType());
}
@Test
public void testMixedAndOrWithoutParentheses() {
List<Term> terms = TermExpressionParser.parse("name = test and age > 18 or status = active");
assertNotNull(terms);
assertTrue(terms.size() > 0);
}
}