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

1432 lines
45 KiB
Go

package storage
import (
"bytes"
"context"
"strconv"
"time"
"github.com/apache/arrow/go/v17/arrow"
"github.com/apache/arrow/go/v17/arrow/array"
"github.com/apache/arrow/go/v17/arrow/memory"
"github.com/apache/arrow/go/v17/parquet"
"github.com/apache/arrow/go/v17/parquet/file"
"github.com/apache/arrow/go/v17/parquet/pqarrow"
"google.golang.org/protobuf/proto"
"github.com/milvus-io/milvus-proto/go-api/v3/schemapb"
"github.com/milvus-io/milvus/pkg/v3/mlog"
"github.com/milvus-io/milvus/pkg/v3/util/merr"
"github.com/milvus-io/milvus/pkg/v3/util/typeutil"
)
// PayloadReader reads data from payload
type PayloadReader struct {
reader *file.Reader
colType schemapb.DataType
numRows int64
nullable bool
// For VectorArray type
elementType schemapb.DataType
// For VectorArray type
dim int64
}
var _ PayloadReaderInterface = (*PayloadReader)(nil)
func NewPayloadReader(colType schemapb.DataType, buf []byte, nullable bool) (*PayloadReader, error) {
if len(buf) == 0 {
return nil, merr.WrapErrDataIntegrityMsg("create Payload reader failed, buffer is empty")
}
parquetReader, err := file.NewParquetReader(bytes.NewReader(buf))
if err != nil {
return nil, err
}
reader := &PayloadReader{
reader: parquetReader,
colType: colType,
numRows: parquetReader.NumRows(),
nullable: nullable,
}
if colType == schemapb.DataType_ArrayOfVector {
arrowReader, err := pqarrow.NewFileReader(parquetReader, pqarrow.ArrowReadProperties{BatchSize: 1024}, memory.DefaultAllocator)
if err != nil {
return nil, merr.WrapErrStorage(err, "failed to create arrow reader for VectorArray")
}
arrowSchema, err := arrowReader.Schema()
if err != nil {
return nil, merr.WrapErrStorage(err, "failed to get arrow schema for VectorArray")
}
if arrowSchema.NumFields() != 1 {
return nil, merr.WrapErrDataIntegrityMsg("VectorArray should have exactly 1 field, got %d", arrowSchema.NumFields())
}
field := arrowSchema.Field(0)
if !field.HasMetadata() {
return nil, merr.WrapErrDataIntegrityMsg("VectorArray field is missing metadata")
}
metadata := field.Metadata
elementTypeStr, ok := metadata.GetValue("elementType")
if !ok {
return nil, merr.WrapErrDataIntegrityMsg("VectorArray metadata missing required 'elementType' field")
}
elementTypeInt, err := strconv.ParseInt(elementTypeStr, 10, 32)
if err != nil {
return nil, merr.WrapErrDataIntegrityMsg("invalid elementType in VectorArray metadata: %s", elementTypeStr)
}
elementType := schemapb.DataType(elementTypeInt)
switch elementType {
case schemapb.DataType_FloatVector,
schemapb.DataType_BinaryVector,
schemapb.DataType_Float16Vector,
schemapb.DataType_BFloat16Vector,
schemapb.DataType_Int8Vector,
schemapb.DataType_SparseFloatVector:
reader.elementType = elementType
default:
return nil, merr.WrapErrDataIntegrityMsg("invalid vector type for VectorArray: %s", elementType.String())
}
dimStr, ok := metadata.GetValue("dim")
if !ok {
return nil, merr.WrapErrDataIntegrityMsg("VectorArray metadata missing required 'dim' field")
}
dimVal, err := strconv.ParseInt(dimStr, 10, 64)
if err != nil {
return nil, merr.WrapErrDataIntegrityMsg("invalid dim in VectorArray metadata: %s", dimStr)
}
if dimVal <= 0 {
return nil, merr.WrapErrDataIntegrityMsg("VectorArray dim must be positive, got %d", dimVal)
}
reader.dim = dimVal
}
return reader, nil
}
// GetDataFromPayload returns data,length from payload, returns err if failed
// Return:
//
// `interface{}`: all types.
// `[]bool`: validData, only meaningful to ScalarField.
// `int`: dim, only meaningful to FLOAT/BINARY VECTOR type.
// `error`: error.
func (r *PayloadReader) GetDataFromPayload() (interface{}, []bool, int, error) {
switch r.colType {
case schemapb.DataType_Bool:
val, validData, err := r.GetBoolFromPayload()
return val, validData, 0, err
case schemapb.DataType_Int8:
val, validData, err := r.GetInt8FromPayload()
return val, validData, 0, err
case schemapb.DataType_Int16:
val, validData, err := r.GetInt16FromPayload()
return val, validData, 0, err
case schemapb.DataType_Int32:
val, validData, err := r.GetInt32FromPayload()
return val, validData, 0, err
case schemapb.DataType_Int64:
val, validData, err := r.GetInt64FromPayload()
return val, validData, 0, err
case schemapb.DataType_Float:
val, validData, err := r.GetFloatFromPayload()
return val, validData, 0, err
case schemapb.DataType_Double:
val, validData, err := r.GetDoubleFromPayload()
return val, validData, 0, err
case schemapb.DataType_Timestamptz:
val, validData, err := r.GetTimestamptzFromPayload()
return val, validData, 0, err
case schemapb.DataType_BinaryVector:
val, dim, validData, _, err := r.GetBinaryVectorFromPayload()
return val, validData, dim, err
case schemapb.DataType_FloatVector:
val, dim, validData, _, err := r.GetFloatVectorFromPayload()
return val, validData, dim, err
case schemapb.DataType_Float16Vector:
val, dim, validData, _, err := r.GetFloat16VectorFromPayload()
return val, validData, dim, err
case schemapb.DataType_BFloat16Vector:
val, dim, validData, _, err := r.GetBFloat16VectorFromPayload()
return val, validData, dim, err
case schemapb.DataType_SparseFloatVector:
val, dim, validData, err := r.GetSparseFloatVectorFromPayload()
return val, validData, dim, err
case schemapb.DataType_Int8Vector:
val, dim, validData, _, err := r.GetInt8VectorFromPayload()
return val, validData, dim, err
case schemapb.DataType_String, schemapb.DataType_VarChar:
val, validData, err := r.GetStringFromPayload()
return val, validData, 0, err
case schemapb.DataType_Array:
val, validData, err := r.GetArrayFromPayload()
return val, validData, 0, err
case schemapb.DataType_ArrayOfVector:
val, err := r.GetVectorArrayFromPayload()
return val, nil, 0, err
case schemapb.DataType_JSON:
val, validData, err := r.GetJSONFromPayload()
return val, validData, 0, err
case schemapb.DataType_Geometry:
val, validData, err := r.GetGeometryFromPayload()
return val, validData, 0, err
default:
return nil, nil, 0, merr.WrapErrDataIntegrityMsg("unknown type")
}
}
// ReleasePayloadReader release payload reader.
func (r *PayloadReader) ReleasePayloadReader() error {
return r.Close()
}
// GetBoolFromPayload returns bool slice from payload.
func (r *PayloadReader) GetBoolFromPayload() ([]bool, []bool, error) {
if r.colType != schemapb.DataType_Bool {
return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get bool from datatype %v", r.colType.String())
}
values := make([]bool, r.numRows)
if r.nullable {
validData := make([]bool, r.numRows)
valuesRead, err := ReadData[bool, *array.Boolean](r.reader, values, validData, r.numRows)
if err != nil {
return nil, nil, err
}
if valuesRead != r.numRows {
return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead)
}
return values, validData, nil
}
valuesRead, err := ReadDataFromAllRowGroups[bool, *file.BooleanColumnChunkReader](r.reader, values, 0, r.numRows)
if err != nil {
return nil, nil, err
}
if valuesRead != r.numRows {
return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead)
}
return values, nil, nil
}
// GetByteFromPayload returns byte slice from payload
func (r *PayloadReader) GetByteFromPayload() ([]byte, []bool, error) {
if r.colType != schemapb.DataType_Int8 {
return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get byte from datatype %v", r.colType.String())
}
if r.nullable {
values := make([]int32, r.numRows)
validData := make([]bool, r.numRows)
valuesRead, err := ReadData[int32, *array.Int32](r.reader, values, validData, r.numRows)
if err != nil {
return nil, nil, err
}
if valuesRead != r.numRows {
return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead)
}
ret := make([]byte, r.numRows)
for i := int64(0); i < r.numRows; i++ {
ret[i] = byte(values[i])
}
return ret, validData, nil
}
values := make([]int32, r.numRows)
valuesRead, err := ReadDataFromAllRowGroups[int32, *file.Int32ColumnChunkReader](r.reader, values, 0, r.numRows)
if err != nil {
return nil, nil, err
}
if valuesRead != r.numRows {
return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead)
}
ret := make([]byte, r.numRows)
for i := int64(0); i < r.numRows; i++ {
ret[i] = byte(values[i])
}
return ret, nil, nil
}
func (r *PayloadReader) GetInt8FromPayload() ([]int8, []bool, error) {
if r.colType != schemapb.DataType_Int8 {
return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get int8 from datatype %v", r.colType.String())
}
if r.nullable {
values := make([]int8, r.numRows)
validData := make([]bool, r.numRows)
valuesRead, err := ReadData[int8, *array.Int8](r.reader, values, validData, r.numRows)
if err != nil {
return nil, nil, err
}
if valuesRead != r.numRows {
return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead)
}
return values, validData, nil
}
values := make([]int32, r.numRows)
valuesRead, err := ReadDataFromAllRowGroups[int32, *file.Int32ColumnChunkReader](r.reader, values, 0, r.numRows)
if err != nil {
return nil, nil, err
}
if valuesRead != r.numRows {
return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead)
}
ret := make([]int8, r.numRows)
for i := int64(0); i < r.numRows; i++ {
ret[i] = int8(values[i])
}
return ret, nil, nil
}
func (r *PayloadReader) GetInt16FromPayload() ([]int16, []bool, error) {
if r.colType != schemapb.DataType_Int16 {
return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get int16 from datatype %v", r.colType.String())
}
if r.nullable {
values := make([]int16, r.numRows)
validData := make([]bool, r.numRows)
valuesRead, err := ReadData[int16, *array.Int16](r.reader, values, validData, r.numRows)
if err != nil {
return nil, nil, err
}
if valuesRead != r.numRows {
return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead)
}
return values, validData, nil
}
values := make([]int32, r.numRows)
valuesRead, err := ReadDataFromAllRowGroups[int32, *file.Int32ColumnChunkReader](r.reader, values, 0, r.numRows)
if err != nil {
return nil, nil, err
}
if valuesRead != r.numRows {
return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead)
}
ret := make([]int16, r.numRows)
for i := int64(0); i < r.numRows; i++ {
ret[i] = int16(values[i])
}
return ret, nil, nil
}
func (r *PayloadReader) GetInt32FromPayload() ([]int32, []bool, error) {
if r.colType != schemapb.DataType_Int32 {
return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get int32 from datatype %v", r.colType.String())
}
values := make([]int32, r.numRows)
if r.nullable {
validData := make([]bool, r.numRows)
valuesRead, err := ReadData[int32, *array.Int32](r.reader, values, validData, r.numRows)
if err != nil {
return nil, nil, err
}
if valuesRead != r.numRows {
return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead)
}
return values, validData, nil
}
valuesRead, err := ReadDataFromAllRowGroups[int32, *file.Int32ColumnChunkReader](r.reader, values, 0, r.numRows)
if err != nil {
return nil, nil, err
}
if valuesRead != r.numRows {
return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead)
}
return values, nil, nil
}
func (r *PayloadReader) GetInt64FromPayload() ([]int64, []bool, error) {
if r.colType != schemapb.DataType_Int64 {
return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get int64 from datatype %v", r.colType.String())
}
values := make([]int64, r.numRows)
if r.nullable {
validData := make([]bool, r.numRows)
valuesRead, err := ReadData[int64, *array.Int64](r.reader, values, validData, r.numRows)
if err != nil {
return nil, nil, err
}
if valuesRead != r.numRows {
return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead)
}
return values, validData, nil
}
valuesRead, err := ReadDataFromAllRowGroups[int64, *file.Int64ColumnChunkReader](r.reader, values, 0, r.numRows)
if err != nil {
return nil, nil, err
}
if valuesRead != r.numRows {
return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead)
}
return values, nil, nil
}
func (r *PayloadReader) GetFloatFromPayload() ([]float32, []bool, error) {
if r.colType != schemapb.DataType_Float {
return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get float32 from datatype %v", r.colType.String())
}
values := make([]float32, r.numRows)
if r.nullable {
validData := make([]bool, r.numRows)
valuesRead, err := ReadData[float32, *array.Float32](r.reader, values, validData, r.numRows)
if err != nil {
return nil, nil, err
}
if valuesRead != r.numRows {
return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead)
}
return values, validData, nil
}
valuesRead, err := ReadDataFromAllRowGroups[float32, *file.Float32ColumnChunkReader](r.reader, values, 0, r.numRows)
if err != nil {
return nil, nil, err
}
if valuesRead != r.numRows {
return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead)
}
return values, nil, nil
}
func (r *PayloadReader) GetDoubleFromPayload() ([]float64, []bool, error) {
if r.colType != schemapb.DataType_Double {
return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get double from datatype %v", r.colType.String())
}
values := make([]float64, r.numRows)
if r.nullable {
validData := make([]bool, r.numRows)
valuesRead, err := ReadData[float64, *array.Float64](r.reader, values, validData, r.numRows)
if err != nil {
return nil, nil, err
}
if valuesRead != r.numRows {
return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead)
}
return values, validData, nil
}
valuesRead, err := ReadDataFromAllRowGroups[float64, *file.Float64ColumnChunkReader](r.reader, values, 0, r.numRows)
if err != nil {
return nil, nil, err
}
if valuesRead != r.numRows {
return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead)
}
return values, nil, nil
}
func (r *PayloadReader) GetTimestamptzFromPayload() ([]int64, []bool, error) {
if r.colType != schemapb.DataType_Timestamptz {
return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get timestamptz from datatype %v", r.colType.String())
}
values := make([]int64, r.numRows)
if r.nullable {
validData := make([]bool, r.numRows)
valuesRead, err := ReadData[int64, *array.Int64](r.reader, values, validData, r.numRows)
if err != nil {
return nil, nil, err
}
if valuesRead != r.numRows {
return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead)
}
return values, validData, nil
}
valuesRead, err := ReadDataFromAllRowGroups[int64, *file.Int64ColumnChunkReader](r.reader, values, 0, r.numRows)
if err != nil {
return nil, nil, err
}
if valuesRead != r.numRows {
return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead)
}
return values, nil, nil
}
func (r *PayloadReader) GetStringFromPayload() ([]string, []bool, error) {
if r.colType != schemapb.DataType_String && r.colType != schemapb.DataType_VarChar {
return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get string from datatype %v", r.colType.String())
}
if r.nullable {
values := make([]string, r.numRows)
validData := make([]bool, r.numRows)
valuesRead, err := ReadData[string, *array.String](r.reader, values, validData, r.numRows)
if err != nil {
return nil, nil, err
}
if valuesRead != r.numRows {
return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead)
}
return values, validData, nil
}
value, err := readByteAndConvert(r, func(bytes parquet.ByteArray) string {
return bytes.String()
})
if err != nil {
return nil, nil, err
}
return value, nil, nil
}
func (r *PayloadReader) GetArrayFromPayload() ([]*schemapb.ScalarField, []bool, error) {
if r.colType != schemapb.DataType_Array {
return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get array from datatype %v", r.colType.String())
}
if r.nullable {
return readNullableByteAndConvert(r, func(bytes []byte) *schemapb.ScalarField {
v := &schemapb.ScalarField{}
proto.Unmarshal(bytes, v)
return v
})
}
value, err := readByteAndConvert(r, func(bytes parquet.ByteArray) *schemapb.ScalarField {
v := &schemapb.ScalarField{}
proto.Unmarshal(bytes, v)
return v
})
if err != nil {
return nil, nil, err
}
return value, nil, nil
}
func (r *PayloadReader) GetVectorArrayFromPayload() ([]*schemapb.VectorField, error) {
if r.colType != schemapb.DataType_ArrayOfVector {
return nil, merr.WrapErrDataIntegrityMsg("failed to get vector from datatype %v", r.colType.String())
}
return readVectorArrayFromListArray(r)
}
func (r *PayloadReader) GetJSONFromPayload() ([][]byte, []bool, error) {
if r.colType != schemapb.DataType_JSON {
return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get json from datatype %v", r.colType.String())
}
if r.nullable {
return readNullableByteAndConvert(r, func(bytes []byte) []byte {
return bytes
})
}
value, err := readByteAndConvert(r, func(bytes parquet.ByteArray) []byte {
return bytes
})
if err != nil {
return nil, nil, err
}
return value, nil, nil
}
func (r *PayloadReader) GetGeometryFromPayload() ([][]byte, []bool, error) {
if r.colType != schemapb.DataType_Geometry {
return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get Geometry from datatype %v", r.colType.String())
}
if r.nullable {
return readNullableByteAndConvert(r, func(bytes []byte) []byte {
return bytes
})
}
value, err := readByteAndConvert(r, func(bytes parquet.ByteArray) []byte {
return bytes
})
if err != nil {
return nil, nil, err
}
return value, nil, nil
}
func (r *PayloadReader) GetByteArrayDataSet() (*DataSet[parquet.ByteArray, *file.ByteArrayColumnChunkReader], error) {
if r.colType != schemapb.DataType_String && r.colType != schemapb.DataType_VarChar {
return nil, merr.WrapErrServiceInternalMsg("failed to get string from datatype %v", r.colType.String())
}
return NewDataSet[parquet.ByteArray, *file.ByteArrayColumnChunkReader](r.reader, 0, r.numRows), nil
}
func (r *PayloadReader) GetArrowRecordReader() (pqarrow.RecordReader, error) {
arrowReader, err := pqarrow.NewFileReader(r.reader, pqarrow.ArrowReadProperties{BatchSize: 1024}, memory.DefaultAllocator)
if err != nil {
return nil, err
}
rr, err := arrowReader.GetRecordReader(context.Background(), nil, nil)
if err != nil {
return nil, err
}
return rr, nil
}
// readVectorArrayFromListArray reads VectorArray data stored as Arrow ListArray
func readVectorArrayFromListArray(r *PayloadReader) ([]*schemapb.VectorField, error) {
arrowReader, err := pqarrow.NewFileReader(r.reader, pqarrow.ArrowReadProperties{BatchSize: 1024}, memory.DefaultAllocator)
if err != nil {
return nil, err
}
defer arrowReader.ParquetReader().Close()
// Read all row groups
table, err := arrowReader.ReadTable(context.Background())
if err != nil {
return nil, err
}
defer table.Release()
if table.NumCols() != 1 {
return nil, merr.WrapErrDataIntegrityMsg("expected 1 column, got %d", table.NumCols())
}
column := table.Column(0)
if column.Len() == 0 {
return []*schemapb.VectorField{}, nil
}
result := make([]*schemapb.VectorField, 0, int(r.numRows))
elementType := r.elementType
dim := r.dim
for _, chunk := range column.Data().Chunks() {
listArray, ok := chunk.(*array.List)
if !ok {
return nil, merr.WrapErrDataIntegrityMsg("expected ListArray, got %T", chunk)
}
for i := 0; i < listArray.Len(); i++ {
value, err := deserializeArrayOfVector(listArray, i, elementType, dim, true)
if err != nil {
return nil, merr.Wrapf(err, "failed to deserialize VectorArray at row %d", len(result))
}
vectorField, _ := value.(*schemapb.VectorField)
if vectorField == nil {
return nil, merr.WrapErrDataIntegrityMsg("null value in VectorArray")
}
result = append(result, vectorField)
}
}
return result, nil
}
func readNullableByteAndConvert[T any](r *PayloadReader, convert func([]byte) T) ([]T, []bool, error) {
values := make([][]byte, r.numRows)
validData := make([]bool, r.numRows)
valuesRead, err := ReadData[[]byte, *array.Binary](r.reader, values, validData, r.numRows)
if err != nil {
return nil, nil, err
}
if valuesRead != r.numRows {
return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead)
}
ret := make([]T, r.numRows)
for i := 0; i < int(r.numRows); i++ {
ret[i] = convert(values[i])
}
return ret, validData, nil
}
func readByteAndConvert[T any](r *PayloadReader, convert func(parquet.ByteArray) T) ([]T, error) {
values := make([]parquet.ByteArray, r.numRows)
valuesRead, err := ReadDataFromAllRowGroups[parquet.ByteArray, *file.ByteArrayColumnChunkReader](r.reader, values, 0, r.numRows)
if err != nil {
return nil, err
}
if valuesRead != r.numRows {
return nil, merr.WrapErrDataIntegrityMsg("expect %d rows, but got valuesRead = %d", r.numRows, valuesRead)
}
ret := make([]T, r.numRows)
for i := 0; i < int(r.numRows); i++ {
ret[i] = convert(values[i])
}
return ret, nil
}
// GetBinaryVectorFromPayload returns vector, dimension, validData, numRows, error
func (r *PayloadReader) GetBinaryVectorFromPayload() ([]byte, int, []bool, int, error) {
if r.colType != schemapb.DataType_BinaryVector {
return nil, -1, nil, 0, merr.WrapErrServiceInternalMsg("failed to get binary vector from datatype %v", r.colType.String())
}
if r.nullable {
fileReader, err := pqarrow.NewFileReader(r.reader, pqarrow.ArrowReadProperties{}, memory.DefaultAllocator)
if err != nil {
return nil, -1, nil, 0, err
}
arrowSchema, err := fileReader.Schema()
if err != nil {
return nil, -1, nil, 0, err
}
if arrowSchema.NumFields() != 1 {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expected 1 field, got %d", arrowSchema.NumFields())
}
field := arrowSchema.Field(0)
var dim int
if field.Type.ID() == arrow.BINARY {
if !field.HasMetadata() {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("nullable binary vector field is missing metadata")
}
metadata := field.Metadata
dimStr, ok := metadata.GetValue("dim")
if !ok {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("nullable binary vector metadata missing required 'dim' field")
}
var err error
dim, err = strconv.Atoi(dimStr)
if err != nil {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("invalid dim value in metadata: %v", err)
}
dim = dim / 8
} else {
col, err := r.reader.RowGroup(0).Column(0)
if err != nil {
return nil, -1, nil, 0, err
}
dim = col.Descriptor().TypeLength()
}
table, err := fileReader.ReadTable(context.Background())
if err != nil {
return nil, -1, nil, 0, err
}
defer table.Release()
if table.NumCols() != 1 {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expected 1 column, got %d", table.NumCols())
}
column := table.Column(0)
ret, validData, err := readNullableVectorData(column, r.numRows, dim)
if err != nil {
return nil, -1, nil, 0, err
}
return ret, dim * 8, validData, int(r.numRows), nil
}
col, err := r.reader.RowGroup(0).Column(0)
if err != nil {
return nil, -1, nil, 0, err
}
dim := col.Descriptor().TypeLength()
values := make([]parquet.FixedLenByteArray, r.numRows)
valuesRead, err := ReadDataFromAllRowGroups[parquet.FixedLenByteArray, *file.FixedLenByteArrayColumnChunkReader](r.reader, values, 0, r.numRows)
if err != nil {
return nil, -1, nil, 0, err
}
if valuesRead != r.numRows {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expect %d rows, but got valuesRead = %d", r.numRows, valuesRead)
}
ret := make([]byte, int64(dim)*r.numRows)
for i := 0; i < int(r.numRows); i++ {
copy(ret[i*dim:(i+1)*dim], values[i])
}
return ret, dim * 8, nil, int(r.numRows), nil
}
// GetFloat16VectorFromPayload returns vector, dimension, validData, numRows, error
func (r *PayloadReader) GetFloat16VectorFromPayload() ([]byte, int, []bool, int, error) {
if r.colType != schemapb.DataType_Float16Vector {
return nil, -1, nil, 0, merr.WrapErrServiceInternalMsg("failed to get float16 vector from datatype %v", r.colType.String())
}
if r.nullable {
fileReader, err := pqarrow.NewFileReader(r.reader, pqarrow.ArrowReadProperties{}, memory.DefaultAllocator)
if err != nil {
return nil, -1, nil, 0, err
}
arrowSchema, err := fileReader.Schema()
if err != nil {
return nil, -1, nil, 0, err
}
if arrowSchema.NumFields() != 1 {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expected 1 field, got %d", arrowSchema.NumFields())
}
field := arrowSchema.Field(0)
var dim int
if field.Type.ID() == arrow.BINARY {
if !field.HasMetadata() {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("nullable float16 vector field is missing metadata")
}
metadata := field.Metadata
dimStr, ok := metadata.GetValue("dim")
if !ok {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("nullable float16 vector metadata missing required 'dim' field")
}
var err error
dim, err = strconv.Atoi(dimStr)
if err != nil {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("invalid dim value in metadata: %v", err)
}
} else {
col, err := r.reader.RowGroup(0).Column(0)
if err != nil {
return nil, -1, nil, 0, err
}
dim = col.Descriptor().TypeLength() / 2
}
table, err := fileReader.ReadTable(context.Background())
if err != nil {
return nil, -1, nil, 0, err
}
defer table.Release()
if table.NumCols() != 1 {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expected 1 column, got %d", table.NumCols())
}
column := table.Column(0)
ret, validData, err := readNullableVectorData(column, r.numRows, dim*2)
if err != nil {
return nil, -1, nil, 0, err
}
return ret, dim, validData, int(r.numRows), nil
}
col, err := r.reader.RowGroup(0).Column(0)
if err != nil {
return nil, -1, nil, 0, err
}
dim := col.Descriptor().TypeLength() / 2
values := make([]parquet.FixedLenByteArray, r.numRows)
valuesRead, err := ReadDataFromAllRowGroups[parquet.FixedLenByteArray, *file.FixedLenByteArrayColumnChunkReader](r.reader, values, 0, r.numRows)
if err != nil {
return nil, -1, nil, 0, err
}
if valuesRead != r.numRows {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expect %d rows, but got valuesRead = %d", r.numRows, valuesRead)
}
ret := make([]byte, int64(dim*2)*r.numRows)
for i := 0; i < int(r.numRows); i++ {
copy(ret[i*dim*2:(i+1)*dim*2], values[i])
}
return ret, dim, nil, int(r.numRows), nil
}
// GetBFloat16VectorFromPayload returns vector, dimension, validData, numRows, error
func (r *PayloadReader) GetBFloat16VectorFromPayload() ([]byte, int, []bool, int, error) {
if r.colType != schemapb.DataType_BFloat16Vector {
return nil, -1, nil, 0, merr.WrapErrServiceInternalMsg("failed to get bfloat16 vector from datatype %v", r.colType.String())
}
if r.nullable {
fileReader, err := pqarrow.NewFileReader(r.reader, pqarrow.ArrowReadProperties{}, memory.DefaultAllocator)
if err != nil {
return nil, -1, nil, 0, err
}
arrowSchema, err := fileReader.Schema()
if err != nil {
return nil, -1, nil, 0, err
}
if arrowSchema.NumFields() != 1 {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expected 1 field, got %d", arrowSchema.NumFields())
}
field := arrowSchema.Field(0)
var dim int
if field.Type.ID() == arrow.BINARY {
if !field.HasMetadata() {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("nullable bfloat16 vector field is missing metadata")
}
metadata := field.Metadata
dimStr, ok := metadata.GetValue("dim")
if !ok {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("nullable bfloat16 vector metadata missing required 'dim' field")
}
var err error
dim, err = strconv.Atoi(dimStr)
if err != nil {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("invalid dim value in metadata: %v", err)
}
} else {
col, err := r.reader.RowGroup(0).Column(0)
if err != nil {
return nil, -1, nil, 0, err
}
dim = col.Descriptor().TypeLength() / 2
}
table, err := fileReader.ReadTable(context.Background())
if err != nil {
return nil, -1, nil, 0, err
}
defer table.Release()
if table.NumCols() != 1 {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expected 1 column, got %d", table.NumCols())
}
column := table.Column(0)
ret, validData, err := readNullableVectorData(column, r.numRows, dim*2)
if err != nil {
return nil, -1, nil, 0, err
}
return ret, dim, validData, int(r.numRows), nil
}
col, err := r.reader.RowGroup(0).Column(0)
if err != nil {
return nil, -1, nil, 0, err
}
dim := col.Descriptor().TypeLength() / 2
values := make([]parquet.FixedLenByteArray, r.numRows)
valuesRead, err := ReadDataFromAllRowGroups[parquet.FixedLenByteArray, *file.FixedLenByteArrayColumnChunkReader](r.reader, values, 0, r.numRows)
if err != nil {
return nil, -1, nil, 0, err
}
if valuesRead != r.numRows {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expect %d rows, but got valuesRead = %d", r.numRows, valuesRead)
}
ret := make([]byte, int64(dim*2)*r.numRows)
for i := 0; i < int(r.numRows); i++ {
copy(ret[i*dim*2:(i+1)*dim*2], values[i])
}
return ret, dim, nil, int(r.numRows), nil
}
// GetFloatVectorFromPayload returns vector, dimension, validData, numRows, error
func (r *PayloadReader) GetFloatVectorFromPayload() ([]float32, int, []bool, int, error) {
if r.colType != schemapb.DataType_FloatVector {
return nil, -1, nil, 0, merr.WrapErrServiceInternalMsg("failed to get float vector from datatype %v", r.colType.String())
}
if r.nullable {
fileReader, err := pqarrow.NewFileReader(r.reader, pqarrow.ArrowReadProperties{}, memory.DefaultAllocator)
if err != nil {
return nil, -1, nil, 0, err
}
arrowSchema, err := fileReader.Schema()
if err != nil {
return nil, -1, nil, 0, err
}
if arrowSchema.NumFields() != 1 {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expected 1 field, got %d", arrowSchema.NumFields())
}
field := arrowSchema.Field(0)
var dim int
if field.Type.ID() == arrow.BINARY {
if !field.HasMetadata() {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("nullable float vector field is missing metadata")
}
metadata := field.Metadata
dimStr, ok := metadata.GetValue("dim")
if !ok {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("nullable float vector metadata missing required 'dim' field")
}
var err error
dim, err = strconv.Atoi(dimStr)
if err != nil {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("invalid dim value in metadata: %v", err)
}
} else {
col, err := r.reader.RowGroup(0).Column(0)
if err != nil {
return nil, -1, nil, 0, err
}
dim = col.Descriptor().TypeLength() / 4
}
table, err := fileReader.ReadTable(context.Background())
if err != nil {
return nil, -1, nil, 0, err
}
defer table.Release()
if table.NumCols() != 1 {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expected 1 column, got %d", table.NumCols())
}
column := table.Column(0)
retBytes, validData, err := readNullableVectorData(column, r.numRows, dim*4)
if err != nil {
return nil, -1, nil, 0, err
}
return arrow.Float32Traits.CastFromBytes(retBytes), dim, validData, int(r.numRows), nil
}
col, err := r.reader.RowGroup(0).Column(0)
if err != nil {
return nil, -1, nil, 0, err
}
dim := col.Descriptor().TypeLength() / 4
values := make([]parquet.FixedLenByteArray, r.numRows)
valuesRead, err := ReadDataFromAllRowGroups[parquet.FixedLenByteArray, *file.FixedLenByteArrayColumnChunkReader](r.reader, values, 0, r.numRows)
if err != nil {
return nil, -1, nil, 0, err
}
if valuesRead != r.numRows {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expect %d rows, but got valuesRead = %d", r.numRows, valuesRead)
}
ret := make([]float32, int64(dim)*r.numRows)
for i := 0; i < int(r.numRows); i++ {
copy(arrow.Float32Traits.CastToBytes(ret[i*dim:(i+1)*dim]), values[i])
}
return ret, dim, nil, int(r.numRows), nil
}
// GetSparseFloatVectorFromPayload returns fieldData, dimension, validData, error
func (r *PayloadReader) GetSparseFloatVectorFromPayload() (*SparseFloatVectorFieldData, int, []bool, error) {
if !typeutil.IsSparseFloatVectorType(r.colType) {
return nil, -1, nil, merr.WrapErrServiceInternalMsg("failed to get sparse float vector from datatype %v", r.colType.String())
}
if r.nullable {
fieldData := &SparseFloatVectorFieldData{}
validData := make([]bool, r.numRows)
fileReader, err := pqarrow.NewFileReader(r.reader, pqarrow.ArrowReadProperties{}, memory.DefaultAllocator)
if err != nil {
return nil, -1, nil, err
}
table, err := fileReader.ReadTable(context.Background())
if err != nil {
return nil, -1, nil, err
}
defer table.Release()
if table.NumCols() != 1 {
return nil, -1, nil, merr.WrapErrDataIntegrityMsg("expected 1 column, got %d", table.NumCols())
}
column := table.Column(0)
offset := 0
for _, chunk := range column.Data().Chunks() {
binaryArray, ok := chunk.(*array.Binary)
if !ok {
return nil, -1, nil, merr.WrapErrDataIntegrityMsg("expected Binary array, got %T", chunk)
}
for i := 0; i < binaryArray.Len(); i++ {
validData[offset+i] = binaryArray.IsValid(i)
if validData[offset+i] {
value := binaryArray.Value(i)
if len(value)%8 != 0 {
return nil, -1, nil, merr.WrapErrDataIntegrityMsg("invalid bytesData length")
}
fieldData.Contents = append(fieldData.Contents, append([]byte{}, value...))
rowDim := typeutil.SparseFloatRowDim(value)
if rowDim > fieldData.Dim {
fieldData.Dim = rowDim
}
}
}
offset += binaryArray.Len()
}
return fieldData, int(fieldData.Dim), validData, nil
}
values := make([]parquet.ByteArray, r.numRows)
valuesRead, err := ReadDataFromAllRowGroups[parquet.ByteArray, *file.ByteArrayColumnChunkReader](r.reader, values, 0, r.numRows)
if err != nil {
return nil, -1, nil, err
}
if valuesRead != r.numRows {
return nil, -1, nil, merr.WrapErrDataIntegrityMsg("expect %d binary, but got = %d", r.numRows, valuesRead)
}
fieldData := &SparseFloatVectorFieldData{}
for _, value := range values {
if len(value)%8 != 0 {
return nil, -1, nil, merr.WrapErrDataIntegrityMsg("invalid bytesData length")
}
fieldData.Contents = append(fieldData.Contents, value)
rowDim := typeutil.SparseFloatRowDim(value)
if rowDim > fieldData.Dim {
fieldData.Dim = rowDim
}
}
return fieldData, int(fieldData.Dim), nil, nil
}
// GetInt8VectorFromPayload returns vector, dimension, validData, numRows, error
func (r *PayloadReader) GetInt8VectorFromPayload() ([]int8, int, []bool, int, error) {
if r.colType != schemapb.DataType_Int8Vector {
return nil, -1, nil, 0, merr.WrapErrServiceInternalMsg("failed to get int8 vector from datatype %v", r.colType.String())
}
if r.nullable {
fileReader, err := pqarrow.NewFileReader(r.reader, pqarrow.ArrowReadProperties{}, memory.DefaultAllocator)
if err != nil {
return nil, -1, nil, 0, err
}
arrowSchema, err := fileReader.Schema()
if err != nil {
return nil, -1, nil, 0, err
}
if arrowSchema.NumFields() != 1 {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expected 1 field, got %d", arrowSchema.NumFields())
}
field := arrowSchema.Field(0)
var dim int
if field.Type.ID() == arrow.BINARY {
if !field.HasMetadata() {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("nullable int8 vector field is missing metadata")
}
metadata := field.Metadata
dimStr, ok := metadata.GetValue("dim")
if !ok {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("nullable int8 vector metadata missing required 'dim' field")
}
var err error
dim, err = strconv.Atoi(dimStr)
if err != nil {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("invalid dim value in metadata: %v", err)
}
} else {
col, err := r.reader.RowGroup(0).Column(0)
if err != nil {
return nil, -1, nil, 0, err
}
dim = col.Descriptor().TypeLength()
}
table, err := fileReader.ReadTable(context.Background())
if err != nil {
return nil, -1, nil, 0, err
}
defer table.Release()
if table.NumCols() != 1 {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expected 1 column, got %d", table.NumCols())
}
column := table.Column(0)
retBytes, validData, err := readNullableVectorData(column, r.numRows, dim)
if err != nil {
return nil, -1, nil, 0, err
}
return arrow.Int8Traits.CastFromBytes(retBytes), dim, validData, int(r.numRows), nil
}
col, err := r.reader.RowGroup(0).Column(0)
if err != nil {
return nil, -1, nil, 0, err
}
dim := col.Descriptor().TypeLength()
values := make([]parquet.FixedLenByteArray, r.numRows)
valuesRead, err := ReadDataFromAllRowGroups[parquet.FixedLenByteArray, *file.FixedLenByteArrayColumnChunkReader](r.reader, values, 0, r.numRows)
if err != nil {
return nil, -1, nil, 0, err
}
if valuesRead != r.numRows {
return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expect %d rows, but got valuesRead = %d", r.numRows, valuesRead)
}
ret := make([]int8, int64(dim)*r.numRows)
for i := 0; i < int(r.numRows); i++ {
int8Vals := arrow.Int8Traits.CastFromBytes(values[i])
copy(ret[i*dim:(i+1)*dim], int8Vals)
}
return ret, dim, nil, int(r.numRows), nil
}
func (r *PayloadReader) GetPayloadLengthFromReader() (int, error) {
return int(r.numRows), nil
}
// Close closes the payload reader
func (r *PayloadReader) Close() error {
return r.reader.Close()
}
// ReadDataFromAllRowGroups iterates all row groups of file.Reader, and convert column to E.
// then calls ReadBatch with provided parameters.
func ReadDataFromAllRowGroups[T any, E interface {
ReadBatch(int64, []T, []int16, []int16) (int64, int, error)
}](reader *file.Reader, values []T, columnIdx int, numRows int64) (int64, error) {
var offset int64
for i := 0; i < reader.NumRowGroups(); i++ {
if columnIdx >= reader.RowGroup(i).NumColumns() {
return -1, merr.WrapErrDataIntegrityMsg("try to fetch %d-th column of reader but row group has only %d column(s)", columnIdx, reader.RowGroup(i).NumColumns())
}
column, err := reader.RowGroup(i).Column(columnIdx)
if err != nil {
return -1, err
}
cReader, ok := column.(E)
if !ok {
return -1, merr.WrapErrDataIntegrityMsg("expect type %T, but got %T", *new(E), column)
}
_, valuesRead, err := cReader.ReadBatch(numRows, values[offset:], nil, nil)
if err != nil {
return -1, err
}
offset += int64(valuesRead)
}
return offset, nil
}
type DataSet[T any, E interface {
ReadBatch(int64, []T, []int16, []int16) (int64, int, error)
}] struct {
reader *file.Reader
cReader E
cnt, numRows int64
groupID, columnIdx int
}
func NewDataSet[T any, E interface {
ReadBatch(int64, []T, []int16, []int16) (int64, int, error)
}](reader *file.Reader, columnIdx int, numRows int64) *DataSet[T, E] {
return &DataSet[T, E]{
reader: reader,
columnIdx: columnIdx,
numRows: numRows,
}
}
func (s *DataSet[T, E]) nextGroup() error {
s.cnt = 0
column, err := s.reader.RowGroup(s.groupID).Column(s.columnIdx)
if err != nil {
return err
}
cReader, ok := column.(E)
if !ok {
return merr.WrapErrDataIntegrityMsg("expect type %T, but got %T", *new(E), column)
}
s.groupID++
s.cReader = cReader
return nil
}
func (s *DataSet[T, E]) HasNext() bool {
if s.groupID > s.reader.NumRowGroups() || (s.groupID == s.reader.NumRowGroups() && s.cnt >= s.numRows) || s.numRows == 0 {
return false
}
return true
}
func (s *DataSet[T, E]) NextBatch(batch int64) ([]T, error) {
if s.groupID > s.reader.NumRowGroups() || (s.groupID == s.reader.NumRowGroups() && s.cnt >= s.numRows) || s.numRows == 0 {
return nil, merr.WrapErrServiceInternalMsg("has no more data")
}
if s.groupID == 0 || s.cnt >= s.numRows {
err := s.nextGroup()
if err != nil {
return nil, err
}
}
batch = Min(batch, s.numRows-s.cnt)
result := make([]T, batch)
_, _, err := s.cReader.ReadBatch(batch, result, nil, nil)
if err != nil {
return nil, err
}
s.cnt += batch
return result, nil
}
func ReadData[T any, E interface {
Value(int) T
NullBitmapBytes() []byte
}](reader *file.Reader, value []T, validData []bool, numRows int64) (int64, error) {
var offset int
fileReader, err := pqarrow.NewFileReader(reader, pqarrow.ArrowReadProperties{}, memory.DefaultAllocator)
// defer fileReader.ParquetReader().Close()
if err != nil {
mlog.Warn(context.TODO(), "create arrow parquet file reader failed", mlog.Err(err))
return -1, err
}
schema, err := fileReader.Schema()
if err != nil {
mlog.Warn(context.TODO(), "can't schema from file", mlog.Err(err))
return -1, err
}
for i, field := range schema.Fields() {
// Spawn a new context to ignore cancellation from parental context.
newCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
columnReader, err := fileReader.GetColumn(newCtx, i)
if err != nil {
mlog.Warn(context.TODO(), "get column reader failed", mlog.String("fieldName", field.Name), mlog.Err(err))
return -1, err
}
chunked, err := columnReader.NextBatch(numRows)
if err != nil {
return -1, err
}
for _, chunk := range chunked.Chunks() {
dataNums := chunk.Data().Len()
reader, ok := chunk.(E)
if !ok {
mlog.Warn(context.TODO(), "the column data in parquet is not equal to field", mlog.String("fieldName", field.Name), mlog.String("actual type", chunk.DataType().Name()))
return -1, merr.WrapErrImportFailedMsg("the column data in parquet is not equal to field: %s, but: %s", field.Name, chunk.DataType().Name())
}
nullBitset := bytesToBoolArray(dataNums, reader.NullBitmapBytes())
for i := 0; i < dataNums; i++ {
value[offset] = reader.Value(i)
validData[offset] = nullBitset[i]
offset++
}
}
}
return int64(offset), nil
}
// todo(smellthemoon): use byte to store valid_data
func bytesToBoolArray(length int, bytes []byte) []bool {
bools := make([]bool, 0, length)
for i := 0; i < length; i++ {
bit := (bytes[uint(i)/8] & BitMask[byte(i)%8]) != 0
bools = append(bools, bit)
}
return bools
}
var (
BitMask = [8]byte{1, 2, 4, 8, 16, 32, 64, 128}
FlippedBitMask = [8]byte{254, 253, 251, 247, 239, 223, 191, 127}
)
// readNullableVectorData reads nullable vector data stored as Arrow Binary chunks.
// bytesPerVector is the byte size of each vector entry.
// Returns compact byte data (only valid vector entries) and validData bitmap.
//
// Arrow Binary array has 3 buffers:
// - Buffer 0 (validity bitmap): bit i indicates whether row i is valid (non-null).
// - Buffer 1 (offsets, int32): length n+1, offsets[i] is the start position of row i
// in the values buffer, offsets[n] is the total byte length.
// - Buffer 2 (values): concatenated raw bytes of all entries.
//
// Milvus's PayloadWriter uses BinaryBuilder.AppendNull() for null entries, which advances
// the offset without appending any bytes (offsets[i] == offsets[i+1]). Therefore null entries
// contribute zero bytes to the values buffer, and ValueBytes() returns a contiguous block
// containing only valid entries' data. This allows us to bulk-copy ValueBytes() directly
// instead of iterating per element with Value(i).
// We add a length assertion to guard against any future change that breaks this invariant.
func readNullableVectorData(column *arrow.Column, numRows int64, bytesPerVector int) ([]byte, []bool, error) {
chunks := column.Data().Chunks()
validCount := column.Len() - column.NullN()
ret := make([]byte, validCount*bytesPerVector)
validData := make([]bool, numRows)
logicalOffset := 0
dataOffset := 0
for _, chunk := range chunks {
binaryArray, ok := chunk.(*array.Binary)
if !ok {
return nil, nil, merr.WrapErrDataIntegrityMsg("expected Binary array for nullable vector, got %T", chunk)
}
chunkLen := binaryArray.Len()
// Bulk copy all non-null vector data from this chunk
chunkValidCount := chunkLen - binaryArray.NullN()
if chunkValidCount > 0 {
valueBytes := binaryArray.ValueBytes()
expected := chunkValidCount * bytesPerVector
if len(valueBytes) != expected {
return nil, nil, merr.WrapErrDataIntegrityMsg(
"unexpected valueBytes length for nullable vector: got %d, expected %d", len(valueBytes), expected)
}
copy(ret[dataOffset:dataOffset+len(valueBytes)], valueBytes)
dataOffset += len(valueBytes)
}
// Build validData
if binaryArray.NullN() == 0 {
// compiler optimizes this range-based fill pattern
for i := range validData[logicalOffset : logicalOffset+chunkLen] {
validData[logicalOffset+i] = true
}
} else {
for i := 0; i < chunkLen; i++ {
validData[logicalOffset+i] = binaryArray.IsValid(i)
}
}
logicalOffset += chunkLen
}
return ret, validData, nil
}