This is an automated email from the ASF dual-hosted git repository.
zqr10159 pushed a commit to branch 2.0.0
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git
The following commit(s) were added to refs/heads/2.0.0 by this push:
new ada22b22c9 Support trace resource filter IN clauses
ada22b22c9 is described below
commit ada22b22c9143d9af85cce26a5db7fcfd192f8af
Author: Logic <[email protected]>
AuthorDate: Tue Jun 9 20:41:47 2026 +0800
Support trace resource filter IN clauses
---
.../service/impl/EntityTraceQueryServiceImpl.java | 118 ++++++++++++++++++++-
.../impl/EntityTraceQueryServiceImplTest.java | 5 +-
2 files changed, 120 insertions(+), 3 deletions(-)
diff --git
a/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/traces/service/impl/EntityTraceQueryServiceImpl.java
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/traces/service/impl/EntityTraceQueryServiceImpl.java
index 716fbfab39..3f4bbf36bc 100644
---
a/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/traces/service/impl/EntityTraceQueryServiceImpl.java
+++
b/hertzbeat-observability/src/main/java/org/apache/hertzbeat/observability/traces/service/impl/EntityTraceQueryServiceImpl.java
@@ -1059,11 +1059,14 @@ public class EntityTraceQueryServiceImpl implements
EntityTraceQueryService {
return Collections.emptyMap();
}
Map<String, Set<String>> filters = new LinkedHashMap<>();
- for (String clause :
resourceFilter.split("(?i)\\s+and\\s+|\\s*,\\s*")) {
+ for (String clause : splitResourceFilterClauses(resourceFilter)) {
String trimmedClause = trimText(clause);
if (!StringUtils.hasText(trimmedClause)) {
continue;
}
+ if (appendResourceFilterInValues(filters, trimmedClause)) {
+ continue;
+ }
int separatorIndex = resourceFilterSeparatorIndex(trimmedClause);
if (separatorIndex <= 0 || separatorIndex >=
trimmedClause.length() - 1) {
continue;
@@ -1078,6 +1081,119 @@ public class EntityTraceQueryServiceImpl implements
EntityTraceQueryService {
return filters;
}
+ private boolean appendResourceFilterInValues(Map<String, Set<String>>
filters, String clause) {
+ int inIndex = resourceFilterInOperatorIndex(clause);
+ if (inIndex <= 0) {
+ return false;
+ }
+ String key = trimText(clause.substring(0, inIndex));
+ String valueList = trimText(clause.substring(inIndex + 4));
+ if (!isSafeResourceFilterKey(key) || !StringUtils.hasText(valueList)
+ || !valueList.startsWith("(") || !valueList.endsWith(")")) {
+ return false;
+ }
+ for (String value :
splitResourceFilterListValues(valueList.substring(1, valueList.length() - 1))) {
+ String normalizedValue =
stripResourceFilterQuotes(trimText(value));
+ if (StringUtils.hasText(normalizedValue)) {
+ filters.computeIfAbsent(key, ignored -> new
LinkedHashSet<>()).add(normalizedValue);
+ }
+ }
+ return filters.containsKey(key);
+ }
+
+ private int resourceFilterInOperatorIndex(String clause) {
+ String normalized = clause == null ? null :
clause.toLowerCase(Locale.ROOT);
+ if (!StringUtils.hasText(normalized)) {
+ return -1;
+ }
+ int index = normalized.indexOf(" in ");
+ return index < 0 ? -1 : index;
+ }
+
+ private List<String> splitResourceFilterClauses(String resourceFilter) {
+ List<String> clauses = new ArrayList<>();
+ StringBuilder current = new StringBuilder();
+ int depth = 0;
+ char quote = 0;
+ for (int index = 0; index < resourceFilter.length(); index++) {
+ char character = resourceFilter.charAt(index);
+ if (quote != 0) {
+ current.append(character);
+ if (character == quote) {
+ quote = 0;
+ }
+ continue;
+ }
+ if (character == '\'' || character == '"') {
+ quote = character;
+ current.append(character);
+ continue;
+ }
+ if (character == '(') {
+ depth++;
+ current.append(character);
+ continue;
+ }
+ if (character == ')') {
+ depth = Math.max(0, depth - 1);
+ current.append(character);
+ continue;
+ }
+ if (depth == 0 && character == ',') {
+ addResourceFilterClause(clauses, current);
+ continue;
+ }
+ if (depth == 0 && isResourceFilterAndDelimiter(resourceFilter,
index)) {
+ addResourceFilterClause(clauses, current);
+ index += 4;
+ continue;
+ }
+ current.append(character);
+ }
+ addResourceFilterClause(clauses, current);
+ return clauses;
+ }
+
+ private List<String> splitResourceFilterListValues(String values) {
+ List<String> result = new ArrayList<>();
+ StringBuilder current = new StringBuilder();
+ char quote = 0;
+ for (int index = 0; index < values.length(); index++) {
+ char character = values.charAt(index);
+ if (quote != 0) {
+ current.append(character);
+ if (character == quote) {
+ quote = 0;
+ }
+ continue;
+ }
+ if (character == '\'' || character == '"') {
+ quote = character;
+ current.append(character);
+ continue;
+ }
+ if (character == ',') {
+ addResourceFilterClause(result, current);
+ continue;
+ }
+ current.append(character);
+ }
+ addResourceFilterClause(result, current);
+ return result;
+ }
+
+ private void addResourceFilterClause(List<String> clauses, StringBuilder
current) {
+ String clause = trimText(current.toString());
+ if (StringUtils.hasText(clause)) {
+ clauses.add(clause);
+ }
+ current.setLength(0);
+ }
+
+ private boolean isResourceFilterAndDelimiter(String value, int index) {
+ return index + 5 <= value.length() && value.regionMatches(true, index,
" and ", 0, 5);
+ }
+
private int resourceFilterSeparatorIndex(String clause) {
int equalsIndex = clause.indexOf('=');
int colonIndex = clause.indexOf(':');
diff --git
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/traces/service/impl/EntityTraceQueryServiceImplTest.java
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/traces/service/impl/EntityTraceQueryServiceImplTest.java
index be2cec643e..3e9c143ac4 100644
---
a/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/traces/service/impl/EntityTraceQueryServiceImplTest.java
+++
b/hertzbeat-observability/src/test/java/org/apache/hertzbeat/observability/traces/service/impl/EntityTraceQueryServiceImplTest.java
@@ -200,7 +200,7 @@ class EntityTraceQueryServiceImplTest {
"checkout",
"commerce",
"prod",
- "host.name=checkout-1",
+ "host.name IN (\"checkout-1\", 'checkout-2') and
k8s.namespace.name:commerce",
"GET /checkout",
100L,
500L,
@@ -239,7 +239,8 @@ class EntityTraceQueryServiceImplTest {
eq("latency-p95-desc"),
eq(5L),
eq(7));
- assertEquals(Set.of("checkout-1"),
filterCaptor.getValue().get("host.name"));
+ assertEquals(Set.of("checkout-1", "checkout-2"),
filterCaptor.getValue().get("host.name"));
+ assertEquals(Set.of("commerce"),
filterCaptor.getValue().get("k8s.namespace.name"));
}
@Test
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]