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 bb901df2e2cdf472896c19fa36922a273bfe263c
Author: Gao Hongtao <[email protected]>
AuthorDate: Sun Aug 24 15:29:15 2025 +0800

    Add part iterator tests: Implement comprehensive unit tests for part 
iterator functionality, covering various scenarios including single and 
multiple series, key range filtering, and edge cases. Ensure robust validation 
of expected outcomes and error handling in part iteration.
---
 banyand/internal/sidx/part_iter_test.go | 448 ++++++++++++++++++++++++++++++++
 banyand/internal/sidx/part_test.go      |   2 +-
 2 files changed, 449 insertions(+), 1 deletion(-)

diff --git a/banyand/internal/sidx/part_iter_test.go 
b/banyand/internal/sidx/part_iter_test.go
new file mode 100644
index 00000000..25df9a74
--- /dev/null
+++ b/banyand/internal/sidx/part_iter_test.go
@@ -0,0 +1,448 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package sidx
+
+import (
+       "fmt"
+       "path/filepath"
+       "testing"
+
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
+
+       "github.com/apache/skywalking-banyandb/api/common"
+       "github.com/apache/skywalking-banyandb/pkg/fs"
+       pbv1 "github.com/apache/skywalking-banyandb/pkg/pb/v1"
+)
+
+func TestPartIterVerification(t *testing.T) {
+       testFS := fs.NewLocalFileSystem()
+       tempDir := t.TempDir()
+
+       tests := []struct {
+               name        string
+               elements    []testElement
+               querySids   []common.SeriesID
+               minKey      int64
+               maxKey      int64
+               expectedLen int
+       }{
+               {
+                       name: "single series single element",
+                       elements: []testElement{
+                               {
+                                       seriesID: 1,
+                                       userKey:  100,
+                                       data:     []byte("data1"),
+                                       tags: []tag{
+                                               {
+                                                       name:      "service",
+                                                       value:     
[]byte("order-service"),
+                                                       valueType: 
pbv1.ValueTypeStr,
+                                                       indexed:   true,
+                                               },
+                                       },
+                               },
+                       },
+                       querySids:   []common.SeriesID{1},
+                       minKey:      50,
+                       maxKey:      150,
+                       expectedLen: 1,
+               },
+               {
+                       name: "single series multiple elements",
+                       elements: []testElement{
+                               {
+                                       seriesID: 1,
+                                       userKey:  100,
+                                       data:     []byte("data1"),
+                                       tags: []tag{
+                                               {
+                                                       name:      "service",
+                                                       value:     
[]byte("order-service"),
+                                                       valueType: 
pbv1.ValueTypeStr,
+                                                       indexed:   true,
+                                               },
+                                       },
+                               },
+                               {
+                                       seriesID: 1,
+                                       userKey:  200,
+                                       data:     []byte("data2"),
+                                       tags: []tag{
+                                               {
+                                                       name:      "service",
+                                                       value:     
[]byte("order-service"),
+                                                       valueType: 
pbv1.ValueTypeStr,
+                                                       indexed:   true,
+                                               },
+                                       },
+                               },
+                       },
+                       querySids:   []common.SeriesID{1},
+                       minKey:      50,
+                       maxKey:      250,
+                       expectedLen: 1, // Elements from same series are 
grouped into 1 block
+               },
+               {
+                       name: "multiple series",
+                       elements: []testElement{
+                               {
+                                       seriesID: 1,
+                                       userKey:  100,
+                                       data:     []byte("data1"),
+                                       tags: []tag{
+                                               {
+                                                       name:      "service",
+                                                       value:     
[]byte("order-service"),
+                                                       valueType: 
pbv1.ValueTypeStr,
+                                                       indexed:   true,
+                                               },
+                                       },
+                               },
+                               {
+                                       seriesID: 2,
+                                       userKey:  150,
+                                       data:     []byte("data2"),
+                                       tags: []tag{
+                                               {
+                                                       name:      "service",
+                                                       value:     
[]byte("payment-service"),
+                                                       valueType: 
pbv1.ValueTypeStr,
+                                                       indexed:   true,
+                                               },
+                                       },
+                               },
+                               {
+                                       seriesID: 3,
+                                       userKey:  200,
+                                       data:     []byte("data3"),
+                                       tags: []tag{
+                                               {
+                                                       name:      "service",
+                                                       value:     
[]byte("user-service"),
+                                                       valueType: 
pbv1.ValueTypeStr,
+                                                       indexed:   true,
+                                               },
+                                       },
+                               },
+                       },
+                       querySids:   []common.SeriesID{1, 2, 3},
+                       minKey:      50,
+                       maxKey:      250,
+                       expectedLen: 3,
+               },
+               {
+                       name: "filtered by key range",
+                       elements: []testElement{
+                               {
+                                       seriesID: 1,
+                                       userKey:  50,
+                                       data:     []byte("data1"),
+                                       tags: []tag{
+                                               {
+                                                       name:      "service",
+                                                       value:     
[]byte("order-service"),
+                                                       valueType: 
pbv1.ValueTypeStr,
+                                                       indexed:   true,
+                                               },
+                                       },
+                               },
+                               {
+                                       seriesID: 1,
+                                       userKey:  100,
+                                       data:     []byte("data2"),
+                                       tags: []tag{
+                                               {
+                                                       name:      "service",
+                                                       value:     
[]byte("order-service"),
+                                                       valueType: 
pbv1.ValueTypeStr,
+                                                       indexed:   true,
+                                               },
+                                       },
+                               },
+                               {
+                                       seriesID: 1,
+                                       userKey:  200,
+                                       data:     []byte("data3"),
+                                       tags: []tag{
+                                               {
+                                                       name:      "service",
+                                                       value:     
[]byte("order-service"),
+                                                       valueType: 
pbv1.ValueTypeStr,
+                                                       indexed:   true,
+                                               },
+                                       },
+                               },
+                       },
+                       querySids:   []common.SeriesID{1},
+                       minKey:      75,
+                       maxKey:      150,
+                       expectedLen: 1, // Block contains all elements 
[50-200], overlaps query range [75-150]
+               },
+               {
+                       name: "filtered by series ID",
+                       elements: []testElement{
+                               {
+                                       seriesID: 1,
+                                       userKey:  100,
+                                       data:     []byte("data1"),
+                                       tags: []tag{
+                                               {
+                                                       name:      "service",
+                                                       value:     
[]byte("order-service"),
+                                                       valueType: 
pbv1.ValueTypeStr,
+                                                       indexed:   true,
+                                               },
+                                       },
+                               },
+                               {
+                                       seriesID: 2,
+                                       userKey:  100,
+                                       data:     []byte("data2"),
+                                       tags: []tag{
+                                               {
+                                                       name:      "service",
+                                                       value:     
[]byte("payment-service"),
+                                                       valueType: 
pbv1.ValueTypeStr,
+                                                       indexed:   true,
+                                               },
+                                       },
+                               },
+                               {
+                                       seriesID: 3,
+                                       userKey:  100,
+                                       data:     []byte("data3"),
+                                       tags: []tag{
+                                               {
+                                                       name:      "service",
+                                                       value:     
[]byte("user-service"),
+                                                       valueType: 
pbv1.ValueTypeStr,
+                                                       indexed:   true,
+                                               },
+                                       },
+                               },
+                       },
+                       querySids:   []common.SeriesID{2},
+                       minKey:      50,
+                       maxKey:      150,
+                       expectedLen: 1, // Only series 2 should match
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       // Step 1: Create elements and initialize memPart
+                       elements := createTestElements(tt.elements)
+                       defer releaseElements(elements)
+
+                       mp := generateMemPart()
+                       defer releaseMemPart(mp)
+
+                       mp.mustInitFromElements(elements)
+
+                       // Step 2: Create part from memPart by flushing to disk
+                       partDir := filepath.Join(tempDir, 
fmt.Sprintf("part_%s", tt.name))
+                       mp.mustFlush(testFS, partDir)
+
+                       part := mustOpenPart(partDir, testFS)
+                       defer part.close()
+
+                       // Step 3: Create partIter and blockMetadataArray
+                       bma := &blockMetadataArray{}
+                       defer bma.reset()
+
+                       pi := &partIter{}
+
+                       // Step 4: Initialize partIter with clean 
blockMetadataArray
+                       bma.reset() // Keep blockMetadataArray clean before 
passing to init
+                       pi.init(bma, part, tt.querySids, tt.minKey, tt.maxKey)
+
+                       // Step 5: Iterate through blocks and collect results
+                       var foundElements []testElement
+                       blockCount := 0
+
+                       for pi.nextBlock() {
+                               blockCount++
+                               curBlock := pi.curBlock
+
+                               t.Logf("Found block for seriesID %d, key range 
[%d, %d], count: %d",
+                                       curBlock.seriesID, curBlock.minKey, 
curBlock.maxKey, curBlock.count)
+
+                               // Verify the block overlaps with query range 
(partIter returns overlapping blocks)
+                               overlaps := curBlock.maxKey >= tt.minKey && 
curBlock.minKey <= tt.maxKey
+                               assert.True(t, overlaps, "block should overlap 
with query range [%d, %d], but got block range [%d, %d]",
+                                       tt.minKey, tt.maxKey, curBlock.minKey, 
curBlock.maxKey)
+                               assert.Contains(t, tt.querySids, 
curBlock.seriesID, "block seriesID should be in query sids")
+
+                               // For verification, create a test element 
representing this block
+                               // Note: In a real scenario, you'd read the 
actual block data
+                               foundElements = append(foundElements, 
testElement{
+                                       seriesID: curBlock.seriesID,
+                                       userKey:  curBlock.minKey, // Use 
minKey as representative
+                                       data:     nil,             // Not 
reading actual data in this test
+                                       tags:     nil,             // Not 
reading actual tags in this test
+                               })
+                       }
+
+                       // Step 6: Check for iteration errors
+                       require.NoError(t, pi.error(), "partIter should not 
have errors")
+
+                       // Step 7: Verify results
+                       assert.Equal(t, tt.expectedLen, len(foundElements), 
"should find expected number of elements")
+
+                       // Additional verification: ensure all found elements 
match expected series
+                       for _, elem := range foundElements {
+                               assert.Contains(t, tt.querySids, elem.seriesID, 
"found element should have expected seriesID")
+                       }
+
+                       t.Logf("Test %s completed: found %d blocks, expected 
%d", tt.name, blockCount, tt.expectedLen)
+               })
+       }
+}
+
+func TestPartIterEdgeCases(t *testing.T) {
+       testFS := fs.NewLocalFileSystem()
+       tempDir := t.TempDir()
+
+       t.Run("empty series list", func(t *testing.T) {
+               // Create a simple part with data
+               elements := createTestElements([]testElement{
+                       {
+                               seriesID: 1,
+                               userKey:  100,
+                               data:     []byte("data1"),
+                               tags: []tag{
+                                       {
+                                               name:      "service",
+                                               value:     
[]byte("test-service"),
+                                               valueType: pbv1.ValueTypeStr,
+                                               indexed:   true,
+                                       },
+                               },
+                       },
+               })
+               defer releaseElements(elements)
+
+               mp := generateMemPart()
+               defer releaseMemPart(mp)
+               mp.mustInitFromElements(elements)
+
+               partDir := filepath.Join(tempDir, "empty_series_test")
+               mp.mustFlush(testFS, partDir)
+
+               part := mustOpenPart(partDir, testFS)
+               defer part.close()
+
+               // Test with empty series list
+               bma := &blockMetadataArray{}
+               defer bma.reset()
+               pi := &partIter{}
+
+               bma.reset()
+               pi.init(bma, part, []common.SeriesID{}, 0, 1000)
+
+               // Should not find any blocks with empty series list
+               foundAny := pi.nextBlock()
+               assert.False(t, foundAny, "should not find any blocks with 
empty series list")
+       })
+
+       t.Run("no matching key range", func(t *testing.T) {
+               // Create a part with data at key 100
+               elements := createTestElements([]testElement{
+                       {
+                               seriesID: 1,
+                               userKey:  100,
+                               data:     []byte("data1"),
+                               tags: []tag{
+                                       {
+                                               name:      "service",
+                                               value:     
[]byte("test-service"),
+                                               valueType: pbv1.ValueTypeStr,
+                                               indexed:   true,
+                                       },
+                               },
+                       },
+               })
+               defer releaseElements(elements)
+
+               mp := generateMemPart()
+               defer releaseMemPart(mp)
+               mp.mustInitFromElements(elements)
+
+               partDir := filepath.Join(tempDir, "no_match_key_range")
+               mp.mustFlush(testFS, partDir)
+
+               part := mustOpenPart(partDir, testFS)
+               defer part.close()
+
+               // Test with non-overlapping key range
+               bma := &blockMetadataArray{}
+               defer bma.reset()
+               pi := &partIter{}
+
+               bma.reset()
+               pi.init(bma, part, []common.SeriesID{1}, 200, 300) // No 
overlap with key 100
+
+               // Should not find any blocks
+               foundAny := pi.nextBlock()
+               assert.False(t, foundAny, "should not find any blocks with 
non-overlapping key range")
+       })
+
+       t.Run("no matching series ID", func(t *testing.T) {
+               // Create a part with seriesID 1
+               elements := createTestElements([]testElement{
+                       {
+                               seriesID: 1,
+                               userKey:  100,
+                               data:     []byte("data1"),
+                               tags: []tag{
+                                       {
+                                               name:      "service",
+                                               value:     
[]byte("test-service"),
+                                               valueType: pbv1.ValueTypeStr,
+                                               indexed:   true,
+                                       },
+                               },
+                       },
+               })
+               defer releaseElements(elements)
+
+               mp := generateMemPart()
+               defer releaseMemPart(mp)
+               mp.mustInitFromElements(elements)
+
+               partDir := filepath.Join(tempDir, "no_match_series")
+               mp.mustFlush(testFS, partDir)
+
+               part := mustOpenPart(partDir, testFS)
+               defer part.close()
+
+               // Test with different series ID
+               bma := &blockMetadataArray{}
+               defer bma.reset()
+               pi := &partIter{}
+
+               bma.reset()
+               pi.init(bma, part, []common.SeriesID{2}, 0, 200) // Different 
series ID
+
+               // Should not find any blocks
+               foundAny := pi.nextBlock()
+               assert.False(t, foundAny, "should not find any blocks with 
non-matching series ID")
+       })
+}
diff --git a/banyand/internal/sidx/part_test.go 
b/banyand/internal/sidx/part_test.go
index a764cb00..12636cbf 100644
--- a/banyand/internal/sidx/part_test.go
+++ b/banyand/internal/sidx/part_test.go
@@ -516,7 +516,7 @@ func compareElements(t *testing.T, expected, actual 
*elements) {
                        return actualTags[a].name < actualTags[b].name
                })
 
-               for j := 0; j < len(expectedTags); j++ {
+               for j := range expectedTags {
                        assert.Equal(t, expectedTags[j].name, 
actualTags[j].name,
                                "tag name mismatch at element %d, tag %d", i, j)
                        assert.Equal(t, expectedTags[j].value, 
actualTags[j].value,

Reply via email to