You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by ha...@apache.org on 2025/08/29 15:33:46 UTC

(skywalking-banyandb) 07/12: Enhance trace filtering logic to return collected tag names alongside filters. Update buildFilter and buildTraceFilter functions to support tag name collection for improved error handling and projection tag management. Implement deduplication of tag names to ensure uniqueness in projections.

This is an automated email from the ASF dual-hosted git repository.

hanahmily pushed a commit to branch trace/query
in repository https://gitbox.apache.org/repos/asf/skywalking-banyandb.git

commit 6e82b414db62172581f96d18019ae970ff035621
Author: Gao Hongtao <ha...@gmail.com>
AuthorDate: Fri Aug 29 15:41:42 2025 +0800

    Enhance trace filtering logic to return collected tag names alongside filters. Update buildFilter and buildTraceFilter functions to support tag name collection for improved error handling and projection tag management. Implement deduplication of tag names to ensure uniqueness in projections.
---
 pkg/query/logical/trace/index_filter.go          | 46 +++++++++++--------
 pkg/query/logical/trace/trace_plan_tag_filter.go | 56 ++++++++++++++++++------
 2 files changed, 70 insertions(+), 32 deletions(-)

diff --git a/pkg/query/logical/trace/index_filter.go b/pkg/query/logical/trace/index_filter.go
index 77d1e55c..227e162b 100644
--- a/pkg/query/logical/trace/index_filter.go
+++ b/pkg/query/logical/trace/index_filter.go
@@ -33,34 +33,39 @@ import (
 // Trace creates explicit index rules for skipping index on all tags that don't belong to entity.
 func buildFilter(criteria *modelv1.Criteria, tagNames map[string]bool,
 	entityDict map[string]int, entity []*modelv1.TagValue,
-) (index.Filter, [][]*modelv1.TagValue, error) {
+) (index.Filter, [][]*modelv1.TagValue, []string, error) {
 	if criteria == nil {
-		return nil, [][]*modelv1.TagValue{entity}, nil
+		return nil, [][]*modelv1.TagValue{entity}, nil, nil
 	}
+
+	var collectedTagNames []string
 	switch criteria.GetExp().(type) {
 	case *modelv1.Criteria_Condition:
 		cond := criteria.GetCondition()
+		// Collect the tag name
+		collectedTagNames = append(collectedTagNames, cond.Name)
 		// Check if the tag name exists in the allowed tag names
 		if !tagNames[cond.Name] {
-			return nil, nil, errors.Errorf("tag name '%s' not found in trace schema", cond.Name)
+			return nil, nil, collectedTagNames, errors.Errorf("tag name '%s' not found in trace schema", cond.Name)
 		}
 		_, parsedEntity, err := logical.ParseExprOrEntity(entityDict, entity, cond)
 		if err != nil {
-			return nil, nil, err
+			return nil, nil, collectedTagNames, err
 		}
 		if parsedEntity != nil {
-			return nil, parsedEntity, nil
+			return nil, parsedEntity, collectedTagNames, nil
 		}
 		// For trace, all non-entity tags have skipping index
 		expr, _, err := logical.ParseExprOrEntity(entityDict, entity, cond)
 		if err != nil {
-			return nil, nil, err
+			return nil, nil, collectedTagNames, err
 		}
-		return parseConditionToFilter(cond, entity, expr)
+		filter, entities, err := parseConditionToFilter(cond, entity, expr)
+		return filter, entities, collectedTagNames, err
 	case *modelv1.Criteria_Le:
 		le := criteria.GetLe()
 		if le.GetLeft() == nil && le.GetRight() == nil {
-			return nil, nil, errors.WithMessagef(logical.ErrInvalidLogicalExpression, "both sides(left and right) of [%v] are empty", criteria)
+			return nil, nil, nil, errors.WithMessagef(logical.ErrInvalidLogicalExpression, "both sides(left and right) of [%v] are empty", criteria)
 		}
 		if le.GetLeft() == nil {
 			return buildFilter(le.Right, tagNames, entityDict, entity)
@@ -68,32 +73,37 @@ func buildFilter(criteria *modelv1.Criteria, tagNames map[string]bool,
 		if le.GetRight() == nil {
 			return buildFilter(le.Left, tagNames, entityDict, entity)
 		}
-		left, leftEntities, err := buildFilter(le.Left, tagNames, entityDict, entity)
+		left, leftEntities, leftTagNames, err := buildFilter(le.Left, tagNames, entityDict, entity)
 		if err != nil {
-			return nil, nil, err
+			return nil, nil, leftTagNames, err
 		}
-		right, rightEntities, err := buildFilter(le.Right, tagNames, entityDict, entity)
+		right, rightEntities, rightTagNames, err := buildFilter(le.Right, tagNames, entityDict, entity)
 		if err != nil {
-			return nil, nil, err
+			return nil, nil, append(leftTagNames, rightTagNames...), err
 		}
+
+		// Merge tag names from both sides
+		collectedTagNames = append(collectedTagNames, leftTagNames...)
+		collectedTagNames = append(collectedTagNames, rightTagNames...)
+
 		entities := logical.ParseEntities(le.Op, entity, leftEntities, rightEntities)
 		if entities == nil {
-			return nil, nil, nil
+			return nil, nil, collectedTagNames, nil
 		}
 		if left == nil {
-			return right, entities, nil
+			return right, entities, collectedTagNames, nil
 		}
 		if right == nil {
-			return left, entities, nil
+			return left, entities, collectedTagNames, nil
 		}
 		switch le.Op {
 		case modelv1.LogicalExpression_LOGICAL_OP_AND:
-			return &traceAndFilter{left: left, right: right}, entities, nil
+			return &traceAndFilter{left: left, right: right}, entities, collectedTagNames, nil
 		case modelv1.LogicalExpression_LOGICAL_OP_OR:
-			return &traceOrFilter{left: left, right: right}, entities, nil
+			return &traceOrFilter{left: left, right: right}, entities, collectedTagNames, nil
 		}
 	}
-	return nil, nil, logical.ErrInvalidCriteriaType
+	return nil, nil, nil, logical.ErrInvalidCriteriaType
 }
 
 func parseConditionToFilter(cond *modelv1.Condition, entity []*modelv1.TagValue, expr logical.LiteralExpr) (index.Filter, [][]*modelv1.TagValue, error) {
diff --git a/pkg/query/logical/trace/trace_plan_tag_filter.go b/pkg/query/logical/trace/trace_plan_tag_filter.go
index 9575c792..af1b78d2 100644
--- a/pkg/query/logical/trace/trace_plan_tag_filter.go
+++ b/pkg/query/logical/trace/trace_plan_tag_filter.go
@@ -51,23 +51,38 @@ func (uis *unresolvedTraceTagFilter) Analyze(s logical.Schema) (logical.Plan, er
 		entityDict[e] = idx
 	}
 	var err error
+	var conditionTagNames []string
 	// For trace, we only use skipping filter (no inverted filter or entities)
-	ctx.skippingFilter, err = buildTraceFilter(uis.criteria, s, entityDict)
+	ctx.skippingFilter, conditionTagNames, err = buildTraceFilter(uis.criteria, s, entityDict)
 	if err != nil {
 		return nil, err
 	}
 
+	// Initialize projectionTags even if no explicit projection tags are provided
+	ctx.projectionTags = &model.TagProjection{
+		Family: "", // Empty family name for trace
+		Names:  make([]string, 0),
+	}
+
+	// Add explicitly requested projection tags
 	if len(uis.projectionTags) > 0 {
-		// For trace, we use a single TagProjection since traces don't have tag families
-		ctx.projectionTags = &model.TagProjection{
-			Family: "", // Empty family name for trace
-			Names:  make([]string, 0),
-		}
 		for i := range uis.projectionTags {
 			for _, tag := range uis.projectionTags[i] {
 				ctx.projectionTags.Names = append(ctx.projectionTags.Names, tag.GetTagName())
 			}
 		}
+	}
+
+	// Add tag names from filter conditions to projection
+	if len(conditionTagNames) > 0 {
+		ctx.projectionTags.Names = append(ctx.projectionTags.Names, conditionTagNames...)
+	}
+
+	// Deduplicate tag names
+	ctx.projectionTags.Names = deduplicateStrings(ctx.projectionTags.Names)
+
+	// Create tag references if we have any projection tags
+	if len(ctx.projectionTags.Names) > 0 {
 		var errProject error
 		ctx.projTagsRefs, errProject = s.CreateTagRef(uis.projectionTags...)
 		if errProject != nil {
@@ -116,11 +131,24 @@ func newTraceAnalyzerContext(s logical.Schema) *traceAnalyzeContext {
 	}
 }
 
-// buildTraceFilter builds a filter for trace queries.
+// deduplicateStrings removes duplicate strings from a slice while preserving order.
+func deduplicateStrings(strings []string) []string {
+	seen := make(map[string]bool)
+	var result []string
+	for _, str := range strings {
+		if !seen[str] {
+			seen[str] = true
+			result = append(result, str)
+		}
+	}
+	return result
+}
+
+// buildTraceFilter builds a filter for trace queries and returns both the filter and collected tag names.
 // Unlike stream, trace only needs skipping filter.
-func buildTraceFilter(criteria *modelv1.Criteria, s logical.Schema, entityDict map[string]int) (index.Filter, error) {
+func buildTraceFilter(criteria *modelv1.Criteria, s logical.Schema, entityDict map[string]int) (index.Filter, []string, error) {
 	if criteria == nil {
-		return nil, nil
+		return nil, nil, nil
 	}
 	// Create a map of valid tag names from the schema
 	tagNames := make(map[string]bool)
@@ -129,8 +157,8 @@ func buildTraceFilter(criteria *modelv1.Criteria, s logical.Schema, entityDict m
 		tagNames[tagName] = true
 	}
 
-	filter, _, err := buildFilter(criteria, tagNames, entityDict, nil)
-	return filter, err
+	filter, _, collectedTagNames, err := buildFilter(criteria, tagNames, entityDict, nil)
+	return filter, collectedTagNames, err
 }
 
 var (
@@ -184,7 +212,7 @@ func (t *traceTagFilterPlan) Execute(ctx context.Context) (model.TraceResult, er
 			Name: "",
 			Tags: make([]*modelv1.Tag, 0, len(result.Tags)),
 		}
-		
+
 		// Build the row by taking the value at rowIdx from each tag (if it exists)
 		for _, tag := range result.Tags {
 			if rowIdx < len(tag.Values) {
@@ -194,13 +222,13 @@ func (t *traceTagFilterPlan) Execute(ctx context.Context) (model.TraceResult, er
 				})
 			}
 		}
-		
+
 		tagFamilies := []*modelv1.TagFamily{family}
 		ok, err := t.tagFilter.Match(logical.TagFamilies(tagFamilies), t.s)
 		if err != nil {
 			return model.TraceResult{}, err
 		}
-		
+
 		// If ANY row matches, return the entire result
 		if ok {
 			return result, nil