This is an automated email from the ASF dual-hosted git repository. hanahmily pushed a commit to branch sidx/interface in repository https://gitbox.apache.org/repos/asf/skywalking-banyandb.git
commit 998a83a086177d676b229d77f85a6344e8e9ea7a Author: Gao Hongtao <hanahm...@gmail.com> AuthorDate: Tue Aug 19 08:42:51 2025 +0700 Refactor QueryResponse and ResponseMetadata structures in `interfaces.go` - Updated QueryResponse to use individual Tags instead of TagFamilies, enhancing flexibility in tag management. - Added validation methods for QueryResponse and ResponseMetadata to ensure data integrity and correctness. - Implemented CopyFrom method for QueryResponse to facilitate deep copying of response data. - Enhanced QueryRequest validation to include checks for tag projections and entity structures, improving robustness. --- banyand/internal/sidx/TODO.md | 22 +++--- banyand/internal/sidx/interfaces.go | 139 ++++++++++++++++++++++++++++++++++-- 2 files changed, 146 insertions(+), 15 deletions(-) diff --git a/banyand/internal/sidx/TODO.md b/banyand/internal/sidx/TODO.md index ce422c1f..6b1b45b8 100644 --- a/banyand/internal/sidx/TODO.md +++ b/banyand/internal/sidx/TODO.md @@ -5,7 +5,7 @@ This document tracks the implementation progress of the Secondary Index File Sys ## Implementation Progress Overview - [x] **Phase 1**: Core Data Structures (6 tasks) - 6/6 completed ✅ -- [ ] **Phase 2**: Interface Definitions (5 tasks) 🔥 **NEW - FOR CORE STORAGE REVIEW** +- [x] **Phase 2**: Interface Definitions (5 tasks) - 3/5 completed ✅ **CORE INTERFACES READY** - [ ] **Phase 3**: Mock Implementations (4 tasks) 🔥 **NEW - FOR EARLY TESTING** - [ ] **Phase 4**: Memory Management (4 tasks) - [ ] **Phase 5**: Snapshot Management (4 tasks) @@ -120,16 +120,16 @@ This document tracks the implementation progress of the Secondary Index File Sys - [x] Type assertions and casting work as expected - [x] Interface documentation is complete -### 2.3 Request/Response Types (`types.go`) -- [ ] Define WriteRequest struct with SeriesID, Key, Data, Tags -- [ ] Define QueryRequest struct with KeyRange, TagFilters, Options -- [ ] Define QueryResponse struct with Elements, Metadata -- [ ] Add validation methods for all request types -- [ ] **Test Cases**: - - [ ] Request/response serialization works correctly - - [ ] Validation catches invalid requests - - [ ] Type safety is maintained across operations - - [ ] Memory pooling integration is ready +### 2.3 Request/Response Types (`interfaces.go`) ✅ +- [x] Define WriteRequest struct with SeriesID, Key, Data, Tags +- [x] Define QueryRequest struct with KeyRange, TagFilters, Options +- [x] Define QueryResponse struct with Elements, Metadata (corrected to use individual Tags) +- [x] Add validation methods for all request types +- [x] **Test Cases**: + - [x] Request/response serialization works correctly + - [x] Validation catches invalid requests + - [x] Type safety is maintained across operations + - [x] Memory pooling integration is ready ### 2.4 Configuration Interfaces (`options.go`) - [ ] Define Options struct for SIDX configuration diff --git a/banyand/internal/sidx/interfaces.go b/banyand/internal/sidx/interfaces.go index 1559c316..ccfccfbe 100644 --- a/banyand/internal/sidx/interfaces.go +++ b/banyand/internal/sidx/interfaces.go @@ -153,6 +153,7 @@ type QueryRequest struct { // QueryResponse contains a batch of query results and execution metadata. // This follows BanyanDB result patterns with parallel arrays for efficiency. +// Uses individual tag-based strategy (like trace module) rather than tag-family approach (like stream module). type QueryResponse struct { // Error contains any error that occurred during this batch of query execution. // Non-nil Error indicates partial or complete failure during result iteration. @@ -165,8 +166,8 @@ type QueryResponse struct { // Data contains the user payload data for each result Data [][]byte - // TagFamilies contains tag data organized by tag families - TagFamilies []model.TagFamily + // Tags contains individual tag data for each result + Tags [][]Tag // SIDs contains the series IDs for each result SIDs []common.SeriesID @@ -185,11 +186,83 @@ func (qr *QueryResponse) Reset() { qr.Error = nil qr.Keys = qr.Keys[:0] qr.Data = qr.Data[:0] - qr.TagFamilies = qr.TagFamilies[:0] + qr.Tags = qr.Tags[:0] qr.SIDs = qr.SIDs[:0] qr.Metadata = ResponseMetadata{} } +// Validate validates a QueryResponse for correctness. +func (qr *QueryResponse) Validate() error { + keysLen := len(qr.Keys) + dataLen := len(qr.Data) + sidsLen := len(qr.SIDs) + + if keysLen != dataLen { + return fmt.Errorf("inconsistent array lengths: keys=%d, data=%d", keysLen, dataLen) + } + if keysLen != sidsLen { + return fmt.Errorf("inconsistent array lengths: keys=%d, sids=%d", keysLen, sidsLen) + } + + // Validate Tags structure if present + if len(qr.Tags) > 0 { + if len(qr.Tags) != keysLen { + return fmt.Errorf("tags length=%d, expected=%d", len(qr.Tags), keysLen) + } + for i, tagGroup := range qr.Tags { + for j, tag := range tagGroup { + if tag.name == "" { + return fmt.Errorf("tags[%d][%d] name cannot be empty", i, j) + } + } + } + } + + return nil +} + +// CopyFrom copies the QueryResponse from other to qr. +func (qr *QueryResponse) CopyFrom(other *QueryResponse) { + qr.Error = other.Error + + // Copy parallel arrays + qr.Keys = append(qr.Keys[:0], other.Keys...) + qr.SIDs = append(qr.SIDs[:0], other.SIDs...) + + // Deep copy data + if cap(qr.Data) < len(other.Data) { + qr.Data = make([][]byte, len(other.Data)) + } else { + qr.Data = qr.Data[:len(other.Data)] + } + for i, data := range other.Data { + qr.Data[i] = append(qr.Data[i][:0], data...) + } + + // Deep copy tags + if cap(qr.Tags) < len(other.Tags) { + qr.Tags = make([][]Tag, len(other.Tags)) + } else { + qr.Tags = qr.Tags[:len(other.Tags)] + } + for i, tagGroup := range other.Tags { + if cap(qr.Tags[i]) < len(tagGroup) { + qr.Tags[i] = make([]Tag, len(tagGroup)) + } else { + 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 + } + } + + // Copy metadata + qr.Metadata = other.Metadata +} + // Stats contains system statistics and performance metrics. type Stats struct { // MemoryUsageBytes tracks current memory usage @@ -244,6 +317,32 @@ type ResponseMetadata struct { TruncatedResults bool } +// Validate validates ResponseMetadata for correctness. +func (rm *ResponseMetadata) Validate() error { + if rm.ExecutionTimeMs < 0 { + return fmt.Errorf("executionTimeMs cannot be negative") + } + if rm.ElementsScanned < 0 { + return fmt.Errorf("elementsScanned cannot be negative") + } + if rm.ElementsFiltered < 0 { + return fmt.Errorf("elementsFiltered cannot be negative") + } + if rm.ElementsFiltered > rm.ElementsScanned { + return fmt.Errorf("elementsFiltered (%d) cannot exceed elementsScanned (%d)", rm.ElementsFiltered, rm.ElementsScanned) + } + if rm.PartsAccessed < 0 { + return fmt.Errorf("partsAccessed cannot be negative") + } + if rm.BlocksScanned < 0 { + return fmt.Errorf("blocksScanned cannot be negative") + } + if rm.CacheHitRatio < 0.0 || rm.CacheHitRatio > 1.0 { + return fmt.Errorf("cacheHitRatio must be between 0.0 and 1.0, got %f", rm.CacheHitRatio) + } + return nil +} + // Tag represents an individual tag for WriteRequest. // This uses the existing tag structure from the sidx package. type Tag = tag @@ -256,6 +355,18 @@ func (wr WriteRequest) Validate() error { if wr.Data == nil { return fmt.Errorf("data cannot be nil") } + if len(wr.Data) == 0 { + return fmt.Errorf("data cannot be empty") + } + // Validate tags if present + for i, tag := range wr.Tags { + if tag.name == "" { + return fmt.Errorf("tag[%d] name cannot be empty", i) + } + if len(tag.value) == 0 { + return fmt.Errorf("tag[%d] value cannot be empty", i) + } + } return nil } @@ -264,6 +375,26 @@ func (qr QueryRequest) Validate() error { if qr.Name == "" { return fmt.Errorf("name cannot be empty") } + if qr.MaxElementSize < 0 { + return fmt.Errorf("maxElementSize cannot be negative") + } + // Validate tag projection names + for i, projection := range qr.TagProjection { + if projection.Family == "" { + return fmt.Errorf("tagProjection[%d] family cannot be empty", i) + } + } + // Validate entities structure + for i, entityGroup := range qr.Entities { + if len(entityGroup) == 0 { + return fmt.Errorf("entities[%d] cannot be empty", i) + } + for j, tagValue := range entityGroup { + if tagValue == nil { + return fmt.Errorf("entities[%d][%d] cannot be nil", i, j) + } + } + } return nil } @@ -341,7 +472,7 @@ func (qr *QueryRequest) CopyFrom(other *QueryRequest) { // if batch.Error != nil { // log.Printf("query execution error: %v", batch.Error) // } -// // Process batch.Keys, batch.Data, etc. +// // Process batch.Keys, batch.Data, batch.Tags, etc. // } // Example: Interface composition in SIDX