This is an automated email from the ASF dual-hosted git repository. hanahmily pushed a commit to branch sidx/query in repository https://gitbox.apache.org/repos/asf/skywalking-banyandb.git
commit 30a037098e4adb20cc3f5384e907eeb43d1671a8 Author: Gao Hongtao <[email protected]> AuthorDate: Thu Aug 28 07:40:04 2025 +0800 Refactor tag structure and update field names: Change tag field names to use exported formats (e.g., name to Name) for consistency across the codebase. Introduce a new Tag struct with methods for creation, resetting, and copying. Update related tests to reflect these changes, ensuring proper functionality and clarity in tag handling. --- banyand/internal/sidx/element.go | 8 +- banyand/internal/sidx/interfaces.go | 82 +++- banyand/internal/sidx/query_result.go | 12 +- banyand/internal/sidx/query_result_test.go | 114 +++--- banyand/internal/sidx/sidx_test.go | 10 +- banyand/internal/sidx/snapshot_test.go | 20 +- banyand/internal/sidx/tag_test.go | 635 ++++------------------------- 7 files changed, 226 insertions(+), 655 deletions(-) diff --git a/banyand/internal/sidx/element.go b/banyand/internal/sidx/element.go index 7985b575..9ca5e253 100644 --- a/banyand/internal/sidx/element.go +++ b/banyand/internal/sidx/element.go @@ -167,10 +167,10 @@ func (e *elements) mustAppend(seriesID common.SeriesID, userKey int64, data []by elementTags := make([]*tag, 0, len(tags)) for _, t := range tags { newTag := generateTag() - newTag.name = t.name - newTag.value = append([]byte(nil), t.value...) - newTag.valueType = t.valueType - newTag.indexed = t.indexed + newTag.name = t.Name + newTag.value = append([]byte(nil), t.Value...) + newTag.valueType = t.ValueType + newTag.indexed = t.Indexed elementTags = append(elementTags, newTag) } e.tags = append(e.tags, elementTags) diff --git a/banyand/internal/sidx/interfaces.go b/banyand/internal/sidx/interfaces.go index e9970141..91b2bb61 100644 --- a/banyand/internal/sidx/interfaces.go +++ b/banyand/internal/sidx/interfaces.go @@ -26,6 +26,7 @@ import ( "github.com/apache/skywalking-banyandb/api/common" "github.com/apache/skywalking-banyandb/pkg/index" + pbv1 "github.com/apache/skywalking-banyandb/pkg/pb/v1" "github.com/apache/skywalking-banyandb/pkg/query/model" ) @@ -190,7 +191,7 @@ func (qr *QueryResponse) Validate() error { } for i, tagGroup := range qr.Tags { for j, tag := range tagGroup { - if tag.name == "" { + if tag.Name == "" { return fmt.Errorf("tags[%d][%d] name cannot be empty", i, j) } } @@ -231,10 +232,10 @@ func (qr *QueryResponse) CopyFrom(other *QueryResponse) { qr.Tags[i] = qr.Tags[i][:len(tagGroup)] } for j, tag := range tagGroup { - qr.Tags[i][j].name = tag.name - qr.Tags[i][j].value = append(qr.Tags[i][j].value[:0], tag.value...) - qr.Tags[i][j].valueType = tag.valueType - qr.Tags[i][j].indexed = tag.indexed + qr.Tags[i][j].Name = tag.Name + qr.Tags[i][j].Value = append(qr.Tags[i][j].Value[:0], tag.Value...) + qr.Tags[i][j].ValueType = tag.ValueType + qr.Tags[i][j].Indexed = tag.Indexed } } @@ -308,8 +309,71 @@ func (rm *ResponseMetadata) Validate() error { } // Tag represents an individual tag for WriteRequest. -// This uses the existing tag structure from the sidx package. -type Tag = tag +// This is an exported type that can be used outside the package. +type Tag struct { + Name string + Value []byte + ValueType pbv1.ValueType + Indexed bool +} + +// NewTag creates a new Tag instance with the given values. +func NewTag(name string, value []byte, valueType pbv1.ValueType, indexed bool) Tag { + return Tag{ + Name: name, + Value: value, + ValueType: valueType, + Indexed: indexed, + } +} + +// Reset resets the Tag to its zero state for reuse. +func (t *Tag) Reset() { + t.Name = "" + t.Value = nil + t.ValueType = pbv1.ValueTypeUnknown + t.Indexed = false +} + +// Size returns the size of the tag in bytes. +func (t *Tag) Size() int { + return len(t.Name) + len(t.Value) + 1 // +1 for valueType +} + +// Copy creates a deep copy of the Tag. +func (t *Tag) Copy() Tag { + var valueCopy []byte + if t.Value != nil { + valueCopy = make([]byte, len(t.Value)) + copy(valueCopy, t.Value) + } + return Tag{ + Name: t.Name, + Value: valueCopy, + ValueType: t.ValueType, + Indexed: t.Indexed, + } +} + +// toInternalTag converts the exported Tag to an internal tag for use with the pooling system. +func (t *Tag) toInternalTag() *tag { + return &tag{ + name: t.Name, + value: t.Value, + valueType: t.ValueType, + indexed: t.Indexed, + } +} + +// fromInternalTag creates a Tag from an internal tag. +func fromInternalTag(t *tag) Tag { + return Tag{ + Name: t.name, + Value: t.value, + ValueType: t.valueType, + Indexed: t.indexed, + } +} // Validate validates a WriteRequest for correctness. func (wr WriteRequest) Validate() error { @@ -324,10 +388,10 @@ func (wr WriteRequest) Validate() error { } // Validate tags if present for i, tag := range wr.Tags { - if tag.name == "" { + if tag.Name == "" { return fmt.Errorf("tag[%d] name cannot be empty", i) } - if len(tag.value) == 0 { + if len(tag.Value) == 0 { return fmt.Errorf("tag[%d] value cannot be empty", i) } } diff --git a/banyand/internal/sidx/query_result.go b/banyand/internal/sidx/query_result.go index 4f8b3b6e..87154640 100644 --- a/banyand/internal/sidx/query_result.go +++ b/banyand/internal/sidx/query_result.go @@ -376,9 +376,9 @@ func (qr *queryResult) extractElementTags(block *block, elemIndex int) []Tag { for _, tagName := range proj.Names { if tagData, exists := block.tags[tagName]; exists && elemIndex < len(tagData.values) { elementTags = append(elementTags, Tag{ - name: tagName, - value: tagData.values[elemIndex], - valueType: tagData.valueType, + Name: tagName, + Value: tagData.values[elemIndex], + ValueType: tagData.valueType, }) } } @@ -389,9 +389,9 @@ func (qr *queryResult) extractElementTags(block *block, elemIndex int) []Tag { for tagName, tagData := range block.tags { if elemIndex < len(tagData.values) { elementTags = append(elementTags, Tag{ - name: tagName, - value: tagData.values[elemIndex], - valueType: tagData.valueType, + Name: tagName, + Value: tagData.values[elemIndex], + ValueType: tagData.valueType, }) } } diff --git a/banyand/internal/sidx/query_result_test.go b/banyand/internal/sidx/query_result_test.go index 357d733f..7c8e1ab0 100644 --- a/banyand/internal/sidx/query_result_test.go +++ b/banyand/internal/sidx/query_result_test.go @@ -154,9 +154,9 @@ func TestQueryResponseHeap_MergeWithHeapAscending(t *testing.T) { Keys: []int64{1, 5, 9}, Data: [][]byte{[]byte("a1"), []byte("a5"), []byte("a9")}, Tags: [][]Tag{ - {{name: "tag1", value: []byte("val1"), valueType: pbv1.ValueTypeStr}}, - {{name: "tag1", value: []byte("val5"), valueType: pbv1.ValueTypeStr}}, - {{name: "tag1", value: []byte("val9"), valueType: pbv1.ValueTypeStr}}, + {{Name: "tag1", Value: []byte("val1"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "tag1", Value: []byte("val5"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "tag1", Value: []byte("val9"), ValueType: pbv1.ValueTypeStr}}, }, SIDs: []common.SeriesID{1, 1, 1}, } @@ -164,9 +164,9 @@ func TestQueryResponseHeap_MergeWithHeapAscending(t *testing.T) { Keys: []int64{2, 6, 10}, Data: [][]byte{[]byte("b2"), []byte("b6"), []byte("b10")}, Tags: [][]Tag{ - {{name: "tag2", value: []byte("val2"), valueType: pbv1.ValueTypeStr}}, - {{name: "tag2", value: []byte("val6"), valueType: pbv1.ValueTypeStr}}, - {{name: "tag2", value: []byte("val10"), valueType: pbv1.ValueTypeStr}}, + {{Name: "tag2", Value: []byte("val2"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "tag2", Value: []byte("val6"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "tag2", Value: []byte("val10"), ValueType: pbv1.ValueTypeStr}}, }, SIDs: []common.SeriesID{2, 2, 2}, } @@ -174,8 +174,8 @@ func TestQueryResponseHeap_MergeWithHeapAscending(t *testing.T) { Keys: []int64{3, 7}, Data: [][]byte{[]byte("c3"), []byte("c7")}, Tags: [][]Tag{ - {{name: "tag3", value: []byte("val3"), valueType: pbv1.ValueTypeStr}}, - {{name: "tag3", value: []byte("val7"), valueType: pbv1.ValueTypeStr}}, + {{Name: "tag3", Value: []byte("val3"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "tag3", Value: []byte("val7"), ValueType: pbv1.ValueTypeStr}}, }, SIDs: []common.SeriesID{3, 3}, } @@ -230,9 +230,9 @@ func TestQueryResponseHeap_MergeWithHeapDescending(t *testing.T) { Keys: []int64{1, 5, 9}, // Will be accessed backwards: 9, 5, 1 Data: [][]byte{[]byte("a1"), []byte("a5"), []byte("a9")}, Tags: [][]Tag{ - {{name: "tag1", value: []byte("val1"), valueType: pbv1.ValueTypeStr}}, - {{name: "tag1", value: []byte("val5"), valueType: pbv1.ValueTypeStr}}, - {{name: "tag1", value: []byte("val9"), valueType: pbv1.ValueTypeStr}}, + {{Name: "tag1", Value: []byte("val1"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "tag1", Value: []byte("val5"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "tag1", Value: []byte("val9"), ValueType: pbv1.ValueTypeStr}}, }, SIDs: []common.SeriesID{1, 1, 1}, } @@ -240,9 +240,9 @@ func TestQueryResponseHeap_MergeWithHeapDescending(t *testing.T) { Keys: []int64{2, 6, 10}, // Will be accessed backwards: 10, 6, 2 Data: [][]byte{[]byte("b2"), []byte("b6"), []byte("b10")}, Tags: [][]Tag{ - {{name: "tag2", value: []byte("val2"), valueType: pbv1.ValueTypeStr}}, - {{name: "tag2", value: []byte("val6"), valueType: pbv1.ValueTypeStr}}, - {{name: "tag2", value: []byte("val10"), valueType: pbv1.ValueTypeStr}}, + {{Name: "tag2", Value: []byte("val2"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "tag2", Value: []byte("val6"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "tag2", Value: []byte("val10"), ValueType: pbv1.ValueTypeStr}}, }, SIDs: []common.SeriesID{2, 2, 2}, } @@ -292,9 +292,9 @@ func TestQueryResponseHeap_MergeWithLimit(t *testing.T) { Keys: []int64{1, 3, 5}, Data: [][]byte{[]byte("a1"), []byte("a3"), []byte("a5")}, Tags: [][]Tag{ - {{name: "tag1", value: []byte("val1"), valueType: pbv1.ValueTypeStr}}, - {{name: "tag1", value: []byte("val3"), valueType: pbv1.ValueTypeStr}}, - {{name: "tag1", value: []byte("val5"), valueType: pbv1.ValueTypeStr}}, + {{Name: "tag1", Value: []byte("val1"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "tag1", Value: []byte("val3"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "tag1", Value: []byte("val5"), ValueType: pbv1.ValueTypeStr}}, }, SIDs: []common.SeriesID{1, 1, 1}, } @@ -302,9 +302,9 @@ func TestQueryResponseHeap_MergeWithLimit(t *testing.T) { Keys: []int64{2, 4, 6}, Data: [][]byte{[]byte("b2"), []byte("b4"), []byte("b6")}, Tags: [][]Tag{ - {{name: "tag2", value: []byte("val2"), valueType: pbv1.ValueTypeStr}}, - {{name: "tag2", value: []byte("val4"), valueType: pbv1.ValueTypeStr}}, - {{name: "tag2", value: []byte("val6"), valueType: pbv1.ValueTypeStr}}, + {{Name: "tag2", Value: []byte("val2"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "tag2", Value: []byte("val4"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "tag2", Value: []byte("val6"), ValueType: pbv1.ValueTypeStr}}, }, SIDs: []common.SeriesID{2, 2, 2}, } @@ -345,7 +345,7 @@ func TestQueryResponseHeap_EdgeCases(t *testing.T) { response := &QueryResponse{ Keys: []int64{42}, Data: [][]byte{[]byte("single")}, - Tags: [][]Tag{{{name: "tag", value: []byte("value"), valueType: pbv1.ValueTypeStr}}}, + Tags: [][]Tag{{{Name: "tag", Value: []byte("value"), ValueType: pbv1.ValueTypeStr}}}, SIDs: []common.SeriesID{1}, } @@ -371,9 +371,9 @@ func TestQueryResponseHeap_EdgeCases(t *testing.T) { Keys: []int64{1, 2, 3}, Data: [][]byte{[]byte("a"), []byte("b"), []byte("c")}, Tags: [][]Tag{ - {{name: "tag", value: []byte("val1"), valueType: pbv1.ValueTypeStr}}, - {{name: "tag", value: []byte("val2"), valueType: pbv1.ValueTypeStr}}, - {{name: "tag", value: []byte("val3"), valueType: pbv1.ValueTypeStr}}, + {{Name: "tag", Value: []byte("val1"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "tag", Value: []byte("val2"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "tag", Value: []byte("val3"), ValueType: pbv1.ValueTypeStr}}, }, SIDs: []common.SeriesID{1, 1, 1}, } @@ -398,7 +398,7 @@ func TestQueryResponseHeap_EdgeCases(t *testing.T) { normalResponse := &QueryResponse{ Keys: []int64{5}, Data: [][]byte{[]byte("normal")}, - Tags: [][]Tag{{{name: "tag", value: []byte("value"), valueType: pbv1.ValueTypeStr}}}, + Tags: [][]Tag{{{Name: "tag", Value: []byte("value"), ValueType: pbv1.ValueTypeStr}}}, SIDs: []common.SeriesID{1}, } @@ -425,9 +425,9 @@ func TestMergeQueryResponseShardsAsc(t *testing.T) { Keys: []int64{1, 5, 9}, Data: [][]byte{[]byte("s1_1"), []byte("s1_5"), []byte("s1_9")}, Tags: [][]Tag{ - {{name: "shard1", value: []byte("val1"), valueType: pbv1.ValueTypeStr}}, - {{name: "shard1", value: []byte("val5"), valueType: pbv1.ValueTypeStr}}, - {{name: "shard1", value: []byte("val9"), valueType: pbv1.ValueTypeStr}}, + {{Name: "shard1", Value: []byte("val1"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "shard1", Value: []byte("val5"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "shard1", Value: []byte("val9"), ValueType: pbv1.ValueTypeStr}}, }, SIDs: []common.SeriesID{1, 1, 1}, } @@ -435,9 +435,9 @@ func TestMergeQueryResponseShardsAsc(t *testing.T) { Keys: []int64{2, 6, 10}, Data: [][]byte{[]byte("s2_2"), []byte("s2_6"), []byte("s2_10")}, Tags: [][]Tag{ - {{name: "shard2", value: []byte("val2"), valueType: pbv1.ValueTypeStr}}, - {{name: "shard2", value: []byte("val6"), valueType: pbv1.ValueTypeStr}}, - {{name: "shard2", value: []byte("val10"), valueType: pbv1.ValueTypeStr}}, + {{Name: "shard2", Value: []byte("val2"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "shard2", Value: []byte("val6"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "shard2", Value: []byte("val10"), ValueType: pbv1.ValueTypeStr}}, }, SIDs: []common.SeriesID{2, 2, 2}, } @@ -445,8 +445,8 @@ func TestMergeQueryResponseShardsAsc(t *testing.T) { Keys: []int64{3, 7}, Data: [][]byte{[]byte("s3_3"), []byte("s3_7")}, Tags: [][]Tag{ - {{name: "shard3", value: []byte("val3"), valueType: pbv1.ValueTypeStr}}, - {{name: "shard3", value: []byte("val7"), valueType: pbv1.ValueTypeStr}}, + {{Name: "shard3", Value: []byte("val3"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "shard3", Value: []byte("val7"), ValueType: pbv1.ValueTypeStr}}, }, SIDs: []common.SeriesID{3, 3}, } @@ -472,9 +472,9 @@ func TestMergeQueryResponseShardsDesc(t *testing.T) { Keys: []int64{9, 5, 1}, Data: [][]byte{[]byte("s1_9"), []byte("s1_5"), []byte("s1_1")}, Tags: [][]Tag{ - {{name: "shard1", value: []byte("val9"), valueType: pbv1.ValueTypeStr}}, - {{name: "shard1", value: []byte("val5"), valueType: pbv1.ValueTypeStr}}, - {{name: "shard1", value: []byte("val1"), valueType: pbv1.ValueTypeStr}}, + {{Name: "shard1", Value: []byte("val9"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "shard1", Value: []byte("val5"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "shard1", Value: []byte("val1"), ValueType: pbv1.ValueTypeStr}}, }, SIDs: []common.SeriesID{1, 1, 1}, } @@ -482,9 +482,9 @@ func TestMergeQueryResponseShardsDesc(t *testing.T) { Keys: []int64{10, 6, 2}, Data: [][]byte{[]byte("s2_10"), []byte("s2_6"), []byte("s2_2")}, Tags: [][]Tag{ - {{name: "shard2", value: []byte("val10"), valueType: pbv1.ValueTypeStr}}, - {{name: "shard2", value: []byte("val6"), valueType: pbv1.ValueTypeStr}}, - {{name: "shard2", value: []byte("val2"), valueType: pbv1.ValueTypeStr}}, + {{Name: "shard2", Value: []byte("val10"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "shard2", Value: []byte("val6"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "shard2", Value: []byte("val2"), ValueType: pbv1.ValueTypeStr}}, }, SIDs: []common.SeriesID{2, 2, 2}, } @@ -539,8 +539,8 @@ func TestMergeQueryResponseShards_EmptyShards(t *testing.T) { Keys: []int64{5, 10}, Data: [][]byte{[]byte("a"), []byte("b")}, Tags: [][]Tag{ - {{name: "mixed", value: []byte("val5"), valueType: pbv1.ValueTypeStr}}, - {{name: "mixed", value: []byte("val10"), valueType: pbv1.ValueTypeStr}}, + {{Name: "mixed", Value: []byte("val5"), ValueType: pbv1.ValueTypeStr}}, + {{Name: "mixed", Value: []byte("val10"), ValueType: pbv1.ValueTypeStr}}, }, SIDs: []common.SeriesID{1, 1}, }, @@ -619,7 +619,7 @@ func createBenchmarkResponse(size int, offset, step int64) *QueryResponse { key := offset + int64(i)*step response.Keys[i] = key response.Data[i] = []byte("benchmark_data") - response.Tags[i] = []Tag{{name: "benchmark", value: []byte("value"), valueType: pbv1.ValueTypeStr}} + response.Tags[i] = []Tag{{Name: "benchmark", Value: []byte("value"), ValueType: pbv1.ValueTypeStr}} response.SIDs[i] = common.SeriesID(offset) } @@ -638,7 +638,7 @@ func createBenchmarkResponseDesc(size int, offset, step int64) *QueryResponse { key := offset + int64(size-1-i)*step response.Keys[i] = key response.Data[i] = []byte("benchmark_data") - response.Tags[i] = []Tag{{name: "benchmark", value: []byte("value"), valueType: pbv1.ValueTypeStr}} + response.Tags[i] = []Tag{{Name: "benchmark", Value: []byte("value"), ValueType: pbv1.ValueTypeStr}} response.SIDs[i] = common.SeriesID(offset) } @@ -802,14 +802,14 @@ func TestQueryResult_Pull_SingleMemPart(t *testing.T) { for j, tag := range tags { expectedTag := expected.tags[j] - if tag.name != expectedTag.name { - t.Errorf("At position %d, tag %d: expected name %s, got %s", i, j, expectedTag.name, tag.name) + if tag.Name != expectedTag.name { + t.Errorf("At position %d, tag %d: expected name %s, got %s", i, j, expectedTag.name, tag.Name) } - if !bytes.Equal(tag.value, expectedTag.value) { - t.Errorf("At position %d, tag %d: expected value %s, got %s", i, j, string(expectedTag.value), string(tag.value)) + if !bytes.Equal(tag.Value, expectedTag.value) { + t.Errorf("At position %d, tag %d: expected value %s, got %s", i, j, string(expectedTag.value), string(tag.Value)) } - if tag.valueType != expectedTag.valueType { - t.Errorf("At position %d, tag %d: expected valueType %v, got %v", i, j, expectedTag.valueType, tag.valueType) + if tag.ValueType != expectedTag.valueType { + t.Errorf("At position %d, tag %d: expected valueType %v, got %v", i, j, expectedTag.valueType, tag.ValueType) } } } @@ -968,14 +968,14 @@ func TestQueryResult_Pull_MultipleMemParts(t *testing.T) { for j, tag := range tags { expectedTag := expected.tags[j] - if tag.name != expectedTag.name { - t.Errorf("At position %d, tag %d: expected name %s, got %s", i, j, expectedTag.name, tag.name) + if tag.Name != expectedTag.name { + t.Errorf("At position %d, tag %d: expected name %s, got %s", i, j, expectedTag.name, tag.Name) } - if !bytes.Equal(tag.value, expectedTag.value) { - t.Errorf("At position %d, tag %d: expected value %s, got %s", i, j, string(expectedTag.value), string(tag.value)) + if !bytes.Equal(tag.Value, expectedTag.value) { + t.Errorf("At position %d, tag %d: expected value %s, got %s", i, j, string(expectedTag.value), string(tag.Value)) } - if tag.valueType != expectedTag.valueType { - t.Errorf("At position %d, tag %d: expected valueType %v, got %v", i, j, expectedTag.valueType, tag.valueType) + if tag.ValueType != expectedTag.valueType { + t.Errorf("At position %d, tag %d: expected valueType %v, got %v", i, j, expectedTag.valueType, tag.ValueType) } } } @@ -1088,8 +1088,8 @@ func TestQueryResult_Pull_WithTagProjection(t *testing.T) { // Note: The actual tag filtering logic needs to be verified based on implementation for i, tagGroup := range response.Tags { for _, tag := range tagGroup { - if tag.name != "service" && tag.name != "environment" { - t.Errorf("Unexpected tag '%s' found at position %d, should only have projected tags", tag.name, i) + if tag.Name != "service" && tag.Name != "environment" { + t.Errorf("Unexpected tag '%s' found at position %d, should only have projected tags", tag.Name, i) } } } diff --git a/banyand/internal/sidx/sidx_test.go b/banyand/internal/sidx/sidx_test.go index ef9324e4..7741a1dd 100644 --- a/banyand/internal/sidx/sidx_test.go +++ b/banyand/internal/sidx/sidx_test.go @@ -59,11 +59,11 @@ func createTestSIDX(t *testing.T) SIDX { } func createTestTag(name, value string) Tag { - return tag{ - name: name, - value: []byte(value), - valueType: pbv1.ValueTypeStr, - indexed: true, + return Tag{ + Name: name, + Value: []byte(value), + ValueType: pbv1.ValueTypeStr, + Indexed: true, } } diff --git a/banyand/internal/sidx/snapshot_test.go b/banyand/internal/sidx/snapshot_test.go index efea64d5..bcdfd1fe 100644 --- a/banyand/internal/sidx/snapshot_test.go +++ b/banyand/internal/sidx/snapshot_test.go @@ -425,7 +425,7 @@ func TestSnapshotReplacement_Basic(t *testing.T) { SeriesID: common.SeriesID(i + 1), Key: int64(i), Data: []byte(fmt.Sprintf("test-data-%d", i)), - Tags: []Tag{{name: "test", value: []byte("snapshot-replacement")}}, + Tags: []Tag{{Name: "test", Value: []byte("snapshot-replacement")}}, } if err := sidx.Write(ctx, []WriteRequest{req}); err != nil { @@ -512,8 +512,8 @@ func TestSnapshotReplacement_ConcurrentReadsConsistentData(t *testing.T) { Key: int64(1000 + i), Data: []byte(fmt.Sprintf("replacement-data-%d", i)), Tags: []Tag{ - {name: "test", value: []byte("replacement")}, - {name: "sequence", value: []byte(fmt.Sprintf("%d", i))}, + {Name: "test", Value: []byte("replacement")}, + {Name: "sequence", Value: []byte(fmt.Sprintf("%d", i))}, }, }, } @@ -601,8 +601,8 @@ func TestSnapshotReplacement_NoDataRacesDuringReplacement(t *testing.T) { Key: int64(id*1000 + j), Data: []byte(fmt.Sprintf("race-test-%d-%d", id, j)), Tags: []Tag{ - {name: "goroutine", value: []byte(fmt.Sprintf("%d", id))}, - {name: "operation", value: []byte(fmt.Sprintf("%d", j))}, + {Name: "goroutine", Value: []byte(fmt.Sprintf("%d", id))}, + {Name: "operation", Value: []byte(fmt.Sprintf("%d", j))}, }, }, } @@ -658,9 +658,9 @@ func TestSnapshotReplacement_MemoryLeaksPrevention(t *testing.T) { Key: int64(i*100 + j), Data: []byte(fmt.Sprintf("leak-test-batch-%d-write-%d", i, j)), Tags: []Tag{ - {name: "batch", value: []byte(fmt.Sprintf("%d", i))}, - {name: "write", value: []byte(fmt.Sprintf("%d", j))}, - {name: "test", value: []byte("memory-leak-prevention")}, + {Name: "batch", Value: []byte(fmt.Sprintf("%d", i))}, + {Name: "write", Value: []byte(fmt.Sprintf("%d", j))}, + {Name: "test", Value: []byte("memory-leak-prevention")}, }, }, } @@ -720,8 +720,8 @@ func TestSnapshotReplacement_MemoryLeaksPrevention(t *testing.T) { Key: int64(writerID*1000 + j + 5000), Data: []byte(fmt.Sprintf("concurrent-leak-test-%d-%d", writerID, j)), Tags: []Tag{ - {name: "writer", value: []byte(fmt.Sprintf("%d", writerID))}, - {name: "concurrent", value: []byte("true")}, + {Name: "writer", Value: []byte(fmt.Sprintf("%d", writerID))}, + {Name: "concurrent", Value: []byte("true")}, }, }, } diff --git a/banyand/internal/sidx/tag_test.go b/banyand/internal/sidx/tag_test.go index 4104459e..9bc500bf 100644 --- a/banyand/internal/sidx/tag_test.go +++ b/banyand/internal/sidx/tag_test.go @@ -18,597 +18,104 @@ package sidx import ( - "bytes" - "encoding/binary" "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/apache/skywalking-banyandb/pkg/filter" pbv1 "github.com/apache/skywalking-banyandb/pkg/pb/v1" ) -const testTagName = "test_tag" - -func TestTagValueMarshaling(t *testing.T) { - tests := []struct { - name string - values [][]byte - want bool // whether marshaling should succeed - }{ - { - name: "empty values", - values: [][]byte{}, - want: true, - }, - { - name: "single value", - values: [][]byte{[]byte("test")}, - want: true, - }, - { - name: "multiple values", - values: [][]byte{ - []byte("value1"), - []byte("value2"), - []byte("value3"), - }, - want: true, - }, - { - name: "values with different lengths", - values: [][]byte{ - []byte("a"), - []byte("longer_value"), - []byte(""), - []byte("medium"), - }, - want: true, - }, +func TestTagExportedFields(t *testing.T) { + // Test that Tag can be created and used outside the package + tag := Tag{ + Name: "test-tag", + Value: []byte("test-value"), + ValueType: pbv1.ValueTypeStr, + Indexed: true, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Marshal values using shared encoding (default to string type for basic marshaling test) - data, err := encodeTagValues(tt.values, pbv1.ValueTypeStr) - if tt.want { - require.NoError(t, err) - } else { - require.Error(t, err) - return - } - - // Unmarshal and verify - unmarshaled, err := decodeTagValues(data, pbv1.ValueTypeStr, len(tt.values)) - require.NoError(t, err) - assert.Equal(t, len(tt.values), len(unmarshaled)) - - for i, expected := range tt.values { - switch { - case expected == nil: - assert.Nil(t, unmarshaled[i]) - case len(expected) == 0: - // Handle empty byte slices - encoding may return nil for empty values - assert.True(t, len(unmarshaled[i]) == 0, "Expected empty value at index %d", i) - default: - assert.Equal(t, expected, unmarshaled[i]) - } - } - }) - } + // Test that exported fields are accessible + assert.Equal(t, "test-tag", tag.Name) + assert.Equal(t, []byte("test-value"), tag.Value) + assert.Equal(t, pbv1.ValueTypeStr, tag.ValueType) + assert.Equal(t, true, tag.Indexed) } -func TestTagValueEncoding(t *testing.T) { - tests := []struct { - name string - values [][]byte - valueType pbv1.ValueType - wantErr bool - }{ - { - name: "int64 values", - values: [][]byte{{0x39, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, // int64(12345) encoded - valueType: pbv1.ValueTypeInt64, - wantErr: false, - }, - { - name: "string values", - values: [][]byte{[]byte("test string"), []byte("another string")}, - valueType: pbv1.ValueTypeStr, - wantErr: false, - }, - { - name: "binary data", - values: [][]byte{{0x01, 0x02, 0x03, 0x04}, {0xFF, 0xFE}}, - valueType: pbv1.ValueTypeBinaryData, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - encoded, err := encodeTagValues(tt.values, tt.valueType) - if tt.wantErr { - assert.Error(t, err) - return - } +func TestNewTag(t *testing.T) { + // Test the NewTag constructor function + tag := NewTag("service", []byte("order-service"), pbv1.ValueTypeStr, true) - require.NoError(t, err) - assert.NotNil(t, encoded) - - // Decode and verify round-trip - decoded, err := decodeTagValues(encoded, tt.valueType, len(tt.values)) - require.NoError(t, err) - assert.Equal(t, len(tt.values), len(decoded)) - for i, expected := range tt.values { - assert.Equal(t, expected, decoded[i]) - } - }) - } + assert.Equal(t, "service", tag.Name) + assert.Equal(t, []byte("order-service"), tag.Value) + assert.Equal(t, pbv1.ValueTypeStr, tag.ValueType) + assert.Equal(t, true, tag.Indexed) } -func TestTagFilterGeneration(t *testing.T) { - tests := []struct { - name string - values [][]byte - expectedElements int - wantNil bool - }{ - { - name: "empty values", - values: [][]byte{}, - expectedElements: 10, - wantNil: true, - }, - { - name: "single value", - values: [][]byte{ - []byte("value1"), - }, - expectedElements: 10, - wantNil: false, - }, - { - name: "multiple values", - values: [][]byte{ - []byte("value1"), - []byte("value2"), - []byte("value3"), - }, - expectedElements: 10, - wantNil: false, - }, +func TestTagReset(t *testing.T) { + tag := Tag{ + Name: "test-tag", + Value: []byte("test-value"), + ValueType: pbv1.ValueTypeStr, + Indexed: true, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - filter := generateTagFilter(tt.values, tt.expectedElements) - if tt.wantNil { - assert.Nil(t, filter) - return - } - - require.NotNil(t, filter) - - // Test that all added values might be contained - for _, value := range tt.values { - assert.True(t, filter.MightContain(value)) - } - - // Test that a non-added value might not be contained - // (this could return true due to false positives, but we test anyway) - nonExistent := []byte("non_existent_value_12345") - // We can't assert false here due to possible false positives - filter.MightContain(nonExistent) - }) - } -} - -func TestTagDataOperations(t *testing.T) { - t.Run("add and check values", func(t *testing.T) { - td := generateTagData() - defer releaseTagData(td) - - td.name = testTagName - td.valueType = pbv1.ValueTypeStr - td.indexed = true - td.filter = generateTagFilter([][]byte{}, 10) // Start with empty filter - - // Add values - values := [][]byte{ - []byte("value1"), - []byte("value2"), - []byte("value3"), - } - - for _, value := range values { - td.addValue(value) - } - - // Check that all values are present - for _, value := range values { - assert.True(t, td.hasValue(value)) - } - - // Check that a non-existent value might not be present - assert.False(t, td.hasValue([]byte("non_existent"))) - }) - - t.Run("update min max for int64", func(t *testing.T) { - td := generateTagData() - defer releaseTagData(td) - - td.name = "int_tag" - td.valueType = pbv1.ValueTypeInt64 - - // Add int64 values (encoded as bytes) - values := []int64{100, -50, 200, 0, 150} - for _, v := range values { - encoded := make([]byte, 8) - binary.LittleEndian.PutUint64(encoded, uint64(v)) - td.values = append(td.values, encoded) - } - - td.updateMinMax() - - // Verify min and max - assert.NotNil(t, td.min) - assert.NotNil(t, td.max) - - // Decode and verify values - minVal := int64(binary.LittleEndian.Uint64(td.min)) - assert.Equal(t, int64(-50), minVal) - - maxVal := int64(binary.LittleEndian.Uint64(td.max)) - assert.Equal(t, int64(200), maxVal) - }) - - t.Run("reset functionality", func(t *testing.T) { - td := generateTagData() - - // Set up tag data - td.name = testTagName - td.valueType = pbv1.ValueTypeStr - td.indexed = true - td.values = [][]byte{[]byte("value1"), []byte("value2")} - td.filter = generateTagFilter(td.values, 10) - td.min = []byte("min") - td.max = []byte("max") - - // Reset - td.reset() - - // Verify reset - assert.Equal(t, "", td.name) - assert.Equal(t, pbv1.ValueTypeUnknown, td.valueType) - assert.False(t, td.indexed) - assert.Equal(t, 0, len(td.values)) - assert.Nil(t, td.filter) - assert.Nil(t, td.min) - assert.Nil(t, td.max) - - releaseTagData(td) - }) -} - -func TestTagMetadataOperations(t *testing.T) { - t.Run("marshal and unmarshal", func(t *testing.T) { - original := generateTagMetadata() - defer releaseTagMetadata(original) - - // Set up metadata - original.name = "test_tag" - original.valueType = pbv1.ValueTypeInt64 - original.indexed = true - original.dataBlock = dataBlock{offset: 100, size: 500} - original.filterBlock = dataBlock{offset: 600, size: 200} - original.min = []byte{0x01, 0x02} - original.max = []byte{0xFF, 0xFE} - - // Marshal - data := original.marshal(nil) - assert.NotNil(t, data) - - // Unmarshal - unmarshaled, err := unmarshalTagMetadata(data) - require.NoError(t, err) - defer releaseTagMetadata(unmarshaled) - - // Verify fields - assert.Equal(t, original.name, unmarshaled.name) - assert.Equal(t, original.valueType, unmarshaled.valueType) - assert.Equal(t, original.indexed, unmarshaled.indexed) - assert.Equal(t, original.dataBlock, unmarshaled.dataBlock) - assert.Equal(t, original.filterBlock, unmarshaled.filterBlock) - assert.Equal(t, original.min, unmarshaled.min) - assert.Equal(t, original.max, unmarshaled.max) - }) - - t.Run("marshal empty metadata", func(t *testing.T) { - tm := generateTagMetadata() - defer releaseTagMetadata(tm) - - tm.name = "empty_tag" - tm.valueType = pbv1.ValueTypeStr - - data := tm.marshal(nil) - - unmarshaled, err := unmarshalTagMetadata(data) - require.NoError(t, err) - defer releaseTagMetadata(unmarshaled) + tag.Reset() - assert.Equal(t, tm.name, unmarshaled.name) - assert.Equal(t, tm.valueType, unmarshaled.valueType) - assert.False(t, unmarshaled.indexed) - assert.Nil(t, unmarshaled.min) - assert.Nil(t, unmarshaled.max) - }) - - t.Run("reset functionality", func(t *testing.T) { - tm := generateTagMetadata() - - // Set up metadata - tm.name = "test_tag" - tm.valueType = pbv1.ValueTypeInt64 - tm.indexed = true - tm.dataBlock = dataBlock{offset: 100, size: 500} - tm.filterBlock = dataBlock{offset: 600, size: 200} - tm.min = []byte("min") - tm.max = []byte("max") - - // Reset - tm.reset() - - // Verify reset - assert.Equal(t, "", tm.name) - assert.Equal(t, pbv1.ValueTypeUnknown, tm.valueType) - assert.False(t, tm.indexed) - assert.Equal(t, dataBlock{}, tm.dataBlock) - assert.Equal(t, dataBlock{}, tm.filterBlock) - assert.Nil(t, tm.min) - assert.Nil(t, tm.max) - - releaseTagMetadata(tm) - }) -} - -func TestTagPooling(t *testing.T) { - t.Run("tagData pooling", func(t *testing.T) { - // Get from pool - td1 := generateTagData() - assert.NotNil(t, td1) - - // Use and release - td1.name = "test" - releaseTagData(td1) - - // Get again - should be reset - td2 := generateTagData() - assert.NotNil(t, td2) - assert.Equal(t, "", td2.name) // Should be reset - - releaseTagData(td2) - }) - - t.Run("tagMetadata pooling", func(t *testing.T) { - // Get from pool - tm1 := generateTagMetadata() - assert.NotNil(t, tm1) - - // Use and release - tm1.name = "test" - releaseTagMetadata(tm1) - - // Get again - should be reset - tm2 := generateTagMetadata() - assert.NotNil(t, tm2) - assert.Equal(t, "", tm2.name) // Should be reset - - releaseTagMetadata(tm2) - }) - - t.Run("bloomFilter pooling", func(t *testing.T) { - // Generate bloom filter - bf1 := generateBloomFilter(100) - assert.NotNil(t, bf1) - assert.Equal(t, 100, bf1.N()) - - // Add some data - bf1.Add([]byte("test1")) - bf1.Add([]byte("test2")) - assert.True(t, bf1.MightContain([]byte("test1"))) - - // Release to pool - releaseBloomFilter(bf1) - - // Get from pool again - bf2 := generateBloomFilter(50) - assert.NotNil(t, bf2) - assert.Equal(t, 50, bf2.N()) // Should be reset to new size - assert.False(t, bf2.MightContain([]byte("test1"))) // Should not contain old data - - releaseBloomFilter(bf2) - }) + assert.Equal(t, "", tag.Name) + assert.Nil(t, tag.Value) + assert.Equal(t, pbv1.ValueTypeUnknown, tag.ValueType) + assert.Equal(t, false, tag.Indexed) } -func TestEdgeCases(t *testing.T) { - t.Run("invalid int64 length", func(t *testing.T) { - // The shared encoding module panics on invalid data (fail fast) - assert.Panics(t, func() { - invalidValues := [][]byte{{0x01, 0x02, 0x03}} // Only 3 bytes, not 8 - _, _ = encodeTagValues(invalidValues, pbv1.ValueTypeInt64) - }) - }) - - t.Run("marshal nil values", func(t *testing.T) { - data, err := encodeTagValues(nil, pbv1.ValueTypeStr) - require.NoError(t, err) - assert.Nil(t, data) - - values, err := decodeTagValues(nil, pbv1.ValueTypeStr, 0) - require.NoError(t, err) - assert.Nil(t, values) - }) - - t.Run("updateMinMax with empty values", func(t *testing.T) { - td := generateTagData() - defer releaseTagData(td) - - td.valueType = pbv1.ValueTypeInt64 - td.values = [][]byte{} // empty - - td.updateMinMax() - assert.Nil(t, td.min) - assert.Nil(t, td.max) - }) - - t.Run("updateMinMax with non-int64 type", func(t *testing.T) { - td := generateTagData() - defer releaseTagData(td) - - td.valueType = pbv1.ValueTypeStr - td.values = [][]byte{[]byte("test")} - - td.updateMinMax() - assert.Nil(t, td.min) - assert.Nil(t, td.max) - }) - - t.Run("hasValue with non-indexed tag", func(t *testing.T) { - td := generateTagData() - defer releaseTagData(td) - - td.indexed = false - td.values = [][]byte{[]byte("value1"), []byte("value2")} - - // Should use linear search - assert.True(t, td.hasValue([]byte("value1"))) - assert.True(t, td.hasValue([]byte("value2"))) - assert.False(t, td.hasValue([]byte("value3"))) - }) -} - -func TestRoundTripIntegrity(t *testing.T) { - t.Run("complete round trip", func(t *testing.T) { - // Create original tag metadata - original := generateTagMetadata() - defer releaseTagMetadata(original) - - original.name = "integration_tag" - original.valueType = pbv1.ValueTypeInt64 - original.indexed = true - original.dataBlock = dataBlock{offset: 1000, size: 2000} - original.filterBlock = dataBlock{offset: 3000, size: 500} - - // Create some int64 values - int64Values := []int64{-100, 0, 100, 200, 50} - var encodedValues [][]byte - for _, v := range int64Values { - encoded := make([]byte, 8) - binary.LittleEndian.PutUint64(encoded, uint64(v)) - encodedValues = append(encodedValues, encoded) - } - - // Marshal values using shared encoding - marshaledValues, err := encodeTagValues(encodedValues, pbv1.ValueTypeInt64) - require.NoError(t, err) - - // Marshal metadata - marshaledMetadata := original.marshal(nil) - - // Unmarshal metadata - unmarshaledMetadata, err := unmarshalTagMetadata(marshaledMetadata) - require.NoError(t, err) - defer releaseTagMetadata(unmarshaledMetadata) - - // Unmarshal values using shared encoding - unmarshaledValues, err := decodeTagValues(marshaledValues, pbv1.ValueTypeInt64, len(encodedValues)) - require.NoError(t, err) - - // Verify metadata integrity - assert.Equal(t, original.name, unmarshaledMetadata.name) - assert.Equal(t, original.valueType, unmarshaledMetadata.valueType) - assert.Equal(t, original.indexed, unmarshaledMetadata.indexed) - - // Verify values integrity - assert.Equal(t, len(encodedValues), len(unmarshaledValues)) - for i, original := range encodedValues { - assert.True(t, bytes.Equal(original, unmarshaledValues[i])) - } +func TestTagSize(t *testing.T) { + tag := Tag{ + Name: "test", + Value: []byte("value"), + ValueType: pbv1.ValueTypeStr, + Indexed: true, + } - // Decode and verify int64 values - for i, expected := range int64Values { - decoded := int64(binary.LittleEndian.Uint64(unmarshaledValues[i])) - assert.Equal(t, expected, decoded) - } - }) + // Size should be len(name) + len(value) + 1 (for valueType) + expectedSize := len("test") + len("value") + 1 + assert.Equal(t, expectedSize, tag.Size()) } -func TestBloomFilterEncoding(t *testing.T) { - t.Run("encode and decode bloom filter", func(t *testing.T) { - // Create a bloom filter and add some data - bf := generateBloomFilter(100) - defer releaseBloomFilter(bf) - - testValues := [][]byte{ - []byte("value1"), - []byte("value2"), - []byte("value3"), - } - - for _, value := range testValues { - bf.Add(value) - } - - // Encode the bloom filter - var encoded []byte - encoded = encodeBloomFilter(encoded, bf) - assert.NotEmpty(t, encoded) - - // Decode the bloom filter - decodedBf, err := decodeBloomFilter(encoded) - require.NoError(t, err) - defer releaseBloomFilter(decodedBf) - - // Verify the decoded filter contains the same data - assert.Equal(t, bf.N(), decodedBf.N()) - for _, value := range testValues { - assert.True(t, decodedBf.MightContain(value)) - } - }) +func TestTagCopy(t *testing.T) { + original := Tag{ + Name: "original", + Value: []byte("original-value"), + ValueType: pbv1.ValueTypeStr, + Indexed: true, + } - t.Run("encode empty bloom filter", func(t *testing.T) { - var encoded []byte - encoded = encodeBloomFilter(encoded, nil) - assert.Empty(t, encoded) - }) + copied := original.Copy() - t.Run("decode invalid data", func(t *testing.T) { - // Test with data too short - invalidData := []byte{0x01, 0x02} - _, err := decodeBloomFilter(invalidData) - assert.Error(t, err) - assert.Contains(t, err.Error(), "too short") + // Test that copied tag has same values + assert.Equal(t, original.Name, copied.Name) + assert.Equal(t, original.Value, copied.Value) + assert.Equal(t, original.ValueType, copied.ValueType) + assert.Equal(t, original.Indexed, copied.Indexed) - // Test with empty data - _, err = decodeBloomFilter([]byte{}) - assert.Error(t, err) - assert.Contains(t, err.Error(), "too short") - }) + // Test that modifying original doesn't affect copy + original.Name = "modified" + original.Value = []byte("modified-value") + assert.Equal(t, "original", copied.Name) + assert.Equal(t, []byte("original-value"), copied.Value) } -// generateTagFilter creates a bloom filter for indexed tags. -func generateTagFilter(values [][]byte, expectedElements int) *filter.BloomFilter { - if len(values) == 0 { - return nil - } - - bloomFilter := generateBloomFilter(expectedElements) - - for _, value := range values { - bloomFilter.Add(value) +func TestTagInWriteRequest(t *testing.T) { + // Test that Tag can be used in WriteRequest + req := WriteRequest{ + SeriesID: 123, + Key: 456, + Data: []byte("test-data"), + Tags: []Tag{ + {Name: "service", Value: []byte("order-service"), ValueType: pbv1.ValueTypeStr, Indexed: true}, + {Name: "environment", Value: []byte("prod"), ValueType: pbv1.ValueTypeStr, Indexed: false}, + }, } - return bloomFilter + assert.Equal(t, 2, len(req.Tags)) + assert.Equal(t, "service", req.Tags[0].Name) + assert.Equal(t, "environment", req.Tags[1].Name) }
