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

442 lines
13 KiB
Go

// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package numpy
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"unicode/utf8"
"github.com/sbinet/npyio"
"github.com/sbinet/npyio/npy"
"github.com/milvus-io/milvus-proto/go-api/v3/schemapb"
"github.com/milvus-io/milvus/internal/json"
"github.com/milvus-io/milvus/internal/util/importutilv2/common"
pkgcommon "github.com/milvus-io/milvus/pkg/v3/common"
"github.com/milvus-io/milvus/pkg/v3/util/merr"
"github.com/milvus-io/milvus/pkg/v3/util/parameterutil"
"github.com/milvus-io/milvus/pkg/v3/util/timestamptz"
"github.com/milvus-io/milvus/pkg/v3/util/typeutil"
)
type FieldReader struct {
reader io.Reader
npyReader *npy.Reader
order binary.ByteOrder
dim int64
field *schemapb.FieldSchema
// timezone is the collection's default timezone
timezone string
readPosition int
}
func NewFieldReader(reader io.Reader, field *schemapb.FieldSchema, timezone string) (*FieldReader, error) {
r, err := npyio.NewReader(reader)
if err != nil {
return nil, err
}
var dim int64 = 1
dataType := field.GetDataType()
if typeutil.IsVectorType(dataType) && !typeutil.IsSparseFloatVectorType(dataType) {
dim, err = typeutil.GetDim(field)
if err != nil {
return nil, err
}
}
err = validateHeader(r, field, int(dim))
if err != nil {
return nil, err
}
cr := &FieldReader{
reader: reader,
npyReader: r,
dim: dim,
field: field,
timezone: timezone,
}
cr.setByteOrder()
return cr, nil
}
func ReadN[T any](reader io.Reader, order binary.ByteOrder, n int64) ([]T, error) {
data := make([]T, n)
err := binary.Read(reader, order, &data)
if err != nil {
return nil, err
}
return data, nil
}
func (c *FieldReader) getCount(count int64) int64 {
shape := c.npyReader.Header.Descr.Shape
if len(shape) == 0 {
return 0
}
total := 1
for i := 0; i < len(shape); i++ {
total *= shape[i]
}
if total == 0 {
return 0
}
switch c.field.GetDataType() {
case schemapb.DataType_BinaryVector:
count *= c.dim / 8
case schemapb.DataType_FloatVector:
count *= c.dim
case schemapb.DataType_Float16Vector, schemapb.DataType_BFloat16Vector:
elementType, err := convertNumpyType(c.npyReader.Header.Descr.Type)
if err == nil && (elementType == schemapb.DataType_Float || elementType == schemapb.DataType_Double) {
count *= c.dim
} else {
count *= c.dim * 2
}
case schemapb.DataType_Int8Vector:
count *= c.dim
}
if int(count) > (total - c.readPosition) {
return int64(total - c.readPosition)
}
return count
}
func (c *FieldReader) logicalRowCount(readCount int64) int64 {
switch c.field.GetDataType() {
case schemapb.DataType_BinaryVector:
return readCount / (c.dim / 8)
case schemapb.DataType_FloatVector:
return readCount / c.dim
case schemapb.DataType_Float16Vector, schemapb.DataType_BFloat16Vector:
elementType, err := convertNumpyType(c.npyReader.Header.Descr.Type)
if err == nil && (elementType == schemapb.DataType_Float || elementType == schemapb.DataType_Double) {
return readCount / c.dim
}
return readCount / (c.dim * 2)
case schemapb.DataType_Int8Vector:
return readCount / c.dim
default:
return readCount
}
}
func (c *FieldReader) Next(count int64) (any, any, error) {
readCount := c.getCount(count)
if readCount == 0 {
return nil, nil, nil
}
var (
data any
validData []bool
err error
)
// numpy file cannot store null value, all the values must be non-null if the numpy file is accepted
// construct a bool array with all-true if the field is nullable
if c.field.GetNullable() || c.field.GetDefaultValue() != nil {
validRows := c.logicalRowCount(readCount)
validData = make([]bool, 0, validRows)
for i := int64(0); i < validRows; i++ {
validData = append(validData, true)
}
}
dt := c.field.GetDataType()
switch dt {
case schemapb.DataType_Bool:
data, err = ReadN[bool](c.reader, c.order, readCount)
if err != nil {
return nil, nil, err
}
c.readPosition += int(readCount)
case schemapb.DataType_Int8:
data, err = ReadN[int8](c.reader, c.order, readCount)
if err != nil {
return nil, nil, err
}
c.readPosition += int(readCount)
case schemapb.DataType_Int16:
data, err = ReadN[int16](c.reader, c.order, readCount)
if err != nil {
return nil, nil, err
}
c.readPosition += int(readCount)
case schemapb.DataType_Int32:
data, err = ReadN[int32](c.reader, c.order, readCount)
if err != nil {
return nil, nil, err
}
c.readPosition += int(readCount)
case schemapb.DataType_Int64:
data, err = ReadN[int64](c.reader, c.order, readCount)
if err != nil {
return nil, nil, err
}
c.readPosition += int(readCount)
case schemapb.DataType_Float:
data, err = ReadN[float32](c.reader, c.order, readCount)
if err != nil {
return nil, nil, err
}
c.readPosition += int(readCount)
case schemapb.DataType_Double:
data, err = ReadN[float64](c.reader, c.order, readCount)
if err != nil {
return nil, nil, err
}
c.readPosition += int(readCount)
case schemapb.DataType_Timestamptz:
var strs []string
strs, err = c.ReadString(readCount)
if err != nil {
return nil, nil, err
}
int64Ts := make([]int64, 0, len(strs))
for _, strValue := range strs {
tz, err := timestamptz.ValidateAndReturnUnixMicroTz(strValue, c.timezone)
if err != nil {
return nil, nil, err
}
int64Ts = append(int64Ts, tz)
}
data = int64Ts
c.readPosition += int(readCount)
case schemapb.DataType_VarChar:
data, err = c.ReadString(readCount)
c.readPosition += int(readCount)
if err != nil {
return nil, nil, err
}
case schemapb.DataType_Geometry:
var strs []string
strs, err = c.ReadString(readCount)
if err != nil {
return nil, nil, err
}
byteArr := make([][]byte, 0)
for _, wktValue := range strs {
wkbValue, err := pkgcommon.ConvertWKTToWKB(wktValue)
if err != nil {
return nil, nil, err
}
byteArr = append(byteArr, wkbValue)
}
data = byteArr
c.readPosition += int(readCount)
case schemapb.DataType_JSON:
var strs []string
strs, err = c.ReadString(readCount)
if err != nil {
return nil, nil, err
}
byteArr := make([][]byte, 0)
for _, str := range strs {
var dummy interface{}
err = json.Unmarshal([]byte(str), &dummy)
if err != nil {
return nil, nil, merr.WrapErrImportFailed(
fmt.Sprintf("failed to parse value '%v' for JSON field '%s', error: %v", str, c.field.GetName(), err))
}
if c.field.GetIsDynamic() {
var dummy2 map[string]interface{}
err = json.Unmarshal([]byte(str), &dummy2)
if err != nil {
return nil, nil, merr.WrapErrImportFailed(
fmt.Sprintf("failed to parse value '%v' for dynamic JSON field '%s', error: %v",
str, c.field.GetName(), err))
}
}
byteArr = append(byteArr, []byte(str))
}
data = byteArr
c.readPosition += int(readCount)
case schemapb.DataType_BinaryVector:
data, err = ReadN[uint8](c.reader, c.order, readCount)
if err != nil {
return nil, nil, err
}
c.readPosition += int(readCount)
case schemapb.DataType_Float16Vector, schemapb.DataType_BFloat16Vector:
data, err = c.readFP16BF16Vector(readCount)
if err != nil {
return nil, nil, err
}
c.readPosition += int(readCount)
case schemapb.DataType_Int8Vector:
data, err = ReadN[int8](c.reader, c.order, readCount)
if err != nil {
return nil, nil, err
}
c.readPosition += int(readCount)
case schemapb.DataType_FloatVector:
var elementType schemapb.DataType
elementType, err = convertNumpyType(c.npyReader.Header.Descr.Type)
if err != nil {
return nil, nil, err
}
switch elementType {
case schemapb.DataType_Float:
data, err = ReadN[float32](c.reader, c.order, readCount)
if err != nil {
return nil, nil, err
}
err = typeutil.VerifyFloats32(data.([]float32))
if err != nil {
return nil, nil, err
}
case schemapb.DataType_Double:
var data64 []float64
data64, err = ReadN[float64](c.reader, c.order, readCount)
if err != nil {
return nil, nil, err
}
err = typeutil.VerifyFloats64(data64)
if err != nil {
return nil, nil, err
}
data32 := make([]float32, len(data64))
for i, f := range data64 {
data32[i] = float32(f)
}
data = data32
}
c.readPosition += int(readCount)
default:
return nil, nil, merr.WrapErrImportFailedMsg("unsupported data type: %s", dt.String())
}
return data, validData, nil
}
func (c *FieldReader) readFP16BF16Vector(readCount int64) ([]byte, error) {
elementType, err := convertNumpyType(c.npyReader.Header.Descr.Type)
if err != nil {
return nil, err
}
switch elementType {
case schemapb.DataType_BinaryVector:
return ReadN[uint8](c.reader, c.order, readCount)
case schemapb.DataType_Float:
floatData, err := ReadN[float32](c.reader, c.order, readCount)
if err != nil {
return nil, err
}
return typeutil.ConvertFloat32ToFP16BF16Bytes(floatData, c.field.GetDataType())
case schemapb.DataType_Double:
data64, err := ReadN[float64](c.reader, c.order, readCount)
if err != nil {
return nil, err
}
if err := typeutil.VerifyFloats64(data64); err != nil {
return nil, err
}
floatData := make([]float32, len(data64))
for i, f := range data64 {
floatData[i] = float32(f)
}
return typeutil.ConvertFloat32ToFP16BF16Bytes(floatData, c.field.GetDataType())
default:
return nil, merr.WrapErrImportFailedMsg("unsupported numpy element type %s for field '%s'", elementType.String(), c.field.GetName())
}
}
// setByteOrder sets BigEndian/LittleEndian, the logic of this method is copied from npyio lib
func (c *FieldReader) setByteOrder() {
var nativeEndian binary.ByteOrder
v := uint16(1)
switch byte(v >> 8) {
case 0:
nativeEndian = binary.LittleEndian
case 1:
nativeEndian = binary.BigEndian
}
switch c.npyReader.Header.Descr.Type[0] {
case '<':
c.order = binary.LittleEndian
case '>':
c.order = binary.BigEndian
default:
c.order = nativeEndian
}
}
func (c *FieldReader) ReadString(count int64) ([]string, error) {
// varchar length, this is the max length, some item is shorter than this length, but they also occupy bytes of max length
maxLen, utf, err := stringLen(c.npyReader.Header.Descr.Type)
if err != nil || maxLen <= 0 {
return nil, merr.WrapErrImportFailed(
fmt.Sprintf("failed to get max length %d of varchar from numpy file header, error: %v", maxLen, err))
}
maxLength, err := parameterutil.GetMaxLength(c.field)
if c.field.DataType == schemapb.DataType_VarChar && err != nil {
return nil, err
}
// read data
data := make([]string, 0, count)
for len(data) < int(count) {
if utf {
// in the numpy file with utf32 encoding, the dType could be like "<U2",
// "<" is byteorder(LittleEndian), "U" means it is utf32 encoding, "2" means the max length of strings is 2(characters)
// each character occupy 4 bytes, each string occupies 4*maxLen bytes
// for example, a numpy file has two strings: "a" and "bb", the maxLen is 2, byte order is LittleEndian
// the character "a" occupies 2*4=8 bytes(0x97,0x00,0x00,0x00,0x00,0x00,0x00,0x00),
// the "bb" occupies 8 bytes(0x97,0x00,0x00,0x00,0x98,0x00,0x00,0x00)
// for non-ascii characters, the unicode could be 1 ~ 4 bytes, each character occupies 4 bytes, too
raw, err := io.ReadAll(io.LimitReader(c.reader, utf8.UTFMax*int64(maxLen)))
if err != nil {
return nil, merr.WrapErrImportFailedMsg("failed to read utf32 bytes from numpy file, error: %v", err)
}
str, err := decodeUtf32(raw, c.order)
if c.field.DataType == schemapb.DataType_VarChar {
if err = common.CheckVarcharLength(str, maxLength, c.field); err != nil {
return nil, err
}
}
if err != nil {
return nil, merr.WrapErrImportFailedMsg("failed to decode utf32 bytes, error: %v", err)
}
data = append(data, str)
} else {
// in the numpy file with ansi encoding, the dType could be like "S2", maxLen is 2, each string occupies 2 bytes
// bytes.Index(buf, []byte{0}) tell us which position is the end of the string
buf, err := io.ReadAll(io.LimitReader(c.reader, int64(maxLen)))
if err != nil {
return nil, merr.WrapErrImportFailedMsg("failed to read ascii bytes from numpy file, error: %v", err)
}
n := bytes.Index(buf, []byte{0})
if n > 0 {
buf = buf[:n]
}
str := string(buf)
if err = common.CheckValidUTF8(str, c.field); err != nil {
return nil, err
}
data = append(data, str)
}
}
return data, nil
}