Files
wehub-resource-sync 498b235461
Build and test / Build and test AMD64 Ubuntu 22.04 (push) Failing after 0s
Publish Builder / amazonlinux2023 (push) Failing after 1s
Build and test / UT for Go (push) Has been skipped
Publish KRTE Images / KRTE (push) Failing after 1s
Build and test / Integration Test (push) Has been skipped
Build and test / Upload Code Coverage (push) Has been skipped
Publish Builder / rockylinux9 (push) Failing after 1s
Publish Builder / ubuntu22.04 (push) Failing after 0s
Publish Builder / ubuntu24.04 (push) Failing after 0s
Publish Gpu Builder / publish-gpu-builder (push) Failing after 1s
Publish Test Images / PyTest (push) Failing after 0s
Build and test / UT for Cpp (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:31:17 +08:00

204 lines
6.2 KiB
Go

// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The 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 queryutil
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"github.com/milvus-io/milvus/pkg/v3/proto/internalpb"
"github.com/milvus-io/milvus/pkg/v3/util/typeutil"
)
// SliceOperator applies offset and limit to a RetrieveResult.
// This operator is used at proxy-side to apply final pagination.
//
// For element-level queries (StructArray), offset and limit count individual
// array elements rather than documents. A single document may contribute
// multiple elements, and offset/limit boundaries can fall mid-document,
// requiring ElementIndices trimming.
type SliceOperator struct {
limit int64
offset int64
}
// NewSliceOperator creates a new slice operator.
// limit: maximum number of results to return (-1 for unlimited)
// offset: number of results to skip
func NewSliceOperator(limit, offset int64) *SliceOperator {
return &SliceOperator{
limit: limit,
offset: offset,
}
}
func (op *SliceOperator) Name() string {
return OpSlice
}
// Run applies offset and limit to a RetrieveResult.
// Input[0]: *internalpb.RetrieveResults
// Output[0]: *internalpb.RetrieveResults (sliced)
func (op *SliceOperator) Run(ctx context.Context, span trace.Span, inputs ...any) ([]any, error) {
_, sp := otel.Tracer(typeutil.ProxyRole).Start(ctx, "SliceOperator")
defer sp.End()
result := inputs[0].(*internalpb.RetrieveResults)
if result == nil || len(result.GetFieldsData()) == 0 {
return []any{result}, nil
}
var sliced *internalpb.RetrieveResults
var err error
if result.GetElementLevel() {
sliced, err = op.applyElementLevelSlice(result)
} else {
sliced, err = op.applySlice(result)
}
if err != nil {
return nil, err
}
return []any{sliced}, nil
}
// applySlice applies doc-level offset and limit to the result.
func (op *SliceOperator) applySlice(result *internalpb.RetrieveResults) (*internalpb.RetrieveResults, error) {
size := getRowCount(result)
if size == 0 {
return result, nil
}
start := int(op.offset)
if start >= size {
return &internalpb.RetrieveResults{}, nil
}
end := size
if op.limit > 0 && start+int(op.limit) < end {
end = start + int(op.limit)
}
if start == 0 && end == size {
return result, nil
}
return rangeSliceRetrieveResults(result, start, end)
}
// applyElementLevelSlice applies element-level offset and limit.
// Offset and limit count individual array elements, not documents.
// When a boundary falls mid-document, the document's ElementIndices are trimmed.
func (op *SliceOperator) applyElementLevelSlice(result *internalpb.RetrieveResults) (*internalpb.RetrieveResults, error) {
docCount := getRowCount(result)
elemIndices := result.GetElementIndices()
if docCount == 0 || len(elemIndices) == 0 {
return result, nil
}
// Phase 1: Apply offset by skipping elements
startDoc := 0
var startTrimCount int // number of elements to trim from the start of startDoc (0 = no trim)
if op.offset > 0 {
var skipped int64
for startDoc < docCount && skipped < op.offset {
if startDoc >= len(elemIndices) {
break
}
docElemCount := int64(len(elemIndices[startDoc].GetIndices()))
if skipped+docElemCount > op.offset {
// Offset lands mid-document: record how many elements to trim
startTrimCount = int(op.offset - skipped)
break
}
skipped += docElemCount
startDoc++
}
if startDoc >= docCount {
return &internalpb.RetrieveResults{ElementLevel: true}, nil
}
}
// Phase 2: Apply limit by counting elements
endDoc := docCount // default: take all remaining
var endTrimCount int // number of elements to keep in endDoc-1 (0 = no trim, keep all)
if op.limit > 0 {
var collected int64
for i := startDoc; i < docCount; i++ {
if i >= len(elemIndices) {
endDoc = i
break
}
docElemCount := int64(len(elemIndices[i].GetIndices()))
if i == startDoc && startTrimCount > 0 {
docElemCount -= int64(startTrimCount)
}
if collected+docElemCount >= op.limit {
// Limit reached at or within this document
remaining := op.limit - collected
fullCount := int64(len(elemIndices[i].GetIndices()))
if i == startDoc && startTrimCount > 0 {
// Both trim from start and end of same doc
endTrimCount = startTrimCount + int(remaining)
} else if remaining < fullCount {
endTrimCount = int(remaining)
}
endDoc = i + 1
break
}
collected += docElemCount
}
}
if startDoc >= endDoc {
return &internalpb.RetrieveResults{ElementLevel: true}, nil
}
// Phase 3: Slice field data at document granularity [startDoc, endDoc)
sliced, err := rangeSliceRetrieveResults(result, startDoc, endDoc)
if err != nil {
return nil, err
}
// Phase 4: Apply element-level trims to ElementIndices
if startTrimCount > 0 && endTrimCount > 0 && startDoc+1 == endDoc {
// Both trims apply to the same (single) document
original := sliced.GetElementIndices()[0].GetIndices()
sliced.ElementIndices[0] = &internalpb.ElementIndices{
Indices: original[startTrimCount:endTrimCount],
}
} else {
if startTrimCount > 0 && len(sliced.GetElementIndices()) > 0 {
original := sliced.GetElementIndices()[0].GetIndices()
sliced.ElementIndices[0] = &internalpb.ElementIndices{
Indices: original[startTrimCount:],
}
}
if endTrimCount > 0 && len(sliced.GetElementIndices()) > 0 {
lastIdx := len(sliced.GetElementIndices()) - 1
original := sliced.GetElementIndices()[lastIdx].GetIndices()
sliced.ElementIndices[lastIdx] = &internalpb.ElementIndices{
Indices: original[:endTrimCount],
}
}
}
return sliced, nil
}