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

1707 lines
58 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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.
// The merge/slice helpers in this file consume RetrieveResults produced by
// segcore (and merged across query nodes), never raw user input, so every
// data-shape assertion below classifies as ServiceInternal: a violation means
// a segcore/Milvus bug, and must not be attributed to the user (fail_input)
// or suppress cross-replica failover the way an InputError would.
package queryutil
import (
"google.golang.org/protobuf/proto"
"github.com/milvus-io/milvus-proto/go-api/v3/schemapb"
"github.com/milvus-io/milvus/pkg/v3/proto/internalpb"
"github.com/milvus-io/milvus/pkg/v3/util/merr"
"github.com/milvus-io/milvus/pkg/v3/util/typeutil"
)
// rowRef references a specific row in a specific result.
type rowRef struct {
resultIdx int
rowIdx int64
}
// comparePK compares two primary keys.
// Returns -1 if a < b, 0 if a == b, 1 if a > b.
func comparePK(a, b any) int {
switch va := a.(type) {
case int64:
vb := b.(int64)
if va < vb {
return -1
} else if va > vb {
return 1
}
return 0
case string:
vb := b.(string)
if va < vb {
return -1
} else if va > vb {
return 1
}
return 0
}
return 0
}
// buildMergedRetrieveResults builds merged result from selected rows.
// schema provides field metadata (DataType, dim, nullable) used to drive
// the merge logic. Pass nil to fall back to template-based inference
// (backward compatible for tests and callers without schema).
func buildMergedRetrieveResults(results []*internalpb.RetrieveResults, selectedRows []rowRef, schema *schemapb.CollectionSchema) (*internalpb.RetrieveResults, error) {
if len(selectedRows) == 0 || len(results) == 0 {
return &internalpb.RetrieveResults{}, nil
}
// Use first result as template for field count / metadata
template := results[selectedRows[0].resultIdx]
numFields := len(template.GetFieldsData())
// Validate all referenced results have the same number of fields.
for _, ref := range selectedRows {
refFields := len(results[ref.resultIdx].GetFieldsData())
if refFields != numFields {
return nil, merr.WrapErrServiceInternalMsg(
"FieldsData count mismatch: result[%d] has %d fields, expected %d",
ref.resultIdx, refFields, numFields)
}
}
// Validate element-level consistency across all results referenced by selectedRows.
if err := validateElementLevelConsistency(results, selectedRows); err != nil {
return nil, err
}
// Build field schema lookup map (fieldID → *FieldSchema).
var fieldSchemaMap map[int64]*schemapb.FieldSchema
if schema != nil {
allFields := typeutil.GetAllFieldSchemas(schema)
fieldSchemaMap = make(map[int64]*schemapb.FieldSchema, len(allFields))
for _, f := range allFields {
fieldSchemaMap[f.GetFieldID()] = f
}
}
merged := &internalpb.RetrieveResults{
FieldsData: make([]*schemapb.FieldData, numFields),
}
// Build merged IDs
merged.Ids = buildMergedIDs(results, selectedRows)
// Build merged field data
for fieldIdx := 0; fieldIdx < numFields; fieldIdx++ {
fieldID := template.GetFieldsData()[fieldIdx].GetFieldId()
var fs *schemapb.FieldSchema
if fieldSchemaMap != nil {
fs = fieldSchemaMap[fieldID] // nil for system fields (RowID=0, Timestamp=1)
}
fd, err := buildMergedFieldData(results, selectedRows, fieldIdx, fs)
if err != nil {
return nil, err
}
merged.FieldsData[fieldIdx] = fd
}
// Propagate element-level metadata
merged.ElementLevel = template.GetElementLevel()
if merged.ElementLevel {
merged.ElementIndices = buildMergedElementIndices(results, selectedRows)
}
return merged, nil
}
// buildMergedElementIndices extracts ElementIndices for selectedRows.
func buildMergedElementIndices(results []*internalpb.RetrieveResults, selectedRows []rowRef) []*internalpb.ElementIndices {
indices := make([]*internalpb.ElementIndices, len(selectedRows))
for i, ref := range selectedRows {
elemIndices := results[ref.resultIdx].GetElementIndices()
if int(ref.rowIdx) < len(elemIndices) {
indices[i] = elemIndices[ref.rowIdx]
}
}
return indices
}
// validateElementLevelConsistency checks that all results referenced by
// selectedRows have a consistent ElementLevel flag, and that element-level
// results have ElementIndices length matching their IDs length.
func validateElementLevelConsistency(results []*internalpb.RetrieveResults, _ []rowRef) error {
if len(results) == 0 {
return nil
}
isElementLevel := results[0].GetElementLevel()
for i, r := range results {
if r.GetElementLevel() != isElementLevel {
return merr.WrapErrServiceInternalMsg(
"inconsistent element-level flag: result[%d] has ElementLevel=%v, expected %v",
i, r.GetElementLevel(), isElementLevel)
}
if isElementLevel {
idsLen := typeutil.GetSizeOfIDs(r.GetIds())
indicesLen := len(r.GetElementIndices())
if indicesLen != idsLen {
return merr.WrapErrServiceInternalMsg(
"element_indices length (%d) does not match ids length (%d) in result[%d]",
indicesLen, idsLen, i)
}
}
}
return nil
}
// buildMergedIDs builds merged IDs from selected rows.
func buildMergedIDs(results []*internalpb.RetrieveResults, selectedRows []rowRef) *schemapb.IDs {
if len(selectedRows) == 0 {
return nil
}
firstIDs := results[selectedRows[0].resultIdx].GetIds()
if firstIDs == nil {
return nil
}
switch firstIDs.GetIdField().(type) {
case *schemapb.IDs_IntId:
ids := make([]int64, len(selectedRows))
for i, ref := range selectedRows {
ids[i] = results[ref.resultIdx].GetIds().GetIntId().GetData()[ref.rowIdx]
}
return &schemapb.IDs{IdField: &schemapb.IDs_IntId{IntId: &schemapb.LongArray{Data: ids}}}
case *schemapb.IDs_StrId:
ids := make([]string, len(selectedRows))
for i, ref := range selectedRows {
ids[i] = results[ref.resultIdx].GetIds().GetStrId().GetData()[ref.rowIdx]
}
return &schemapb.IDs{IdField: &schemapb.IDs_StrId{StrId: &schemapb.StringArray{Data: ids}}}
}
return nil
}
// buildMergedFieldData builds merged field data from selected rows.
// fieldSchema provides DataType, dim, and nullable from the collection schema.
// It may be nil for system fields (RowID, Timestamp) or in tests; in that case
// the function falls back to template-based inference for backward compatibility.
func buildMergedFieldData(results []*internalpb.RetrieveResults, selectedRows []rowRef, fieldIdx int, fieldSchema *schemapb.FieldSchema) (*schemapb.FieldData, error) {
template := results[selectedRows[0].resultIdx].GetFieldsData()[fieldIdx]
isNullable := fieldSchema != nil && fieldSchema.GetNullable()
newFd := &schemapb.FieldData{
Type: template.GetType(),
FieldName: template.GetFieldName(),
FieldId: template.GetFieldId(),
IsDynamic: template.GetIsDynamic(),
}
// Determine whether this field is a vector using schema (preferred) or template (fallback).
isVector := false
if fieldSchema != nil {
isVector = typeutil.IsVectorType(fieldSchema.GetDataType())
} else {
_, isVector = template.GetField().(*schemapb.FieldData_Vectors)
}
if isVector {
vecField, err := buildMergedVectorField(results, selectedRows, fieldIdx, fieldSchema)
if err != nil {
return nil, err
}
newFd.Field = &schemapb.FieldData_Vectors{
Vectors: vecField,
}
} else {
newFd.Field = &schemapb.FieldData_Scalars{
Scalars: buildMergedScalarField(results, selectedRows, fieldIdx),
}
}
// Preserve ValidData (nullable bitmap) for nullable fields.
if isNullable {
validData := make([]bool, len(selectedRows))
for i, ref := range selectedRows {
vd := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetValidData()
if len(vd) > 0 && int(ref.rowIdx) < len(vd) {
validData[i] = vd[ref.rowIdx]
}
// ValidData absent or rowIdx out of bounds: keep false (null semantics)
}
newFd.ValidData = validData
}
return newFd, nil
}
// buildMergedScalarField builds merged scalar field from selected rows.
// Bounds-checks each row access: nullable fields with all-null results may have an empty
// Data array (segcore omits the storage); out-of-bounds rows keep the Go zero value.
// The corresponding ValidData entry will be false, so users never see zero-filled nulls.
func buildMergedScalarField(results []*internalpb.RetrieveResults, selectedRows []rowRef, fieldIdx int) *schemapb.ScalarField {
template := results[selectedRows[0].resultIdx].GetFieldsData()[fieldIdx].GetScalars()
newSf := &schemapb.ScalarField{}
switch template.GetData().(type) {
case *schemapb.ScalarField_BoolData:
data := make([]bool, len(selectedRows))
for i, ref := range selectedRows {
if src := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetScalars().GetBoolData().GetData(); int(ref.rowIdx) < len(src) {
data[i] = src[ref.rowIdx]
}
}
newSf.Data = &schemapb.ScalarField_BoolData{BoolData: &schemapb.BoolArray{Data: data}}
case *schemapb.ScalarField_IntData:
data := make([]int32, len(selectedRows))
for i, ref := range selectedRows {
if src := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetScalars().GetIntData().GetData(); int(ref.rowIdx) < len(src) {
data[i] = src[ref.rowIdx]
}
}
newSf.Data = &schemapb.ScalarField_IntData{IntData: &schemapb.IntArray{Data: data}}
case *schemapb.ScalarField_LongData:
data := make([]int64, len(selectedRows))
for i, ref := range selectedRows {
if src := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetScalars().GetLongData().GetData(); int(ref.rowIdx) < len(src) {
data[i] = src[ref.rowIdx]
}
}
newSf.Data = &schemapb.ScalarField_LongData{LongData: &schemapb.LongArray{Data: data}}
case *schemapb.ScalarField_FloatData:
data := make([]float32, len(selectedRows))
for i, ref := range selectedRows {
if src := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetScalars().GetFloatData().GetData(); int(ref.rowIdx) < len(src) {
data[i] = src[ref.rowIdx]
}
}
newSf.Data = &schemapb.ScalarField_FloatData{FloatData: &schemapb.FloatArray{Data: data}}
case *schemapb.ScalarField_DoubleData:
data := make([]float64, len(selectedRows))
for i, ref := range selectedRows {
if src := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetScalars().GetDoubleData().GetData(); int(ref.rowIdx) < len(src) {
data[i] = src[ref.rowIdx]
}
}
newSf.Data = &schemapb.ScalarField_DoubleData{DoubleData: &schemapb.DoubleArray{Data: data}}
case *schemapb.ScalarField_StringData:
data := make([]string, len(selectedRows))
for i, ref := range selectedRows {
if src := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetScalars().GetStringData().GetData(); int(ref.rowIdx) < len(src) {
data[i] = src[ref.rowIdx]
}
}
newSf.Data = &schemapb.ScalarField_StringData{StringData: &schemapb.StringArray{Data: data}}
case *schemapb.ScalarField_BytesData:
data := make([][]byte, len(selectedRows))
for i, ref := range selectedRows {
if src := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetScalars().GetBytesData().GetData(); int(ref.rowIdx) < len(src) {
data[i] = src[ref.rowIdx]
}
}
newSf.Data = &schemapb.ScalarField_BytesData{BytesData: &schemapb.BytesArray{Data: data}}
case *schemapb.ScalarField_JsonData:
data := make([][]byte, len(selectedRows))
for i, ref := range selectedRows {
if src := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetScalars().GetJsonData().GetData(); int(ref.rowIdx) < len(src) {
data[i] = src[ref.rowIdx]
}
}
newSf.Data = &schemapb.ScalarField_JsonData{JsonData: &schemapb.JSONArray{Data: data}}
case *schemapb.ScalarField_ArrayData:
data := make([]*schemapb.ScalarField, len(selectedRows))
for i, ref := range selectedRows {
if src := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetScalars().GetArrayData().GetData(); int(ref.rowIdx) < len(src) {
data[i] = src[ref.rowIdx]
}
}
newSf.Data = &schemapb.ScalarField_ArrayData{ArrayData: &schemapb.ArrayArray{
Data: data,
ElementType: template.GetArrayData().GetElementType(),
}}
case *schemapb.ScalarField_GeometryData:
data := make([][]byte, len(selectedRows))
for i, ref := range selectedRows {
if src := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetScalars().GetGeometryData().GetData(); int(ref.rowIdx) < len(src) {
data[i] = src[ref.rowIdx]
}
}
newSf.Data = &schemapb.ScalarField_GeometryData{GeometryData: &schemapb.GeometryArray{Data: data}}
case *schemapb.ScalarField_GeometryWktData:
data := make([]string, len(selectedRows))
for i, ref := range selectedRows {
if src := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetScalars().GetGeometryWktData().GetData(); int(ref.rowIdx) < len(src) {
data[i] = src[ref.rowIdx]
}
}
newSf.Data = &schemapb.ScalarField_GeometryWktData{GeometryWktData: &schemapb.GeometryWktArray{Data: data}}
case *schemapb.ScalarField_TimestamptzData:
data := make([]int64, len(selectedRows))
for i, ref := range selectedRows {
if src := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetScalars().GetTimestamptzData().GetData(); int(ref.rowIdx) < len(src) {
data[i] = src[ref.rowIdx]
}
}
newSf.Data = &schemapb.ScalarField_TimestamptzData{TimestamptzData: &schemapb.TimestamptzArray{Data: data}}
case *schemapb.ScalarField_MolData:
data := make([][]byte, len(selectedRows))
for i, ref := range selectedRows {
if src := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetScalars().GetMolData().GetData(); int(ref.rowIdx) < len(src) {
data[i] = src[ref.rowIdx]
}
}
newSf.Data = &schemapb.ScalarField_MolData{MolData: &schemapb.MolArray{Data: data}}
}
return newSf
}
// buildCompactIndices pre-computes the compact data index for each result's vector field.
// In compact mode (nullable vectors), the data array only contains entries for valid rows.
// compactIdx[resultIdx][logicalRowIdx] = data array index, or -1 if null.
// Returns (nil, nil) for non-nullable fields (data index = row index).
//
// Returns error when the segcore contract is violated:
// - nullable field with numRows > 0 but empty ValidData
// - len(ValidData) != numRows
//
// These conditions indicate a segcore bug and must fail the request loudly
// rather than silently treating rows as null (which corrupts the downstream
// Contents/ValidData contract and causes index-out-of-range panics).
//
// Why hard-error instead of graceful fallback:
// All segcore paths that produce vector FieldData for nullable fields
// (SegmentGrowingImpl::bulk_subscript, ChunkedSegmentSealedImpl::get_raw_data,
// ChunkedSegmentSealedImpl::get_vector) call FilterVectorValidOffsets and write
// a full-length ValidData bitmap. The only path that omits ValidData is
// fill_with_empty(field_id, count) (2-arg overload, used when index is not ready),
// but that early-returns with numRows=count and no selected rows can reference it.
// Therefore, "empty ValidData + numRows > 0" in a merge input is never a legitimate
// state — it always indicates a segcore bug that must be surfaced, not masked.
func buildCompactIndices(results []*internalpb.RetrieveResults, fieldIdx int, isNullable bool) ([][]int, error) {
if !isNullable {
return nil, nil
}
indices := make([][]int, len(results))
for ri, r := range results {
numRows := typeutil.GetSizeOfIDs(r.GetIds())
fd := r.GetFieldsData()[fieldIdx]
vd := fd.GetValidData()
if numRows == 0 {
indices[ri] = nil
continue
}
// Hard-error, not graceful fallback. All segcore vector output paths for
// nullable fields populate ValidData:
// - get_raw_data: FilterVectorValidOffsets → fill_with_empty(4-arg) → ValidData set
// - get_vector: FilterVectorValidOffsets → CreateVectorDataArrayFrom → ValidData set
// - bulk_subscript (growing): FilterVectorValidOffsets → CreateEmptyVectorDataArray(4-arg) → ValidData set
// - bulk_subscript_not_exist_field: CreateEmptyVectorDataArray(0) + manual Add(false) × count → ValidData set
// The only path that omits ValidData is fill_with_empty(2-arg) when index is not ready,
// but that path is unreachable for merge inputs (HasRawData gate + empty IDs filtering).
// Therefore empty ValidData here is always a segcore bug, not a legitimate state.
if len(vd) == 0 {
return nil, merr.WrapErrServiceInternalMsg(
"buildCompactIndices: nullable vector field fid=%d name=%q has empty ValidData but numRows=%d in result[%d]; "+
"segcore must always provide ValidData for nullable fields with rows",
fd.GetFieldId(), fd.GetFieldName(), numRows, ri)
}
if len(vd) != numRows {
return nil, merr.WrapErrServiceInternalMsg(
"buildCompactIndices: nullable vector field fid=%d name=%q has len(ValidData)=%d but numRows=%d in result[%d]; "+
"segcore violated the nullable contract (len(ValidData) must equal numRows)",
fd.GetFieldId(), fd.GetFieldName(), len(vd), numRows, ri)
}
idx, _ := typeutil.BuildNullableVectorDataIndices(vd)
indices[ri] = idx
}
return indices, nil
}
func arrayOfVectorRowValid(fd *schemapb.FieldData, rowIdx int64, isNullable bool, numRows int, resultIdx int) (bool, error) {
if rowIdx < 0 {
return false, merr.WrapErrServiceInternalMsg(
"arrayOfVectorRowValid: field fid=%d name=%q in result[%d] has invalid rowIdx=%d",
fd.GetFieldId(), fd.GetFieldName(), resultIdx, rowIdx)
}
validData := fd.GetValidData()
if len(validData) == 0 {
if isNullable {
return false, merr.WrapErrServiceInternalMsg(
"arrayOfVectorRowValid: nullable ArrayOfVector field fid=%d name=%q has empty ValidData but numRows=%d in result[%d]; "+
"segcore must always provide ValidData for nullable fields with rows",
fd.GetFieldId(), fd.GetFieldName(), numRows, resultIdx)
}
return true, nil
}
if int(rowIdx) >= len(validData) {
return false, merr.WrapErrServiceInternalMsg(
"arrayOfVectorRowValid: ArrayOfVector field fid=%d name=%q in result[%d] has rowIdx=%d outside ValidData bounds len(ValidData)=%d",
fd.GetFieldId(), fd.GetFieldName(), resultIdx, rowIdx, len(validData))
}
if isNullable && numRows > 0 && len(validData) != numRows {
return false, merr.WrapErrServiceInternalMsg(
"arrayOfVectorRowValid: nullable ArrayOfVector field fid=%d name=%q has len(ValidData)=%d but numRows=%d in result[%d]; "+
"segcore violated the nullable contract (len(ValidData) must equal numRows)",
fd.GetFieldId(), fd.GetFieldName(), len(validData), numRows, resultIdx)
}
return validData[rowIdx], nil
}
// getVecDataIdx returns the compact data index for a vector row.
// Returns -1 if the row is null. If compactIndices is nil, returns rowIdx directly.
// Callers must call buildCompactIndices first, which validates len(vd) == numRows;
// after that validation passes, out-of-range access here is impossible for correctly
// constructed selectedRows. If it somehow happens, the Go runtime panics with a
// clear stack trace — no silent fallback.
func getVecDataIdx(compactIndices [][]int, ref rowRef) int {
if compactIndices == nil {
return int(ref.rowIdx)
}
ci := compactIndices[ref.resultIdx]
if ci == nil {
return int(ref.rowIdx)
}
return ci[ref.rowIdx]
}
// buildMergedVectorField builds merged vector field from selected rows.
// For nullable primitive vector fields, segcore uses compact mode: the data
// array only contains entries for valid (non-null) rows, and ValidData bitmap
// marks which logical rows are null. Null rows don't occupy space in the data
// array. buildCompactIndices/getVecDataIdx handle the logical→data index
// mapping. ArrayOfVector is row-dense in merged output: null rows are represented
// by empty per-row placeholders, so it keeps logical row indexes.
//
// Keep the ArrayOfVector row-dense contract aligned with the rest of the stack:
// internal/proxy/validate_util.go:408 validates that ArrayOfVector Data length
// equals numRows after null filling, and pkg/util/typeutil/schema.go:894 makes
// FieldDataIdxComputer index ArrayOfVector by logical row instead of compact
// vector data index.
//
// fieldSchema drives the type switch (DataType) and dim, avoiding the previous
// bug where template.GetData().(type) was used: when the template result had
// nil Data oneof (e.g., sparse field not yet in a segment via AlterCollection),
// the type switch missed all cases, producing nil Data in the merged output.
//
// fieldSchema may be nil for system fields or tests; in that case we fall back
// to scanning results for the first non-nil Data to infer the type.
func buildMergedVectorField(results []*internalpb.RetrieveResults, selectedRows []rowRef, fieldIdx int, fieldSchema *schemapb.FieldSchema) (*schemapb.VectorField, error) {
isNullable := fieldSchema != nil && fieldSchema.GetNullable()
// Determine DataType and dim from schema (preferred) or from result data (fallback).
dataType := schemapb.DataType_None
var dim int64
if fieldSchema != nil {
dataType = fieldSchema.GetDataType()
if !typeutil.IsSparseFloatVectorType(dataType) {
h := typeutil.CreateFieldSchemaHelper(fieldSchema)
dim, _ = h.GetDim()
}
}
// Fallback: if no schema, scan results for the first non-nil VectorField.Data
// to infer type and dim. This keeps backward compatibility with tests that pass
// nil schema.
if dataType == schemapb.DataType_None {
for _, ref := range selectedRows {
vec := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetVectors()
if vec != nil && vec.GetData() != nil {
dim = vec.GetDim()
switch vec.GetData().(type) {
case *schemapb.VectorField_FloatVector:
dataType = schemapb.DataType_FloatVector
case *schemapb.VectorField_BinaryVector:
dataType = schemapb.DataType_BinaryVector
case *schemapb.VectorField_Float16Vector:
dataType = schemapb.DataType_Float16Vector
case *schemapb.VectorField_Bfloat16Vector:
dataType = schemapb.DataType_BFloat16Vector
case *schemapb.VectorField_Int8Vector:
dataType = schemapb.DataType_Int8Vector
case *schemapb.VectorField_SparseFloatVector:
dataType = schemapb.DataType_SparseFloatVector
case *schemapb.VectorField_VectorArray:
dataType = schemapb.DataType_ArrayOfVector
}
break
}
}
}
newVf := &schemapb.VectorField{Dim: dim}
useCompactVectorIndex := isNullable && dataType != schemapb.DataType_ArrayOfVector
var compactIdx [][]int
if dataType != schemapb.DataType_ArrayOfVector {
var err error
compactIdx, err = buildCompactIndices(results, fieldIdx, useCompactVectorIndex)
if err != nil {
return nil, err
}
}
// vecDataOOB builds a descriptive error for vector data out-of-bounds access.
// This indicates segcore returned truncated/malformed vector data.
vecDataOOB := func(ref rowRef, di int, dataLen int) error {
fd := results[ref.resultIdx].GetFieldsData()[fieldIdx]
return merr.WrapErrServiceInternalMsg(
"buildMergedVectorField: vector data too short for %s field fid=%d name=%q in result[%d]: "+
"dataIdx=%d requires offset beyond data length %d (dim=%d, numRows=%d); segcore returned truncated data",
dataType, fd.GetFieldId(), fd.GetFieldName(), ref.resultIdx,
di, dataLen, dim, typeutil.GetSizeOfIDs(results[ref.resultIdx].GetIds()))
}
switch dataType {
case schemapb.DataType_FloatVector:
data := make([]float32, 0, len(selectedRows)*int(dim))
for _, ref := range selectedRows {
di := getVecDataIdx(compactIdx, ref)
if di < 0 {
continue
}
vec := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetVectors().GetFloatVector().GetData()
start := di * int(dim)
if start+int(dim) > len(vec) {
return nil, vecDataOOB(ref, di, len(vec))
}
data = append(data, vec[start:start+int(dim)]...)
}
newVf.Data = &schemapb.VectorField_FloatVector{FloatVector: &schemapb.FloatArray{Data: data}}
case schemapb.DataType_BinaryVector:
bytesPerRow := int(dim) / 8
data := make([]byte, 0, len(selectedRows)*bytesPerRow)
for _, ref := range selectedRows {
di := getVecDataIdx(compactIdx, ref)
if di < 0 {
continue
}
vec := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetVectors().GetBinaryVector()
start := di * bytesPerRow
if start+bytesPerRow > len(vec) {
return nil, vecDataOOB(ref, di, len(vec))
}
data = append(data, vec[start:start+bytesPerRow]...)
}
newVf.Data = &schemapb.VectorField_BinaryVector{BinaryVector: data}
case schemapb.DataType_Float16Vector:
bytesPerRow := int(dim) * 2
data := make([]byte, 0, len(selectedRows)*bytesPerRow)
for _, ref := range selectedRows {
di := getVecDataIdx(compactIdx, ref)
if di < 0 {
continue
}
vec := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetVectors().GetFloat16Vector()
start := di * bytesPerRow
if start+bytesPerRow > len(vec) {
return nil, vecDataOOB(ref, di, len(vec))
}
data = append(data, vec[start:start+bytesPerRow]...)
}
newVf.Data = &schemapb.VectorField_Float16Vector{Float16Vector: data}
case schemapb.DataType_BFloat16Vector:
bytesPerRow := int(dim) * 2
data := make([]byte, 0, len(selectedRows)*bytesPerRow)
for _, ref := range selectedRows {
di := getVecDataIdx(compactIdx, ref)
if di < 0 {
continue
}
vec := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetVectors().GetBfloat16Vector()
start := di * bytesPerRow
if start+bytesPerRow > len(vec) {
return nil, vecDataOOB(ref, di, len(vec))
}
data = append(data, vec[start:start+bytesPerRow]...)
}
newVf.Data = &schemapb.VectorField_Bfloat16Vector{Bfloat16Vector: data}
case schemapb.DataType_Int8Vector:
bytesPerRow := int(dim)
data := make([]byte, 0, len(selectedRows)*bytesPerRow)
for _, ref := range selectedRows {
di := getVecDataIdx(compactIdx, ref)
if di < 0 {
continue
}
vec := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetVectors().GetInt8Vector()
start := di * bytesPerRow
if start+bytesPerRow > len(vec) {
return nil, vecDataOOB(ref, di, len(vec))
}
data = append(data, vec[start:start+bytesPerRow]...)
}
newVf.Data = &schemapb.VectorField_Int8Vector{Int8Vector: data}
case schemapb.DataType_SparseFloatVector:
contents := make([][]byte, 0, len(selectedRows))
var maxDim int64
for _, ref := range selectedRows {
di := getVecDataIdx(compactIdx, ref)
if di < 0 {
continue
}
sparse := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetVectors().GetSparseFloatVector()
if sparse == nil || di >= len(sparse.GetContents()) {
fd := results[ref.resultIdx].GetFieldsData()[fieldIdx]
return nil, merr.WrapErrServiceInternalMsg(
"buildMergedVectorField: sparse vector data missing for field fid=%d name=%q in result[%d]: "+
"dataIdx=%d but SparseFloatArray is nil or has only %d contents (numRows=%d); segcore returned truncated data",
fd.GetFieldId(), fd.GetFieldName(), ref.resultIdx,
di, len(sparse.GetContents()), typeutil.GetSizeOfIDs(results[ref.resultIdx].GetIds()))
}
contents = append(contents, sparse.GetContents()[di])
if sparse.GetDim() > maxDim {
maxDim = sparse.GetDim()
}
}
newVf.Data = &schemapb.VectorField_SparseFloatVector{
SparseFloatVector: &schemapb.SparseFloatArray{
Contents: contents,
Dim: maxDim,
},
}
newVf.Dim = maxDim
case schemapb.DataType_ArrayOfVector:
elementType := schemapb.DataType_None
if fieldSchema != nil {
elementType = fieldSchema.GetElementType()
}
for _, ref := range selectedRows {
va := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetVectors().GetVectorArray()
if va != nil {
if dim == 0 && va.GetDim() != 0 {
dim = va.GetDim()
}
if elementType == schemapb.DataType_None && va.GetElementType() != schemapb.DataType_None {
elementType = va.GetElementType()
}
if dim != 0 && elementType != schemapb.DataType_None {
break
}
}
}
// ArrayOfVector must stay row-dense even for nullable all-null output.
// Do not use len(Data)==0 as an all-null signal: ValidData carries nullness,
// while Data keeps one placeholder per logical row.
data := make([]*schemapb.VectorField, 0, len(selectedRows))
for _, ref := range selectedRows {
fd := results[ref.resultIdx].GetFieldsData()[fieldIdx]
numRows := typeutil.GetSizeOfIDs(results[ref.resultIdx].GetIds())
rowValid, err := arrayOfVectorRowValid(fd, ref.rowIdx, isNullable, numRows, ref.resultIdx)
if err != nil {
return nil, err
}
di := getVecDataIdx(compactIdx, ref)
if di < 0 || !rowValid {
emptyRow, err := typeutil.NewEmptyArrayOfVectorRow(dim, elementType)
if err != nil {
return nil, err
}
data = append(data, emptyRow)
continue
}
va := fd.GetVectors().GetVectorArray()
if va == nil || len(va.GetData()) == 0 {
return nil, merr.WrapErrServiceInternalMsg(
"buildMergedVectorField: VectorArray data missing for field fid=%d name=%q in result[%d]: "+
"dataIdx=%d but VectorArray is nil or has no entries (numRows=%d); segcore returned truncated data",
fd.GetFieldId(), fd.GetFieldName(), ref.resultIdx,
di, typeutil.GetSizeOfIDs(results[ref.resultIdx].GetIds()))
}
if di >= len(va.GetData()) {
return nil, merr.WrapErrServiceInternalMsg(
"buildMergedVectorField: VectorArray data missing for field fid=%d name=%q in result[%d]: "+
"dataIdx=%d but VectorArray is nil or has only %d entries (numRows=%d); segcore returned truncated data",
fd.GetFieldId(), fd.GetFieldName(), ref.resultIdx,
di, len(va.GetData()), typeutil.GetSizeOfIDs(results[ref.resultIdx].GetIds()))
}
data = append(data, va.GetData()[di])
}
newVf.Data = &schemapb.VectorField_VectorArray{
VectorArray: &schemapb.VectorArray{
Dim: dim,
Data: data,
ElementType: elementType,
},
}
}
return newVf, nil
}
// rangeSliceRetrieveResults extracts a contiguous range [start, end) from a RetrieveResult.
// This is more efficient than sliceRetrieveResults for contiguous ranges since it uses
// direct sub-slicing instead of element-by-element copying.
func rangeSliceRetrieveResults(result *internalpb.RetrieveResults, start, end int) (*internalpb.RetrieveResults, error) {
if start >= end {
return &internalpb.RetrieveResults{}, nil
}
newResult := &internalpb.RetrieveResults{
Ids: rangeSliceIDs(result.GetIds(), start, end),
FieldsData: make([]*schemapb.FieldData, len(result.GetFieldsData())),
}
for i, fd := range result.GetFieldsData() {
sliced, err := rangeSliceFieldData(fd, start, end)
if err != nil {
return nil, err
}
newResult.FieldsData[i] = sliced
}
// Propagate element-level metadata
newResult.ElementLevel = result.GetElementLevel()
if result.GetElementLevel() && len(result.GetElementIndices()) > 0 {
newResult.ElementIndices = result.GetElementIndices()[start:end]
}
return newResult, nil
}
// rangeSliceIDs extracts a contiguous range [start, end) from IDs.
func rangeSliceIDs(ids *schemapb.IDs, start, end int) *schemapb.IDs {
if ids == nil {
return nil
}
switch ids.GetIdField().(type) {
case *schemapb.IDs_IntId:
data := ids.GetIntId().GetData()
return &schemapb.IDs{IdField: &schemapb.IDs_IntId{IntId: &schemapb.LongArray{Data: data[start:end]}}}
case *schemapb.IDs_StrId:
data := ids.GetStrId().GetData()
return &schemapb.IDs{IdField: &schemapb.IDs_StrId{StrId: &schemapb.StringArray{Data: data[start:end]}}}
}
return nil
}
// rangeSliceFieldData extracts a contiguous range [start, end) from field data.
func rangeSliceFieldData(fd *schemapb.FieldData, start, end int) (*schemapb.FieldData, error) {
if fd == nil {
return nil, nil
}
newFd := &schemapb.FieldData{
Type: fd.GetType(),
FieldName: fd.GetFieldName(),
FieldId: fd.GetFieldId(),
IsDynamic: fd.GetIsDynamic(),
}
switch fd.GetField().(type) {
case *schemapb.FieldData_Scalars:
newFd.Field = &schemapb.FieldData_Scalars{
Scalars: rangeSliceScalarField(fd.GetScalars(), start, end),
}
case *schemapb.FieldData_Vectors:
vectors, err := rangeSliceVectorField(fd.GetVectors(), start, end, fd.GetValidData())
if err != nil {
return nil, err
}
newFd.Field = &schemapb.FieldData_Vectors{
Vectors: vectors,
}
}
if len(fd.GetValidData()) > 0 {
newFd.ValidData = fd.GetValidData()[start:end]
}
return newFd, nil
}
// rangeSliceStructArrayField extracts a contiguous range [start, end) from each sub-field.
func rangeSliceStructArrayField(sa *schemapb.StructArrayField, start, end int) (*schemapb.StructArrayField, error) {
if sa == nil {
return nil, nil
}
newFields := make([]*schemapb.FieldData, len(sa.GetFields()))
for i, subFd := range sa.GetFields() {
sliced, err := rangeSliceFieldData(subFd, start, end)
if err != nil {
return nil, err
}
newFields[i] = sliced
}
return &schemapb.StructArrayField{Fields: newFields}, nil
}
// rangeSliceScalarField extracts a contiguous range [start, end) from scalar data.
func rangeSliceScalarField(sf *schemapb.ScalarField, start, end int) *schemapb.ScalarField {
newSf := &schemapb.ScalarField{}
switch sf.GetData().(type) {
case *schemapb.ScalarField_BoolData:
newSf.Data = &schemapb.ScalarField_BoolData{BoolData: &schemapb.BoolArray{Data: sf.GetBoolData().GetData()[start:end]}}
case *schemapb.ScalarField_IntData:
newSf.Data = &schemapb.ScalarField_IntData{IntData: &schemapb.IntArray{Data: sf.GetIntData().GetData()[start:end]}}
case *schemapb.ScalarField_LongData:
newSf.Data = &schemapb.ScalarField_LongData{LongData: &schemapb.LongArray{Data: sf.GetLongData().GetData()[start:end]}}
case *schemapb.ScalarField_FloatData:
newSf.Data = &schemapb.ScalarField_FloatData{FloatData: &schemapb.FloatArray{Data: sf.GetFloatData().GetData()[start:end]}}
case *schemapb.ScalarField_DoubleData:
newSf.Data = &schemapb.ScalarField_DoubleData{DoubleData: &schemapb.DoubleArray{Data: sf.GetDoubleData().GetData()[start:end]}}
case *schemapb.ScalarField_StringData:
newSf.Data = &schemapb.ScalarField_StringData{StringData: &schemapb.StringArray{Data: sf.GetStringData().GetData()[start:end]}}
case *schemapb.ScalarField_BytesData:
newSf.Data = &schemapb.ScalarField_BytesData{BytesData: &schemapb.BytesArray{Data: sf.GetBytesData().GetData()[start:end]}}
case *schemapb.ScalarField_JsonData:
newSf.Data = &schemapb.ScalarField_JsonData{JsonData: &schemapb.JSONArray{Data: sf.GetJsonData().GetData()[start:end]}}
case *schemapb.ScalarField_ArrayData:
newSf.Data = &schemapb.ScalarField_ArrayData{ArrayData: &schemapb.ArrayArray{
Data: sf.GetArrayData().GetData()[start:end],
ElementType: sf.GetArrayData().GetElementType(),
}}
case *schemapb.ScalarField_GeometryData:
newSf.Data = &schemapb.ScalarField_GeometryData{GeometryData: &schemapb.GeometryArray{Data: sf.GetGeometryData().GetData()[start:end]}}
case *schemapb.ScalarField_GeometryWktData:
newSf.Data = &schemapb.ScalarField_GeometryWktData{GeometryWktData: &schemapb.GeometryWktArray{Data: sf.GetGeometryWktData().GetData()[start:end]}}
case *schemapb.ScalarField_TimestamptzData:
newSf.Data = &schemapb.ScalarField_TimestamptzData{TimestamptzData: &schemapb.TimestamptzArray{Data: sf.GetTimestamptzData().GetData()[start:end]}}
case *schemapb.ScalarField_MolData:
newSf.Data = &schemapb.ScalarField_MolData{MolData: &schemapb.MolArray{Data: sf.GetMolData().GetData()[start:end]}}
}
return newSf
}
// rangeSliceVectorField extracts a contiguous range [start, end) from vector data.
// Supported nullable vectors use compact payload data, so logical rows must be
// mapped to physical vector rows through ValidData.
func rangeSliceVectorField(vf *schemapb.VectorField, start, end int, validData []bool) (*schemapb.VectorField, error) {
dim := int(vf.GetDim())
newVf := &schemapb.VectorField{Dim: vf.GetDim()}
dataStart, dataEnd := start, end
if usesCompactNullableVectorData(vf, validData) {
dataStart = 0
for i := 0; i < start; i++ {
if validData[i] {
dataStart++
}
}
dataEnd = dataStart
for i := start; i < end; i++ {
if validData[i] {
dataEnd++
}
}
}
switch vf.GetData().(type) {
case *schemapb.VectorField_FloatVector:
data := vf.GetFloatVector().GetData()
newVf.Data = &schemapb.VectorField_FloatVector{FloatVector: &schemapb.FloatArray{Data: data[dataStart*dim : dataEnd*dim]}}
case *schemapb.VectorField_BinaryVector:
bytesPerRow := dim / 8
data := vf.GetBinaryVector()
newVf.Data = &schemapb.VectorField_BinaryVector{BinaryVector: data[dataStart*bytesPerRow : dataEnd*bytesPerRow]}
case *schemapb.VectorField_Float16Vector:
bytesPerRow := dim * 2
data := vf.GetFloat16Vector()
newVf.Data = &schemapb.VectorField_Float16Vector{Float16Vector: data[dataStart*bytesPerRow : dataEnd*bytesPerRow]}
case *schemapb.VectorField_Bfloat16Vector:
bytesPerRow := dim * 2
data := vf.GetBfloat16Vector()
newVf.Data = &schemapb.VectorField_Bfloat16Vector{Bfloat16Vector: data[dataStart*bytesPerRow : dataEnd*bytesPerRow]}
case *schemapb.VectorField_Int8Vector:
bytesPerRow := dim // 1 byte per element
data := vf.GetInt8Vector()
newVf.Data = &schemapb.VectorField_Int8Vector{Int8Vector: data[dataStart*bytesPerRow : dataEnd*bytesPerRow]}
case *schemapb.VectorField_SparseFloatVector:
contents := vf.GetSparseFloatVector().GetContents()
newVf.Data = &schemapb.VectorField_SparseFloatVector{
SparseFloatVector: &schemapb.SparseFloatArray{
Contents: contents[dataStart:dataEnd],
Dim: vf.GetSparseFloatVector().GetDim(),
},
}
case *schemapb.VectorField_VectorArray:
data := vf.GetVectorArray().GetData()
var newData []*schemapb.VectorField
if len(validData) > 0 {
newData = make([]*schemapb.VectorField, 0, end-start)
for i := start; i < end; i++ {
if !validData[i] {
emptyRow, err := typeutil.NewEmptyArrayOfVectorRow(vf.GetVectorArray().GetDim(), vf.GetVectorArray().GetElementType())
if err != nil {
return nil, err
}
newData = append(newData, emptyRow)
continue
}
newData = append(newData, data[i])
}
} else {
newData = data[dataStart:dataEnd]
}
newVf.Data = &schemapb.VectorField_VectorArray{
VectorArray: &schemapb.VectorArray{
Dim: vf.GetVectorArray().GetDim(),
Data: newData,
ElementType: vf.GetVectorArray().GetElementType(),
},
}
}
return newVf, nil
}
// sliceIDs extracts IDs at the given indices.
func sliceIDs(ids *schemapb.IDs, indices []int) *schemapb.IDs {
if ids == nil || len(indices) == 0 {
return nil
}
switch ids.GetIdField().(type) {
case *schemapb.IDs_IntId:
data := ids.GetIntId().GetData()
newData := make([]int64, len(indices))
for i, idx := range indices {
newData[i] = data[idx]
}
return &schemapb.IDs{IdField: &schemapb.IDs_IntId{IntId: &schemapb.LongArray{Data: newData}}}
case *schemapb.IDs_StrId:
data := ids.GetStrId().GetData()
newData := make([]string, len(indices))
for i, idx := range indices {
newData[i] = data[idx]
}
return &schemapb.IDs{IdField: &schemapb.IDs_StrId{StrId: &schemapb.StringArray{Data: newData}}}
}
return nil
}
// sliceFieldData extracts field data at the given indices.
func sliceFieldData(fd *schemapb.FieldData, indices []int) (*schemapb.FieldData, error) {
if fd == nil || len(indices) == 0 {
return nil, nil
}
newFd := &schemapb.FieldData{
Type: fd.GetType(),
FieldName: fd.GetFieldName(),
FieldId: fd.GetFieldId(),
IsDynamic: fd.GetIsDynamic(),
}
switch fd.GetField().(type) {
case *schemapb.FieldData_Scalars:
newFd.Field = &schemapb.FieldData_Scalars{
Scalars: sliceScalarField(fd.GetScalars(), indices),
}
case *schemapb.FieldData_Vectors:
vectors, err := sliceVectorField(fd.GetVectors(), indices, fd.GetValidData())
if err != nil {
return nil, err
}
newFd.Field = &schemapb.FieldData_Vectors{
Vectors: vectors,
}
}
// Preserve ValidData (nullable bitmap) for nullable fields.
if len(fd.GetValidData()) > 0 {
validData := fd.GetValidData()
newValidData := make([]bool, len(indices))
for i, idx := range indices {
newValidData[i] = validData[idx]
}
newFd.ValidData = newValidData
}
return newFd, nil
}
// sliceScalarField extracts scalar data at the given indices.
// sliceStructArrayField extracts struct array sub-fields at the given indices.
func sliceStructArrayField(sa *schemapb.StructArrayField, indices []int) (*schemapb.StructArrayField, error) {
if sa == nil {
return nil, nil
}
newFields := make([]*schemapb.FieldData, len(sa.GetFields()))
for i, subFd := range sa.GetFields() {
sliced, err := sliceFieldData(subFd, indices)
if err != nil {
return nil, err
}
newFields[i] = sliced
}
return &schemapb.StructArrayField{Fields: newFields}, nil
}
func sliceScalarField(sf *schemapb.ScalarField, indices []int) *schemapb.ScalarField {
newSf := &schemapb.ScalarField{}
switch sf.GetData().(type) {
case *schemapb.ScalarField_BoolData:
data := sf.GetBoolData().GetData()
newData := make([]bool, len(indices))
for i, idx := range indices {
newData[i] = data[idx]
}
newSf.Data = &schemapb.ScalarField_BoolData{BoolData: &schemapb.BoolArray{Data: newData}}
case *schemapb.ScalarField_IntData:
data := sf.GetIntData().GetData()
newData := make([]int32, len(indices))
for i, idx := range indices {
newData[i] = data[idx]
}
newSf.Data = &schemapb.ScalarField_IntData{IntData: &schemapb.IntArray{Data: newData}}
case *schemapb.ScalarField_LongData:
data := sf.GetLongData().GetData()
newData := make([]int64, len(indices))
for i, idx := range indices {
newData[i] = data[idx]
}
newSf.Data = &schemapb.ScalarField_LongData{LongData: &schemapb.LongArray{Data: newData}}
case *schemapb.ScalarField_FloatData:
data := sf.GetFloatData().GetData()
newData := make([]float32, len(indices))
for i, idx := range indices {
newData[i] = data[idx]
}
newSf.Data = &schemapb.ScalarField_FloatData{FloatData: &schemapb.FloatArray{Data: newData}}
case *schemapb.ScalarField_DoubleData:
data := sf.GetDoubleData().GetData()
newData := make([]float64, len(indices))
for i, idx := range indices {
newData[i] = data[idx]
}
newSf.Data = &schemapb.ScalarField_DoubleData{DoubleData: &schemapb.DoubleArray{Data: newData}}
case *schemapb.ScalarField_StringData:
data := sf.GetStringData().GetData()
newData := make([]string, len(indices))
for i, idx := range indices {
newData[i] = data[idx]
}
newSf.Data = &schemapb.ScalarField_StringData{StringData: &schemapb.StringArray{Data: newData}}
case *schemapb.ScalarField_BytesData:
data := sf.GetBytesData().GetData()
newData := make([][]byte, len(indices))
for i, idx := range indices {
newData[i] = data[idx]
}
newSf.Data = &schemapb.ScalarField_BytesData{BytesData: &schemapb.BytesArray{Data: newData}}
case *schemapb.ScalarField_JsonData:
data := sf.GetJsonData().GetData()
newData := make([][]byte, len(indices))
for i, idx := range indices {
newData[i] = data[idx]
}
newSf.Data = &schemapb.ScalarField_JsonData{JsonData: &schemapb.JSONArray{Data: newData}}
case *schemapb.ScalarField_ArrayData:
data := sf.GetArrayData().GetData()
newData := make([]*schemapb.ScalarField, len(indices))
for i, idx := range indices {
newData[i] = data[idx]
}
newSf.Data = &schemapb.ScalarField_ArrayData{ArrayData: &schemapb.ArrayArray{
Data: newData,
ElementType: sf.GetArrayData().GetElementType(),
}}
case *schemapb.ScalarField_GeometryData:
data := sf.GetGeometryData().GetData()
newData := make([][]byte, len(indices))
for i, idx := range indices {
newData[i] = data[idx]
}
newSf.Data = &schemapb.ScalarField_GeometryData{GeometryData: &schemapb.GeometryArray{Data: newData}}
case *schemapb.ScalarField_GeometryWktData:
data := sf.GetGeometryWktData().GetData()
newData := make([]string, len(indices))
for i, idx := range indices {
newData[i] = data[idx]
}
newSf.Data = &schemapb.ScalarField_GeometryWktData{GeometryWktData: &schemapb.GeometryWktArray{Data: newData}}
case *schemapb.ScalarField_TimestamptzData:
data := sf.GetTimestamptzData().GetData()
newData := make([]int64, len(indices))
for i, idx := range indices {
newData[i] = data[idx]
}
newSf.Data = &schemapb.ScalarField_TimestamptzData{TimestamptzData: &schemapb.TimestamptzArray{Data: newData}}
case *schemapb.ScalarField_MolData:
data := sf.GetMolData().GetData()
newData := make([][]byte, len(indices))
for i, idx := range indices {
newData[i] = data[idx]
}
newSf.Data = &schemapb.ScalarField_MolData{MolData: &schemapb.MolArray{Data: newData}}
}
return newSf
}
// sliceVectorField extracts vector data at the given logical indices.
// Supported nullable vectors use compact payload data, so logical rows must be
// mapped to physical vector rows through ValidData.
func sliceVectorField(vf *schemapb.VectorField, indices []int, validData []bool) (*schemapb.VectorField, error) {
dim := int(vf.GetDim())
newVf := &schemapb.VectorField{Dim: vf.GetDim()}
var compactIdx []int
if usesCompactNullableVectorData(vf, validData) {
compactIdx, _ = typeutil.BuildNullableVectorDataIndices(validData)
}
toDataIdx := func(logicalIdx int) int {
if compactIdx == nil {
return logicalIdx
}
return compactIdx[logicalIdx]
}
// Count valid rows in indices for output capacity.
validCount := 0
for _, idx := range indices {
di := toDataIdx(idx)
if di >= 0 {
validCount++
}
}
switch vf.GetData().(type) {
case *schemapb.VectorField_FloatVector:
data := vf.GetFloatVector().GetData()
newData := make([]float32, 0, validCount*dim)
for _, idx := range indices {
di := toDataIdx(idx)
if di < 0 {
continue
}
newData = append(newData, data[di*dim:(di+1)*dim]...)
}
newVf.Data = &schemapb.VectorField_FloatVector{FloatVector: &schemapb.FloatArray{Data: newData}}
case *schemapb.VectorField_BinaryVector:
bytesPerRow := dim / 8
data := vf.GetBinaryVector()
newData := make([]byte, 0, validCount*bytesPerRow)
for _, idx := range indices {
di := toDataIdx(idx)
if di < 0 {
continue
}
newData = append(newData, data[di*bytesPerRow:(di+1)*bytesPerRow]...)
}
newVf.Data = &schemapb.VectorField_BinaryVector{BinaryVector: newData}
case *schemapb.VectorField_Float16Vector:
bytesPerRow := dim * 2
data := vf.GetFloat16Vector()
newData := make([]byte, 0, validCount*bytesPerRow)
for _, idx := range indices {
di := toDataIdx(idx)
if di < 0 {
continue
}
newData = append(newData, data[di*bytesPerRow:(di+1)*bytesPerRow]...)
}
newVf.Data = &schemapb.VectorField_Float16Vector{Float16Vector: newData}
case *schemapb.VectorField_Bfloat16Vector:
bytesPerRow := dim * 2
data := vf.GetBfloat16Vector()
newData := make([]byte, 0, validCount*bytesPerRow)
for _, idx := range indices {
di := toDataIdx(idx)
if di < 0 {
continue
}
newData = append(newData, data[di*bytesPerRow:(di+1)*bytesPerRow]...)
}
newVf.Data = &schemapb.VectorField_Bfloat16Vector{Bfloat16Vector: newData}
case *schemapb.VectorField_Int8Vector:
bytesPerRow := dim // 1 byte per element
data := vf.GetInt8Vector()
newData := make([]byte, 0, validCount*bytesPerRow)
for _, idx := range indices {
di := toDataIdx(idx)
if di < 0 {
continue
}
newData = append(newData, data[di*bytesPerRow:(di+1)*bytesPerRow]...)
}
newVf.Data = &schemapb.VectorField_Int8Vector{Int8Vector: newData}
case *schemapb.VectorField_SparseFloatVector:
contents := vf.GetSparseFloatVector().GetContents()
newContents := make([][]byte, 0, validCount)
for _, idx := range indices {
di := toDataIdx(idx)
if di < 0 {
continue
}
newContents = append(newContents, contents[di])
}
newVf.Data = &schemapb.VectorField_SparseFloatVector{
SparseFloatVector: &schemapb.SparseFloatArray{
Contents: newContents,
Dim: vf.GetSparseFloatVector().GetDim(),
},
}
case *schemapb.VectorField_VectorArray:
srcData := vf.GetVectorArray().GetData()
newData := make([]*schemapb.VectorField, 0, len(indices))
for _, idx := range indices {
di := toDataIdx(idx)
if di < 0 {
continue
}
if len(validData) > 0 && !validData[idx] {
emptyRow, err := typeutil.NewEmptyArrayOfVectorRow(vf.GetVectorArray().GetDim(), vf.GetVectorArray().GetElementType())
if err != nil {
return nil, err
}
newData = append(newData, emptyRow)
continue
}
newData = append(newData, srcData[di])
}
newVf.Data = &schemapb.VectorField_VectorArray{
VectorArray: &schemapb.VectorArray{
Dim: vf.GetVectorArray().GetDim(),
Data: newData,
ElementType: vf.GetVectorArray().GetElementType(),
},
}
}
return newVf, nil
}
func usesCompactNullableVectorData(vf *schemapb.VectorField, validData []bool) bool {
if len(validData) == 0 {
return false
}
switch vf.GetData().(type) {
case *schemapb.VectorField_FloatVector,
*schemapb.VectorField_BinaryVector,
*schemapb.VectorField_Float16Vector,
*schemapb.VectorField_Bfloat16Vector,
*schemapb.VectorField_Int8Vector,
*schemapb.VectorField_SparseFloatVector:
return true
default:
return false
}
}
// calcRowSize computes the size in bytes of a single row in a RetrieveResult
// by summing the per-element size of each field. This is used during the
// merge selection phase (Phase 1) to track accumulated output size before
// the actual memory-copy phase (Phase 2), enabling early termination
// when maxOutputSize would be exceeded.
type rowSizeCalculator struct {
result *internalpb.RetrieveResults
compactIndices [][]int
}
func newRowSizeCalculator(result *internalpb.RetrieveResults) *rowSizeCalculator {
fieldsData := result.GetFieldsData()
c := &rowSizeCalculator{
result: result,
compactIndices: make([][]int, len(fieldsData)),
}
for fieldIdx, fd := range fieldsData {
if typeutil.IsCompactNullableVectorFieldData(fd) {
indices, _ := typeutil.BuildNullableVectorDataIndices(fd.GetValidData())
c.compactIndices[fieldIdx] = indices
}
}
return c
}
func (c *rowSizeCalculator) rowSize(rowIdx int64) int64 {
var size int64
for fieldIdx, fd := range c.result.GetFieldsData() {
size += calcFieldElementSizeWithCompactIndex(fd, int(rowIdx), c.compactIndices[fieldIdx])
}
return size
}
// calcRowSize is a convenience wrapper for one-off checks. Hot merge loops
// should create one rowSizeCalculator per result and reuse it.
func calcRowSize(result *internalpb.RetrieveResults, rowIdx int64) int64 {
return newRowSizeCalculator(result).rowSize(rowIdx)
}
// calcFieldElementSize returns the byte size of a single
// element at rowIdx within a FieldData.
func calcFieldElementSize(fd *schemapb.FieldData, rowIdx int) int64 {
var compactIdx []int
if typeutil.IsCompactNullableVectorFieldData(fd) {
compactIdx, _ = typeutil.BuildNullableVectorDataIndices(fd.GetValidData())
}
return calcFieldElementSizeWithCompactIndex(fd, rowIdx, compactIdx)
}
func calcFieldElementSizeWithCompactIndex(fd *schemapb.FieldData, rowIdx int, compactIdx []int) int64 {
if scalars := fd.GetScalars(); scalars != nil {
switch data := scalars.GetData().(type) {
case *schemapb.ScalarField_BoolData:
return 1
case *schemapb.ScalarField_IntData:
return 4
case *schemapb.ScalarField_LongData:
return 8
case *schemapb.ScalarField_FloatData:
return 4
case *schemapb.ScalarField_DoubleData:
return 8
case *schemapb.ScalarField_StringData:
d := data.StringData.GetData()
if rowIdx < len(d) {
return int64(len(d[rowIdx]))
}
return 0
case *schemapb.ScalarField_BytesData:
d := data.BytesData.GetData()
if rowIdx < len(d) {
return int64(len(d[rowIdx]))
}
return 0
case *schemapb.ScalarField_JsonData:
d := data.JsonData.GetData()
if rowIdx < len(d) {
return int64(len(d[rowIdx]))
}
return 0
case *schemapb.ScalarField_ArrayData:
d := data.ArrayData.GetData()
if rowIdx < len(d) {
return int64(proto.Size(d[rowIdx]))
}
return 0
case *schemapb.ScalarField_GeometryData:
d := data.GeometryData.GetData()
if rowIdx < len(d) {
return int64(len(d[rowIdx]))
}
return 0
case *schemapb.ScalarField_GeometryWktData:
d := data.GeometryWktData.GetData()
if rowIdx < len(d) {
return int64(len(d[rowIdx]))
}
return 0
case *schemapb.ScalarField_TimestamptzData:
return 8
case *schemapb.ScalarField_MolData:
d := data.MolData.GetData()
if rowIdx < len(d) {
return int64(len(d[rowIdx]))
}
return 0
}
}
if vectors := fd.GetVectors(); vectors != nil {
if compactIdx != nil {
if rowIdx < 0 || rowIdx >= len(compactIdx) || compactIdx[rowIdx] < 0 {
return 0
}
}
dim := int(vectors.GetDim())
switch vectors.GetData().(type) {
case *schemapb.VectorField_FloatVector:
return int64(dim * 4)
case *schemapb.VectorField_BinaryVector:
return int64(dim / 8)
case *schemapb.VectorField_Float16Vector:
return int64(dim * 2)
case *schemapb.VectorField_Bfloat16Vector:
return int64(dim * 2)
case *schemapb.VectorField_Int8Vector:
return int64(dim)
case *schemapb.VectorField_SparseFloatVector:
dataIdx := rowIdx
if compactIdx != nil {
dataIdx = compactIdx[rowIdx]
}
contents := vectors.GetSparseFloatVector().GetContents()
if dataIdx < len(contents) {
return int64(len(contents[dataIdx]))
}
return 0
case *schemapb.VectorField_VectorArray:
d := vectors.GetVectorArray().GetData()
if rowIdx < len(d) {
return int64(proto.Size(d[rowIdx]))
}
return 0
}
}
return 0
}
// getFieldValue extracts the value at rowIdx from field data
// Returns (value, isNull)
func getFieldValue(fd *schemapb.FieldData, rowIdx int) (any, bool) {
// Check valid_data for nullable fields
validData := fd.GetValidData()
if len(validData) > rowIdx && !validData[rowIdx] {
return nil, true
}
scalars := fd.GetScalars()
if scalars == nil {
return nil, true
}
switch scalars.GetData().(type) {
case *schemapb.ScalarField_BoolData:
data := scalars.GetBoolData().GetData()
if rowIdx >= len(data) {
return nil, true
}
return data[rowIdx], false
case *schemapb.ScalarField_IntData:
data := scalars.GetIntData().GetData()
if rowIdx >= len(data) {
return nil, true
}
return data[rowIdx], false
case *schemapb.ScalarField_LongData:
data := scalars.GetLongData().GetData()
if rowIdx >= len(data) {
return nil, true
}
return data[rowIdx], false
case *schemapb.ScalarField_FloatData:
data := scalars.GetFloatData().GetData()
if rowIdx >= len(data) {
return nil, true
}
return data[rowIdx], false
case *schemapb.ScalarField_DoubleData:
data := scalars.GetDoubleData().GetData()
if rowIdx >= len(data) {
return nil, true
}
return data[rowIdx], false
case *schemapb.ScalarField_StringData:
data := scalars.GetStringData().GetData()
if rowIdx >= len(data) {
return nil, true
}
return data[rowIdx], false
}
return nil, true
}
// getRowCount returns the number of rows in the result.
func getRowCount(result *internalpb.RetrieveResults) int {
// Try to get count from IDs first (most reliable)
if result.GetIds() != nil {
return typeutil.GetSizeOfIDs(result.GetIds())
}
// Fall back to field data
if len(result.GetFieldsData()) == 0 {
return 0
}
fd := result.GetFieldsData()[0]
if len(fd.GetValidData()) > 0 {
return len(fd.GetValidData())
}
if fd.GetScalars() != nil {
switch data := fd.GetScalars().GetData().(type) {
case *schemapb.ScalarField_BoolData:
return len(data.BoolData.GetData())
case *schemapb.ScalarField_IntData:
return len(data.IntData.GetData())
case *schemapb.ScalarField_LongData:
return len(data.LongData.GetData())
case *schemapb.ScalarField_FloatData:
return len(data.FloatData.GetData())
case *schemapb.ScalarField_DoubleData:
return len(data.DoubleData.GetData())
case *schemapb.ScalarField_StringData:
return len(data.StringData.GetData())
case *schemapb.ScalarField_JsonData:
return len(data.JsonData.GetData())
case *schemapb.ScalarField_ArrayData:
return len(data.ArrayData.GetData())
case *schemapb.ScalarField_GeometryData:
return len(data.GeometryData.GetData())
case *schemapb.ScalarField_GeometryWktData:
return len(data.GeometryWktData.GetData())
case *schemapb.ScalarField_TimestamptzData:
return len(data.TimestamptzData.GetData())
case *schemapb.ScalarField_MolData:
return len(data.MolData.GetData())
}
}
if fd.GetVectors() != nil {
dim := int(fd.GetVectors().GetDim())
if dim == 0 {
return 0
}
switch data := fd.GetVectors().GetData().(type) {
case *schemapb.VectorField_FloatVector:
return len(data.FloatVector.GetData()) / dim
case *schemapb.VectorField_BinaryVector:
return len(data.BinaryVector) / (dim / 8)
case *schemapb.VectorField_Float16Vector:
return len(data.Float16Vector) / (dim * 2)
case *schemapb.VectorField_Bfloat16Vector:
return len(data.Bfloat16Vector) / (dim * 2)
case *schemapb.VectorField_Int8Vector:
return len(data.Int8Vector) / dim
case *schemapb.VectorField_SparseFloatVector:
return len(data.SparseFloatVector.GetContents())
case *schemapb.VectorField_VectorArray:
return len(data.VectorArray.GetData())
}
}
if fd.GetStructArrays() != nil && len(fd.GetStructArrays().GetFields()) > 0 {
// Use the first sub-field's row count
return getRowCount(&internalpb.RetrieveResults{
FieldsData: []*schemapb.FieldData{fd.GetStructArrays().GetFields()[0]},
})
}
return 0
}
// compareValues compares two non-null values of the given data type
// Returns -1 if a < b, 0 if equal, 1 if a > b
func compareValues(a, b any, dataType schemapb.DataType) int {
switch dataType {
case schemapb.DataType_Bool:
va := a.(bool)
vb := b.(bool)
if !va && vb {
return -1
}
if va && !vb {
return 1
}
return 0
case schemapb.DataType_Int8, schemapb.DataType_Int16, schemapb.DataType_Int32:
va := a.(int32)
vb := b.(int32)
if va < vb {
return -1
}
if va > vb {
return 1
}
return 0
case schemapb.DataType_Int64:
va := a.(int64)
vb := b.(int64)
if va < vb {
return -1
}
if va > vb {
return 1
}
return 0
case schemapb.DataType_Float:
va := a.(float32)
vb := b.(float32)
if va < vb {
return -1
}
if va > vb {
return 1
}
return 0
case schemapb.DataType_Double:
va := a.(float64)
vb := b.(float64)
if va < vb {
return -1
}
if va > vb {
return 1
}
return 0
case schemapb.DataType_String, schemapb.DataType_VarChar:
va := a.(string)
vb := b.(string)
if va < vb {
return -1
}
if va > vb {
return 1
}
return 0
}
return 0
}