feat: impl StructArray -- create schema, insert, and retrieve data (#42855)

Ref https://github.com/milvus-io/milvus/issues/42148

https://github.com/milvus-io/milvus/pull/42406 impls the segcore part of
storage for handling with VectorArray.
This PR:
1. impls the go part of storage for VectorArray
2. impls the collection creation with StructArrayField and VectorArray
3. insert and retrieve data from the collection.

---------

Signed-off-by: SpadeA <tangchenjie1210@gmail.com>
Signed-off-by: SpadeA-Tang <tangchenjie1210@gmail.com>
Signed-off-by: SpadeA-Tang <u6748471@anu.edu.au>
This commit is contained in:
Spade A
2025-07-27 01:30:55 +08:00
committed by GitHub
parent b497d3d7a4
commit faeb7fd410
78 changed files with 5114 additions and 670 deletions
+2
View File
@@ -50,6 +50,7 @@ func (meta *TtCollectionsMeta220) GenerateSaves(sourceVersion semver.Version) (m
if sourceVersion.LT(versions.Version220) {
opts = append(opts, model.WithFields())
opts = append(opts, model.WithPartitions())
opts = append(opts, model.WithStructArrayFields())
}
for collectionID := range *meta {
@@ -98,6 +99,7 @@ func (meta *CollectionsMeta220) GenerateSaves(sourceVersion semver.Version) (map
if sourceVersion.LT(versions.Version220) {
opts = append(opts, model.WithFields())
opts = append(opts, model.WithPartitions())
opts = append(opts, model.WithStructArrayFields())
}
for collectionID := range *meta {
-4
View File
@@ -788,10 +788,6 @@ func isFlush(segment *SegmentInfo) bool {
return segment.GetState() == commonpb.SegmentState_Flushed || segment.GetState() == commonpb.SegmentState_Flushing
}
func needSync(segment *SegmentInfo) bool {
return segment.GetState() == commonpb.SegmentState_Flushed || segment.GetState() == commonpb.SegmentState_Flushing || segment.GetState() == commonpb.SegmentState_Sealed
}
// buckets will be updated inplace
func (t *compactionTrigger) squeezeSmallSegmentsToBuckets(small []*SegmentInfo, buckets [][]*SegmentInfo, expectedSize int64) (remaining []*SegmentInfo) {
for i := len(small) - 1; i >= 0; i-- {
+1 -1
View File
@@ -135,7 +135,7 @@ func (s *Server) Flush(ctx context.Context, req *datapb.FlushRequest) (*datapb.F
flushSegmentIDs := make([]UniqueID, 0, len(segments))
for _, segment := range segments {
if segment != nil &&
(isFlushState(segment.GetState())) &&
isFlushState(segment.GetState()) &&
segment.GetLevel() != datapb.SegmentLevel_L0 && // SegmentLevel_Legacy, SegmentLevel_L1, SegmentLevel_L2
!sealedSegmentsIDDict[segment.GetID()] {
flushSegmentIDs = append(flushSegmentIDs, segment.GetID())
@@ -633,7 +633,7 @@ func (t *clusteringCompactionTask) mappingSegment(
}
vs := make([]*storage.Value, r.Len())
if err = storage.ValueDeserializer(r, vs, t.plan.Schema.Fields, false); err != nil {
if err = storage.ValueDeserializerWithSchema(r, vs, t.plan.Schema, false); err != nil {
log.Warn("compact wrong, failed to deserialize data", zap.Error(err))
return err
}
@@ -918,7 +918,7 @@ func (t *clusteringCompactionTask) scalarAnalyzeSegment(
}
pkIter := storage.NewDeserializeReader(rr, func(r storage.Record, v []*storage.Value) error {
return storage.ValueDeserializer(r, v, selectedFields, true)
return storage.ValueDeserializerWithSelectedFields(r, v, selectedFields, true)
})
defer pkIter.Close()
analyzeResult, remained, err := t.iterAndGetScalarAnalyzeResult(pkIter, expiredFilter)
@@ -556,7 +556,7 @@ func NewSegmentWriter(sch *schemapb.CollectionSchema, maxCount int64, batchSize
func newBinlogWriter(collID, partID, segID int64, schema *schemapb.CollectionSchema, batchSize int,
) (writer *storage.BinlogSerializeWriter, closers []func() (*storage.Blob, error), err error) {
fieldWriters := storage.NewBinlogStreamWriters(collID, partID, segID, schema.Fields)
fieldWriters := storage.NewBinlogStreamWriters(collID, partID, segID, schema)
closers = make([]func() (*storage.Blob, error), 0, len(fieldWriters))
for _, w := range fieldWriters {
closers = append(closers, w.Finalize)
+6
View File
@@ -491,6 +491,12 @@ func GetInsertDataRowCount(data *storage.InsertData, schema *schemapb.Collection
fields := lo.KeyBy(schema.GetFields(), func(field *schemapb.FieldSchema) int64 {
return field.GetFieldID()
})
for _, structField := range schema.GetStructArrayFields() {
for _, subField := range structField.GetFields() {
fields[subField.GetFieldID()] = subField
}
}
for fieldID, fd := range data.Data {
if fd == nil {
// normaly is impossible, just to avoid potential crash here
@@ -179,8 +179,7 @@ func (bw *BulkPackWriterV2) serializeBinlog(ctx context.Context, pack *SyncPack)
if len(pack.insertData) == 0 {
return nil, nil
}
arrowSchema, err := storage.ConvertToArrowSchema(bw.schema.GetFields())
arrowSchema, err := storage.ConvertToArrowSchema(bw.schema)
if err != nil {
return nil, err
}
@@ -188,7 +187,7 @@ func (bw *BulkPackWriterV2) serializeBinlog(ctx context.Context, pack *SyncPack)
defer builder.Release()
for _, chunk := range pack.insertData {
if err := storage.BuildRecord(builder, chunk, bw.schema.GetFields()); err != nil {
if err := storage.BuildRecord(builder, chunk, bw.schema); err != nil {
return nil, err
}
}
+59 -1
View File
@@ -80,6 +80,14 @@ func BuildFunctionKey(collectionID typeutil.UniqueID, functionID int64) string {
return fmt.Sprintf("%s/%d", BuildFunctionPrefix(collectionID), functionID)
}
func BuildStructArrayFieldPrefix(collectionID typeutil.UniqueID) string {
return fmt.Sprintf("%s/%d", StructArrayFieldMetaPrefix, collectionID)
}
func BuildStructArrayFieldKey(collectionId typeutil.UniqueID, fieldId int64) string {
return fmt.Sprintf("%s/%d", BuildStructArrayFieldPrefix(collectionId), fieldId)
}
func BuildAliasKey210(alias string) string {
return fmt.Sprintf("%s/%s", CollectionAliasMetaPrefix210, alias)
}
@@ -209,6 +217,17 @@ func (kc *Catalog) CreateCollection(ctx context.Context, coll *model.Collection,
kvs[k] = string(v)
}
// save struct array fields to new path
for _, structArrayField := range coll.StructArrayFields {
k := BuildStructArrayFieldKey(coll.CollectionID, structArrayField.FieldID)
structArrayFieldInfo := model.MarshalStructArrayFieldModel(structArrayField)
v, err := proto.Marshal(structArrayFieldInfo)
if err != nil {
return err
}
kvs[k] = string(v)
}
// save functions info to new path.
for _, function := range coll.Functions {
k := BuildFunctionKey(coll.CollectionID, function.ID)
@@ -389,7 +408,7 @@ func (kc *Catalog) batchListPartitionsAfter210(ctx context.Context, ts typeutil.
}
func fieldVersionAfter210(collMeta *pb.CollectionInfo) bool {
return len(collMeta.GetSchema().GetFields()) <= 0
return len(collMeta.GetSchema().GetFields()) <= 0 && len(collMeta.GetSchema().GetStructArrayFields()) <= 0
}
func (kc *Catalog) listFieldsAfter210(ctx context.Context, collectionID typeutil.UniqueID, ts typeutil.Timestamp) ([]*model.Field, error) {
@@ -436,6 +455,24 @@ func (kc *Catalog) batchListFieldsAfter210(ctx context.Context, ts typeutil.Time
return ret, nil
}
func (kc *Catalog) listStructArrayFieldsAfter210(ctx context.Context, collectionID typeutil.UniqueID, ts typeutil.Timestamp) ([]*model.StructArrayField, error) {
prefix := BuildStructArrayFieldPrefix(collectionID)
_, values, err := kc.Snapshot.LoadWithPrefix(ctx, prefix, ts)
if err != nil {
return nil, err
}
structFields := make([]*model.StructArrayField, 0, len(values))
for _, v := range values {
partitionMeta := &schemapb.StructArrayFieldSchema{}
err := proto.Unmarshal([]byte(v), partitionMeta)
if err != nil {
return nil, err
}
structFields = append(structFields, model.UnmarshalStructArrayFieldModel(partitionMeta))
}
return structFields, nil
}
func (kc *Catalog) listFunctions(ctx context.Context, collectionID typeutil.UniqueID, ts typeutil.Timestamp) ([]*model.Function, error) {
prefix := BuildFunctionPrefix(collectionID)
_, values, err := kc.Snapshot.LoadWithPrefix(ctx, prefix, ts)
@@ -499,6 +536,12 @@ func (kc *Catalog) appendPartitionAndFieldsInfo(ctx context.Context, collMeta *p
}
collection.Fields = fields
structArrayFields, err := kc.listStructArrayFieldsAfter210(ctx, collection.CollectionID, ts)
if err != nil {
return nil, err
}
collection.StructArrayFields = structArrayFields
functions, err := kc.listFunctions(ctx, collection.CollectionID, ts)
if err != nil {
return nil, err
@@ -610,6 +653,9 @@ func (kc *Catalog) DropCollection(ctx context.Context, collectionInfo *model.Col
for _, field := range collectionInfo.Fields {
delMetakeysSnap = append(delMetakeysSnap, BuildFieldKey(collectionInfo.CollectionID, field.FieldID))
}
for _, structArrayField := range collectionInfo.StructArrayFields {
delMetakeysSnap = append(delMetakeysSnap, BuildStructArrayFieldKey(collectionInfo.CollectionID, structArrayField.FieldID))
}
for _, function := range collectionInfo.Functions {
delMetakeysSnap = append(delMetakeysSnap, BuildFunctionKey(collectionInfo.CollectionID, function.ID))
}
@@ -650,6 +696,7 @@ func (kc *Catalog) alterModifyCollection(ctx context.Context, oldColl *model.Col
oldCollClone.State = newColl.State
oldCollClone.Properties = newColl.Properties
oldCollClone.Fields = newColl.Fields
oldCollClone.StructArrayFields = newColl.StructArrayFields
oldCollClone.UpdateTimestamp = newColl.UpdateTimestamp
newKey := BuildCollectionKey(newColl.DBID, oldColl.CollectionID)
@@ -670,7 +717,18 @@ func (kc *Catalog) alterModifyCollection(ctx context.Context, oldColl *model.Col
}
saves[k] = string(v)
}
for _, structArrayField := range newColl.StructArrayFields {
k := BuildStructArrayFieldKey(newColl.CollectionID, structArrayField.FieldID)
structArrayFieldInfo := model.MarshalStructArrayFieldModel(structArrayField)
v, err := proto.Marshal(structArrayFieldInfo)
if err != nil {
return err
}
saves[k] = string(v)
}
}
return etcd.SaveByBatchWithLimit(saves, util.MaxEtcdTxnNum/2, func(partialKvs map[string]string) error {
return kc.Snapshot.MultiSave(ctx, partialKvs, ts)
})
@@ -214,6 +214,12 @@ func TestCatalog_ListCollections(t *testing.T) {
}), ts).
Return([]string{"rootcoord/functions/1/1"}, []string{string(fcm)}, nil)
kv.On("LoadWithPrefix", mock.Anything, mock.MatchedBy(
func(prefix string) bool {
return strings.HasPrefix(prefix, StructArrayFieldMetaPrefix)
}), ts).
Return([]string{}, []string{}, nil)
kc := NewCatalog(nil, kv)
ret, err := kc.ListCollections(ctx, testDb, ts)
assert.NoError(t, err)
@@ -264,6 +270,12 @@ func TestCatalog_ListCollections(t *testing.T) {
}), ts).
Return([]string{"rootcoord/functions/1/1"}, []string{string(fcm)}, nil)
kv.On("LoadWithPrefix", mock.Anything, mock.MatchedBy(
func(prefix string) bool {
return strings.HasPrefix(prefix, StructArrayFieldMetaPrefix)
}), ts).
Return([]string{}, []string{}, nil)
kv.On("MultiSaveAndRemove", mock.Anything, mock.Anything, mock.Anything, ts).Return(nil)
kc := NewCatalog(nil, kv)
@@ -1287,7 +1299,7 @@ func TestCatalog_CreateCollection(t *testing.T) {
assert.NoError(t, err)
})
t.Run("create collection with function", func(t *testing.T) {
t.Run("create collection with function and struct array field", func(t *testing.T) {
mockSnapshot := newMockSnapshot(t, withMockSave(nil), withMockMultiSave(nil))
kc := NewCatalog(nil, mockSnapshot)
ctx := context.Background()
@@ -1311,6 +1323,23 @@ func TestCatalog_CreateCollection(t *testing.T) {
DataType: schemapb.DataType_SparseFloatVector,
},
},
StructArrayFields: []*model.StructArrayField{
{
Name: "test_struct",
Fields: []*model.Field{
{
Name: "sub_text",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_VarChar,
},
{
Name: "sub_sparse",
DataType: schemapb.DataType_ArrayOfVector,
ElementType: schemapb.DataType_SparseFloatVector,
},
},
},
},
Functions: []*model.Function{
{
Name: "test",
@@ -1417,6 +1446,23 @@ func TestCatalog_DropCollection(t *testing.T) {
DataType: schemapb.DataType_SparseFloatVector,
},
},
StructArrayFields: []*model.StructArrayField{
{
Name: "test_struct",
Fields: []*model.Field{
{
Name: "sub_text",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_VarChar,
},
{
Name: "sub_sparse",
DataType: schemapb.DataType_ArrayOfVector,
ElementType: schemapb.DataType_SparseFloatVector,
},
},
},
},
Functions: []*model.Function{
{
Name: "test",
@@ -17,10 +17,11 @@ const (
// CollectionMetaPrefix prefix for collection meta
CollectionMetaPrefix = ComponentPrefix + "/collection"
PartitionMetaPrefix = ComponentPrefix + "/partitions"
AliasMetaPrefix = ComponentPrefix + "/aliases"
FieldMetaPrefix = ComponentPrefix + "/fields"
FunctionMetaPrefix = ComponentPrefix + "/functions"
PartitionMetaPrefix = ComponentPrefix + "/partitions"
AliasMetaPrefix = ComponentPrefix + "/aliases"
FieldMetaPrefix = ComponentPrefix + "/fields"
StructArrayFieldMetaPrefix = ComponentPrefix + "/struct-array-fields"
FunctionMetaPrefix = ComponentPrefix + "/functions"
// CollectionAliasMetaPrefix210 prefix for collection alias meta
CollectionAliasMetaPrefix210 = ComponentPrefix + "/collection-alias"
+20 -3
View File
@@ -35,6 +35,7 @@ type Collection struct {
Description string
AutoID bool
Fields []*Field
StructArrayFields []*StructArrayField
Functions []*Function
VirtualChannelNames []string
PhysicalChannelNames []string
@@ -63,6 +64,7 @@ func (c *Collection) ShallowClone() *Collection {
Description: c.Description,
AutoID: c.AutoID,
Fields: c.Fields,
StructArrayFields: c.StructArrayFields,
Partitions: c.Partitions,
VirtualChannelNames: c.VirtualChannelNames,
PhysicalChannelNames: c.PhysicalChannelNames,
@@ -89,6 +91,7 @@ func (c *Collection) Clone() *Collection {
Description: c.Description,
AutoID: c.AutoID,
Fields: CloneFields(c.Fields),
StructArrayFields: CloneStructArrayFields(c.StructArrayFields),
Partitions: ClonePartitions(c.Partitions),
VirtualChannelNames: common.CloneStringList(c.VirtualChannelNames),
PhysicalChannelNames: common.CloneStringList(c.PhysicalChannelNames),
@@ -120,6 +123,7 @@ func (c *Collection) Equal(other Collection) bool {
c.Description == other.Description &&
c.AutoID == other.AutoID &&
CheckFieldsEqual(c.Fields, other.Fields) &&
CheckStructArrayFieldsEqual(c.StructArrayFields, other.StructArrayFields) &&
c.ShardsNum == other.ShardsNum &&
c.ConsistencyLevel == other.ConsistencyLevel &&
checkParamsEqual(c.Properties, other.Properties) &&
@@ -149,6 +153,7 @@ func UnmarshalCollectionModel(coll *pb.CollectionInfo) *Collection {
Description: coll.Schema.Description,
AutoID: coll.Schema.AutoID,
Fields: UnmarshalFieldModels(coll.GetSchema().GetFields()),
StructArrayFields: UnmarshalStructArrayFieldModels(coll.GetSchema().GetStructArrayFields()),
Partitions: partitions,
VirtualChannelNames: coll.VirtualChannelNames,
PhysicalChannelNames: coll.PhysicalChannelNames,
@@ -170,14 +175,15 @@ func MarshalCollectionModel(coll *Collection) *pb.CollectionInfo {
}
type config struct {
withFields bool
withPartitions bool
withFields bool
withPartitions bool
withStructArrayFields bool
}
type Option func(c *config)
func newDefaultConfig() *config {
return &config{withFields: false, withPartitions: false}
return &config{withFields: false, withPartitions: false, withStructArrayFields: false}
}
func WithFields() Option {
@@ -192,6 +198,12 @@ func WithPartitions() Option {
}
}
func WithStructArrayFields() Option {
return func(c *config) {
c.withStructArrayFields = true
}
}
func marshalCollectionModelWithConfig(coll *Collection, c *config) *pb.CollectionInfo {
if coll == nil {
return nil
@@ -210,6 +222,11 @@ func marshalCollectionModelWithConfig(coll *Collection, c *config) *pb.Collectio
collSchema.Fields = fields
}
if c.withStructArrayFields {
structArrayFields := MarshalStructArrayFieldModels(coll.StructArrayFields)
collSchema.StructArrayFields = structArrayFields
}
collectionPb := &pb.CollectionInfo{
ID: coll.CollectionID,
DbId: coll.DBID,
+86 -4
View File
@@ -57,6 +57,7 @@ var (
AutoID: false,
Description: "none",
Fields: []*Field{fieldModel},
StructArrayFields: []*StructArrayField{structFieldModel},
VirtualChannelNames: []string{"vch"},
PhysicalChannelNames: []string{"pch"},
ShardsNum: common.DefaultShardsNum,
@@ -81,10 +82,11 @@ var (
deprecatedColPb = &pb.CollectionInfo{
ID: colID,
Schema: &schemapb.CollectionSchema{
Name: colName,
Description: "none",
AutoID: false,
Fields: []*schemapb.FieldSchema{filedSchemaPb},
Name: colName,
Description: "none",
AutoID: false,
Fields: []*schemapb.FieldSchema{filedSchemaPb},
StructArrayFields: []*schemapb.StructArrayFieldSchema{structFieldPb},
},
CreateTime: 1,
PartitionIDs: []int64{partID},
@@ -411,6 +413,76 @@ func TestCollection_Equal(t *testing.T) {
},
want: true,
},
{
args: args{
a: Collection{
TenantID: "aaa",
Fields: []*Field{{Name: "f1", TypeParams: []*commonpb.KeyValuePair{
{Key: "dim", Value: "128"},
}}},
StructArrayFields: []*StructArrayField{
{
Name: "f2",
Description: "none",
Fields: []*Field{{Name: "f3", TypeParams: []*commonpb.KeyValuePair{
{Key: "dim", Value: "128"},
}}},
},
},
},
b: Collection{
TenantID: "aaa",
Fields: []*Field{{Name: "f1", TypeParams: []*commonpb.KeyValuePair{
{Key: "dim", Value: "128"},
}}},
StructArrayFields: []*StructArrayField{
{
Name: "f2",
Description: "none",
Fields: []*Field{{Name: "f3", TypeParams: []*commonpb.KeyValuePair{
{Key: "dim", Value: "128"},
}}},
},
},
},
},
want: true,
},
{
args: args{
a: Collection{
TenantID: "aaa",
Fields: []*Field{{Name: "f1", TypeParams: []*commonpb.KeyValuePair{
{Key: "dim", Value: "128"},
}}},
StructArrayFields: []*StructArrayField{
{
Name: "f2",
Description: "none",
Fields: []*Field{{Name: "f3", TypeParams: []*commonpb.KeyValuePair{
{Key: "dim", Value: "128"},
}}},
},
},
},
b: Collection{
TenantID: "aaa",
Fields: []*Field{{Name: "f1", TypeParams: []*commonpb.KeyValuePair{
{Key: "dim", Value: "128"},
}}},
StructArrayFields: []*StructArrayField{
{
Name: "f3",
Description: "none",
Fields: []*Field{{Name: "f3", TypeParams: []*commonpb.KeyValuePair{
{Key: "dim", Value: "128"},
}}},
},
},
},
},
want: false,
},
}
for _, tt := range tests {
@@ -485,6 +557,16 @@ func TestClone(t *testing.T) {
Nullable: true,
},
},
StructArrayFields: []*StructArrayField{
{
FieldID: 25,
Name: "26",
Description: "none",
Fields: []*Field{{FieldID: 27, Name: "28", TypeParams: []*commonpb.KeyValuePair{
{Key: "dim", Value: "128"},
}}},
},
},
Functions: []*Function{
{
ID: functionID,
+104
View File
@@ -0,0 +1,104 @@
package model
import (
"github.com/milvus-io/milvus-proto/go-api/v2/schemapb"
)
type StructArrayField struct {
FieldID int64
Name string
Description string
Fields []*Field
}
func (s *StructArrayField) Clone() *StructArrayField {
return &StructArrayField{
FieldID: s.FieldID,
Name: s.Name,
Description: s.Description,
Fields: CloneFields(s.Fields),
}
}
func CloneStructArrayFields(structArrayFields []*StructArrayField) []*StructArrayField {
clone := make([]*StructArrayField, len(structArrayFields))
for i, structArrayField := range structArrayFields {
clone[i] = structArrayField.Clone()
}
return clone
}
func (s *StructArrayField) Equal(other StructArrayField) bool {
return s.FieldID == other.FieldID &&
s.Name == other.Name &&
s.Description == other.Description &&
CheckFieldsEqual(s.Fields, other.Fields)
}
func CheckStructArrayFieldsEqual(structArrayFieldsA, structArrayFieldsB []*StructArrayField) bool {
if len(structArrayFieldsA) != len(structArrayFieldsB) {
return false
}
mapA := make(map[int64]*StructArrayField)
for _, f := range structArrayFieldsA {
mapA[f.FieldID] = f
}
for _, f := range structArrayFieldsB {
if other, exists := mapA[f.FieldID]; !exists || !f.Equal(*other) {
return false
}
}
return true
}
func MarshalStructArrayFieldModel(structArrayField *StructArrayField) *schemapb.StructArrayFieldSchema {
if structArrayField == nil {
return nil
}
return &schemapb.StructArrayFieldSchema{
FieldID: structArrayField.FieldID,
Name: structArrayField.Name,
Description: structArrayField.Description,
Fields: MarshalFieldModels(structArrayField.Fields),
}
}
func MarshalStructArrayFieldModels(fieldSchemas []*StructArrayField) []*schemapb.StructArrayFieldSchema {
if fieldSchemas == nil {
return nil
}
structArrayFields := make([]*schemapb.StructArrayFieldSchema, len(fieldSchemas))
for idx, structArrayField := range fieldSchemas {
structArrayFields[idx] = MarshalStructArrayFieldModel(structArrayField)
}
return structArrayFields
}
func UnmarshalStructArrayFieldModel(fieldSchema *schemapb.StructArrayFieldSchema) *StructArrayField {
if fieldSchema == nil {
return nil
}
return &StructArrayField{
FieldID: fieldSchema.FieldID,
Name: fieldSchema.Name,
Description: fieldSchema.Description,
Fields: UnmarshalFieldModels(fieldSchema.Fields),
}
}
func UnmarshalStructArrayFieldModels(fieldSchemas []*schemapb.StructArrayFieldSchema) []*StructArrayField {
if fieldSchemas == nil {
return nil
}
structArrayFields := make([]*StructArrayField, len(fieldSchemas))
for idx, StructArrayFieldSchema := range fieldSchemas {
structArrayFields[idx] = UnmarshalStructArrayFieldModel(StructArrayFieldSchema)
}
return structArrayFields
}
@@ -0,0 +1,222 @@
package model
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/milvus-io/milvus-proto/go-api/v2/schemapb"
)
var (
subVectorArrayField = &schemapb.FieldSchema{
FieldID: fieldID,
Name: fieldName,
IsPrimaryKey: false,
Description: "none",
DataType: schemapb.DataType_ArrayOfVector,
ElementType: schemapb.DataType_FloatVector,
TypeParams: typeParams,
IndexParams: indexParams,
AutoID: false,
}
subVectorArrayFieldModel = &Field{
FieldID: fieldID,
Name: fieldName,
IsPrimaryKey: false,
Description: "none",
DataType: schemapb.DataType_ArrayOfVector,
ElementType: schemapb.DataType_FloatVector,
TypeParams: typeParams,
IndexParams: indexParams,
AutoID: false,
}
subVarcharArrayField = &schemapb.FieldSchema{
FieldID: fieldID,
Name: fieldName,
IsPrimaryKey: false,
Description: "none",
AutoID: false,
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_VarChar,
TypeParams: typeParams,
IndexParams: indexParams,
}
subVarcharArrayFieldModel = &Field{
FieldID: fieldID,
Name: fieldName,
IsPrimaryKey: false,
Description: "none",
AutoID: false,
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_VarChar,
TypeParams: typeParams,
IndexParams: indexParams,
}
structFieldPb = &schemapb.StructArrayFieldSchema{
FieldID: fieldID,
Name: fieldName,
Description: "none",
Fields: []*schemapb.FieldSchema{subVarcharArrayField, subVectorArrayField},
}
structFieldModel = &StructArrayField{
FieldID: fieldID,
Name: fieldName,
Description: "none",
Fields: []*Field{subVarcharArrayFieldModel, subVectorArrayFieldModel},
}
)
func TestMarshalStructArrayFieldModel(t *testing.T) {
ret := MarshalStructArrayFieldModel(structFieldModel)
assert.Equal(t, structFieldPb, ret)
assert.Nil(t, MarshalStructArrayFieldModel(nil))
}
func TestMarshalStructArrayFieldModels(t *testing.T) {
ret := MarshalStructArrayFieldModels([]*StructArrayField{structFieldModel})
assert.Equal(t, []*schemapb.StructArrayFieldSchema{structFieldPb}, ret)
assert.Nil(t, MarshalStructArrayFieldModels(nil))
}
func TestUnmarshalStructArrayFieldModel(t *testing.T) {
ret := UnmarshalStructArrayFieldModel(structFieldPb)
assert.Equal(t, structFieldModel, ret)
assert.Nil(t, UnmarshalStructArrayFieldModel(nil))
}
func TestUnmarshalStructArrayFieldModels(t *testing.T) {
ret := UnmarshalStructArrayFieldModels([]*schemapb.StructArrayFieldSchema{structFieldPb})
assert.Equal(t, []*StructArrayField{structFieldModel}, ret)
assert.Nil(t, UnmarshalStructArrayFieldModels(nil))
}
func TestStructArrayField_Equal(t *testing.T) {
field1 := &Field{
FieldID: 1,
Name: "field1",
DataType: schemapb.DataType_VarChar,
Description: "test field 1",
}
field2 := &Field{
FieldID: 2,
Name: "field2",
DataType: schemapb.DataType_Int64,
Description: "test field 2",
}
type args struct {
structArrayFieldA StructArrayField
structArrayFieldB StructArrayField
}
tests := []struct {
name string
args args
want bool
}{
{
name: "equal fields",
args: args{
structArrayFieldA: StructArrayField{
FieldID: 1,
Name: "struct_field",
Description: "test struct field",
Fields: []*Field{field1, field2},
},
structArrayFieldB: StructArrayField{
FieldID: 1,
Name: "struct_field",
Description: "test struct field",
Fields: []*Field{field1, field2},
},
},
want: true,
},
{
name: "different FieldID",
args: args{
structArrayFieldA: StructArrayField{
FieldID: 1,
Name: "struct_field",
Description: "test struct field",
Fields: []*Field{field1},
},
structArrayFieldB: StructArrayField{
FieldID: 2,
Name: "struct_field",
Description: "test struct field",
Fields: []*Field{field1},
},
},
want: false,
},
{
name: "different Name",
args: args{
structArrayFieldA: StructArrayField{
FieldID: 1,
Name: "struct_field_1",
Description: "test struct field",
Fields: []*Field{field1},
},
structArrayFieldB: StructArrayField{
FieldID: 1,
Name: "struct_field_2",
Description: "test struct field",
Fields: []*Field{field1},
},
},
want: false,
},
{
name: "different Description",
args: args{
structArrayFieldA: StructArrayField{
FieldID: 1,
Name: "struct_field",
Description: "test struct field 1",
Fields: []*Field{field1},
},
structArrayFieldB: StructArrayField{
FieldID: 1,
Name: "struct_field",
Description: "test struct field 2",
Fields: []*Field{field1},
},
},
want: false,
},
{
name: "different Fields",
args: args{
structArrayFieldA: StructArrayField{
FieldID: 1,
Name: "struct_field",
Description: "test struct field",
Fields: []*Field{field1},
},
structArrayFieldB: StructArrayField{
FieldID: 1,
Name: "struct_field",
Description: "test struct field",
Fields: []*Field{field2},
},
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.args.structArrayFieldA.Equal(tt.args.structArrayFieldB); got != tt.want {
t.Errorf("StructArrayField.Equal() = %v, want %v", got, tt.want)
}
})
}
}
+1
View File
@@ -355,6 +355,7 @@ func describeCollection(node *Proxy) gin.HandlerFunc {
PartitionInfos: metricsinfo.NewPartitionInfos(describePartitionResp),
EnableDynamicField: describeCollectionResp.Schema.EnableDynamicField,
Fields: metricsinfo.NewFields(describeCollectionResp.GetSchema()),
StructArrayFields: metricsinfo.NewStructArrayFields(describeCollectionResp.GetSchema()),
}
// Marshal the collection struct to JSON
+15
View File
@@ -143,6 +143,12 @@ func newSchemaInfo(schema *schemapb.CollectionSchema) *schemaInfo {
pkField = field
}
}
for _, structField := range schema.GetStructArrayFields() {
fieldMap.Insert(structField.GetName(), structField.GetFieldID())
for _, field := range structField.GetFields() {
fieldMap.Insert(field.GetName(), field.GetFieldID())
}
}
// skip load fields logic for now
// partial load shall be processed as hint after tiered storage feature
schemaHelper, _ := typeutil.CreateSchemaHelper(schema)
@@ -183,6 +189,15 @@ func (s *schemaInfo) GetLoadFieldIDs(loadFields []string, skipDynamicField bool)
// fieldIDs := make([]int64, 0, len(loadFields))
fields := make([]*schemapb.FieldSchema, 0, len(loadFields))
for _, name := range loadFields {
// todo(SpadeA): check struct field
if structArrayField := s.schemaHelper.GetStructArrayFieldFromName(name); structArrayField != nil {
for _, field := range structArrayField.GetFields() {
fields = append(fields, field)
fieldIDs.Insert(field.GetFieldID())
}
continue
}
fieldSchema, err := s.schemaHelper.GetFieldFromName(name)
if err != nil {
return nil, err
+90
View File
@@ -1918,6 +1918,30 @@ func TestSchemaInfo_GetLoadFieldIDs(t *testing.T) {
IsClusteringKey: true,
}
subIntField := &schemapb.FieldSchema{
FieldID: common.StartOfUserFieldID + 7,
Name: "sub_int",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_Int32,
}
subFloatVectorField := &schemapb.FieldSchema{
FieldID: common.StartOfUserFieldID + 8,
Name: "sub_float_vector",
DataType: schemapb.DataType_ArrayOfVector,
ElementType: schemapb.DataType_FloatVector,
TypeParams: []*commonpb.KeyValuePair{
{Key: common.DimKey, Value: "768"},
},
}
structArrayField := &schemapb.StructArrayFieldSchema{
FieldID: common.StartOfUserFieldID + 6,
Name: "struct_array",
Fields: []*schemapb.FieldSchema{
subIntField,
subFloatVectorField,
},
}
testCases := []testCase{
{
tag: "default",
@@ -2076,6 +2100,72 @@ func TestSchemaInfo_GetLoadFieldIDs(t *testing.T) {
loadFields: []string{"pk", "part_key", "vector"},
expectErr: true,
},
{
tag: "struct_array_field_default",
schema: &schemapb.CollectionSchema{
EnableDynamicField: true,
Fields: []*schemapb.FieldSchema{
rowIDField,
timestampField,
pkField,
scalarField,
partitionKeyField,
vectorField,
clusteringKeyField,
},
StructArrayFields: []*schemapb.StructArrayFieldSchema{
structArrayField,
},
},
loadFields: nil,
skipDynamicField: false,
expectResult: []int64{},
expectErr: false,
},
{
tag: "load_struct_array_field",
schema: &schemapb.CollectionSchema{
EnableDynamicField: true,
Fields: []*schemapb.FieldSchema{
rowIDField,
timestampField,
pkField,
scalarField,
partitionKeyField,
vectorField,
clusteringKeyField,
},
StructArrayFields: []*schemapb.StructArrayFieldSchema{
structArrayField,
},
},
loadFields: []string{"pk", "part_key", "clustering_key", "struct_array"},
skipDynamicField: false,
expectResult: []int64{common.StartOfUserFieldID, common.StartOfUserFieldID + 2, common.StartOfUserFieldID + 5, common.StartOfUserFieldID + 7, common.StartOfUserFieldID + 8},
expectErr: false,
},
{
tag: "load_struct_array_field_with_vector",
schema: &schemapb.CollectionSchema{
EnableDynamicField: true,
Fields: []*schemapb.FieldSchema{
rowIDField,
timestampField,
pkField,
scalarField,
partitionKeyField,
vectorField,
clusteringKeyField,
},
StructArrayFields: []*schemapb.StructArrayFieldSchema{
structArrayField,
},
},
loadFields: []string{"pk", "part_key", "clustering_key", "vector", "struct_array"},
skipDynamicField: false,
expectResult: []int64{common.StartOfUserFieldID, common.StartOfUserFieldID + 2, common.StartOfUserFieldID + 3, common.StartOfUserFieldID + 5, common.StartOfUserFieldID + 7, common.StartOfUserFieldID + 8},
expectErr: false,
},
}
for _, tc := range testCases {
+4
View File
@@ -375,3 +375,7 @@ func newFloat16VectorFieldData(fieldName string, numRows, dim int) *schemapb.Fie
func newBFloat16VectorFieldData(fieldName string, numRows, dim int) *schemapb.FieldData {
return testutils.NewBFloat16VectorFieldData(fieldName, numRows, dim)
}
func newStructArrayFieldData(StructArrayFieldSchema *schemapb.StructArrayFieldSchema, fieldName string, numRows int, dim int) *schemapb.FieldData {
return testutils.GenerateStructFieldData(StructArrayFieldSchema, fieldName, numRows, dim)
}
+275 -18
View File
@@ -424,16 +424,14 @@ func TestProxy(t *testing.T) {
floatVecField := "fVec"
binaryVecField := "bVec"
dim := 128
rowNum := 3000
rowNum := 500
floatIndexName := "float_index"
binaryIndexName := "binary_index"
structId := "structI32"
structFVec := "structFVec"
structField := "structField"
nlist := 10
// nprobe := 10
// topk := 10
// add a test parameter
// roundDecimal := 6
nq := 10
// expr := fmt.Sprintf("%s > 0", int64Field)
var segmentIDs []int64
// an int64 field (pk) & a float vector field
@@ -478,6 +476,48 @@ func TestProxy(t *testing.T) {
IndexParams: nil,
AutoID: false,
}
// struct schema fields
sId := &schemapb.FieldSchema{
FieldID: 104,
Name: structId,
IsPrimaryKey: false,
Description: "",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_Int32,
TypeParams: []*commonpb.KeyValuePair{
{
Key: common.MaxCapacityKey,
Value: "100",
},
},
IndexParams: nil,
AutoID: false,
}
sFVec := &schemapb.FieldSchema{
FieldID: 105,
Name: structFVec,
IsPrimaryKey: false,
Description: "",
DataType: schemapb.DataType_ArrayOfVector,
ElementType: schemapb.DataType_FloatVector,
TypeParams: []*commonpb.KeyValuePair{
{
Key: common.DimKey,
Value: strconv.Itoa(dim),
},
{
Key: common.MaxCapacityKey,
Value: "100",
},
},
IndexParams: nil,
AutoID: false,
}
structF := &schemapb.StructArrayFieldSchema{
FieldID: 103,
Name: structField,
Fields: []*schemapb.FieldSchema{sId, sFVec},
}
return &schemapb.CollectionSchema{
Name: collectionName,
Description: "",
@@ -487,6 +527,7 @@ func TestProxy(t *testing.T) {
fVec,
bVec,
},
StructArrayFields: []*schemapb.StructArrayFieldSchema{structF},
}
}
schema := constructCollectionSchema()
@@ -507,13 +548,14 @@ func TestProxy(t *testing.T) {
constructCollectionInsertRequest := func() *milvuspb.InsertRequest {
fVecColumn := newFloatVectorFieldData(floatVecField, rowNum, dim)
bVecColumn := newBinaryVectorFieldData(binaryVecField, rowNum, dim)
structColumn := newStructArrayFieldData(schema.StructArrayFields[0], structField, rowNum, dim)
hashKeys := testutils.GenerateHashKeys(rowNum)
return &milvuspb.InsertRequest{
Base: nil,
DbName: dbName,
CollectionName: collectionName,
PartitionName: "",
FieldsData: []*schemapb.FieldData{fVecColumn, bVecColumn},
FieldsData: []*schemapb.FieldData{fVecColumn, bVecColumn, structColumn},
HashKeys: hashKeys,
NumRows: uint32(rowNum),
}
@@ -522,13 +564,14 @@ func TestProxy(t *testing.T) {
constructPartitionInsertRequest := func() *milvuspb.InsertRequest {
fVecColumn := newFloatVectorFieldData(floatVecField, rowNum, dim)
bVecColumn := newBinaryVectorFieldData(binaryVecField, rowNum, dim)
structColumn := newStructArrayFieldData(schema.StructArrayFields[0], structField, rowNum, dim)
hashKeys := testutils.GenerateHashKeys(rowNum)
return &milvuspb.InsertRequest{
Base: nil,
DbName: dbName,
CollectionName: collectionName,
PartitionName: partitionName,
FieldsData: []*schemapb.FieldData{fVecColumn, bVecColumn},
FieldsData: []*schemapb.FieldData{fVecColumn, bVecColumn, structColumn},
HashKeys: hashKeys,
NumRows: uint32(rowNum),
}
@@ -537,13 +580,14 @@ func TestProxy(t *testing.T) {
constructCollectionUpsertRequestNoPK := func() *milvuspb.UpsertRequest {
fVecColumn := newFloatVectorFieldData(floatVecField, rowNum, dim)
bVecColumn := newBinaryVectorFieldData(binaryVecField, rowNum, dim)
structColumn := newStructArrayFieldData(schema.StructArrayFields[0], structField, rowNum, dim)
hashKeys := testutils.GenerateHashKeys(rowNum)
return &milvuspb.UpsertRequest{
Base: nil,
DbName: dbName,
CollectionName: collectionName,
PartitionName: partitionName,
FieldsData: []*schemapb.FieldData{fVecColumn, bVecColumn},
FieldsData: []*schemapb.FieldData{fVecColumn, bVecColumn, structColumn},
HashKeys: hashKeys,
NumRows: uint32(rowNum),
}
@@ -553,19 +597,20 @@ func TestProxy(t *testing.T) {
pkFieldData := newScalarFieldData(schema.Fields[0], int64Field, rowNum)
fVecColumn := newFloatVectorFieldData(floatVecField, rowNum, dim)
bVecColumn := newBinaryVectorFieldData(binaryVecField, rowNum, dim)
structColumn := newStructArrayFieldData(schema.StructArrayFields[0], structField, rowNum, dim)
hashKeys := testutils.GenerateHashKeys(rowNum)
return &milvuspb.UpsertRequest{
Base: nil,
DbName: dbName,
CollectionName: collectionName,
PartitionName: partitionName,
FieldsData: []*schemapb.FieldData{pkFieldData, fVecColumn, bVecColumn},
FieldsData: []*schemapb.FieldData{pkFieldData, fVecColumn, bVecColumn, structColumn},
HashKeys: hashKeys,
NumRows: uint32(rowNum),
}
}
constructCreateIndexRequest := func(dataType schemapb.DataType) *milvuspb.CreateIndexRequest {
constructCreateIndexRequest := func(dataType schemapb.DataType, fieldName string) *milvuspb.CreateIndexRequest {
req := &milvuspb.CreateIndexRequest{
Base: nil,
DbName: dbName,
@@ -574,7 +619,7 @@ func TestProxy(t *testing.T) {
switch dataType {
case schemapb.DataType_FloatVector:
{
req.FieldName = floatVecField
req.FieldName = fieldName
req.IndexName = floatIndexName
req.ExtraParams = []*commonpb.KeyValuePair{
{
@@ -597,7 +642,7 @@ func TestProxy(t *testing.T) {
}
case schemapb.DataType_BinaryVector:
{
req.FieldName = binaryVecField
req.FieldName = fieldName
req.IndexName = binaryIndexName
req.ExtraParams = []*commonpb.KeyValuePair{
{
@@ -801,9 +846,31 @@ func TestProxy(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, commonpb.ErrorCode_Success, resp.GetStatus().GetErrorCode())
assert.Equal(t, collectionID, resp.CollectionID)
// TODO(dragondriver): shards num
assert.Equal(t, len(schema.Fields), len(resp.Schema.Fields))
// TODO(dragondriver): compare fields schema, not sure the order of fields
assert.Equal(t, len(schema.StructArrayFields), len(resp.Schema.StructArrayFields))
fieldsMap := make(map[string]*schemapb.FieldSchema)
for _, field := range resp.Schema.Fields {
fieldsMap[field.Name] = field
}
for _, structField := range resp.Schema.StructArrayFields {
for _, field := range structField.Fields {
fieldsMap[field.Name] = field
}
}
assert.Equal(t, len(fieldsMap), len(schema.Fields)+len(schema.StructArrayFields[0].Fields))
for _, field := range schema.Fields {
fSchema, ok := fieldsMap[field.Name]
assert.True(t, ok)
assert.True(t, proto.Equal(field, fSchema))
}
for _, structField := range schema.StructArrayFields {
for _, field := range structField.Fields {
fSchema, ok := fieldsMap[field.Name]
assert.True(t, ok)
assert.True(t, proto.Equal(field, fSchema))
}
}
// describe other collection -> fail
resp, err = proxy.DescribeCollection(ctx, &milvuspb.DescribeCollectionRequest{
@@ -1012,7 +1079,7 @@ func TestProxy(t *testing.T) {
resp, err := proxy.Insert(ctx, req)
assert.NoError(t, err)
assert.Equal(t, commonpb.ErrorCode_Success, resp.GetStatus().GetErrorCode())
assert.Equal(t, commonpb.ErrorCode_Success, resp.GetStatus().GetErrorCode(), "resp: %+v", resp.GetStatus().GetReason())
assert.Equal(t, rowNum, len(resp.SuccIndex))
assert.Equal(t, 0, len(resp.ErrIndex))
assert.Equal(t, int64(rowNum), resp.InsertCnt)
@@ -1094,7 +1161,7 @@ func TestProxy(t *testing.T) {
wg.Add(1)
t.Run("create index for floatVec field", func(t *testing.T) {
defer wg.Done()
req := constructCreateIndexRequest(schemapb.DataType_FloatVector)
req := constructCreateIndexRequest(schemapb.DataType_FloatVector, floatVecField)
resp, err := proxy.CreateIndex(ctx, req)
assert.NoError(t, err)
@@ -1236,7 +1303,7 @@ func TestProxy(t *testing.T) {
wg.Add(1)
t.Run("create index for binVec field", func(t *testing.T) {
defer wg.Done()
req := constructCreateIndexRequest(schemapb.DataType_BinaryVector)
req := constructCreateIndexRequest(schemapb.DataType_BinaryVector, binaryVecField)
resp, err := proxy.CreateIndex(ctx, req)
assert.NoError(t, err)
@@ -3783,6 +3850,196 @@ func TestProxy(t *testing.T) {
assert.Equal(t, 0, len(resp.ErrIndex))
assert.Equal(t, int64(rowNum), resp.UpsertCnt)
})
wg.Add(1)
// todo: when struct array field is done, this will be merged with above logic
t.Run("test struct array field", func(t *testing.T) {
defer wg.Done()
structCollectionName := prefix + "struct_" + funcutil.GenRandomStr()
structId := "structI32Array"
structVec := "structFloatVecArray"
structField := "struct"
constructStructCollectionSchema := func() *schemapb.CollectionSchema {
pk := &schemapb.FieldSchema{
FieldID: 100,
Name: int64Field,
IsPrimaryKey: true,
Description: "",
DataType: schemapb.DataType_Int64,
TypeParams: nil,
IndexParams: nil,
AutoID: true,
}
fVec := &schemapb.FieldSchema{
FieldID: 101,
Name: floatVecField,
IsPrimaryKey: false,
Description: "",
DataType: schemapb.DataType_FloatVector,
TypeParams: []*commonpb.KeyValuePair{
{
Key: common.DimKey,
Value: strconv.Itoa(dim),
},
},
IndexParams: nil,
AutoID: false,
}
// struct schema fields
sId := &schemapb.FieldSchema{
FieldID: 103,
Name: structId,
IsPrimaryKey: false,
Description: "",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_Int32,
TypeParams: []*commonpb.KeyValuePair{
{
Key: common.MaxCapacityKey,
Value: "100",
},
},
IndexParams: nil,
AutoID: false,
}
sVec := &schemapb.FieldSchema{
FieldID: 104,
Name: structVec,
IsPrimaryKey: false,
Description: "",
DataType: schemapb.DataType_ArrayOfVector,
ElementType: schemapb.DataType_FloatVector,
TypeParams: []*commonpb.KeyValuePair{
{
Key: common.DimKey,
Value: strconv.Itoa(dim),
},
{
Key: common.MaxCapacityKey,
Value: "100",
},
},
IndexParams: nil,
AutoID: false,
}
structF := &schemapb.StructArrayFieldSchema{
FieldID: 105,
Name: structField,
Fields: []*schemapb.FieldSchema{sId, sVec},
}
return &schemapb.CollectionSchema{
Name: structCollectionName,
Description: "",
AutoID: false,
Fields: []*schemapb.FieldSchema{
pk,
fVec,
},
StructArrayFields: []*schemapb.StructArrayFieldSchema{structF},
}
}
structSchema := constructStructCollectionSchema()
constructStructCreateCollectionRequest := func() *milvuspb.CreateCollectionRequest {
bs, err := proto.Marshal(structSchema)
assert.NoError(t, err)
return &milvuspb.CreateCollectionRequest{
Base: nil,
DbName: dbName,
CollectionName: structCollectionName,
Schema: bs,
ShardsNum: shardsNum,
}
}
createStructCollectionReq := constructStructCreateCollectionRequest()
resp, err := proxy.CreateCollection(ctx, createStructCollectionReq)
assert.NoError(t, err)
assert.Equal(t, commonpb.ErrorCode_Success, resp.ErrorCode)
reqInvalidField := constructStructCreateCollectionRequest()
invalidSchema := constructStructCollectionSchema()
invalidSchema.Fields = append(invalidSchema.Fields, &schemapb.FieldSchema{
Name: "StringField",
DataType: schemapb.DataType_String,
})
bs, err := proto.Marshal(invalidSchema)
assert.NoError(t, err)
reqInvalidField.CollectionName = "invalid_field_coll"
reqInvalidField.Schema = bs
resp, err = proxy.CreateCollection(ctx, reqInvalidField)
assert.NoError(t, err)
assert.NotEqual(t, commonpb.ErrorCode_Success, resp.ErrorCode)
hasResp, err := proxy.HasCollection(ctx, &milvuspb.HasCollectionRequest{
Base: nil,
DbName: dbName,
CollectionName: structCollectionName,
TimeStamp: 0,
})
assert.NoError(t, err)
assert.Equal(t, commonpb.ErrorCode_Success, hasResp.GetStatus().GetErrorCode())
assert.True(t, hasResp.Value)
collectionID, err := globalMetaCache.GetCollectionID(ctx, dbName, structCollectionName)
assert.NoError(t, err)
descResp, err := proxy.DescribeCollection(ctx, &milvuspb.DescribeCollectionRequest{
Base: nil,
DbName: dbName,
CollectionName: structCollectionName,
CollectionID: collectionID,
TimeStamp: 0,
})
assert.NoError(t, err)
assert.Equal(t, commonpb.ErrorCode_Success, descResp.GetStatus().GetErrorCode())
assert.Equal(t, collectionID, descResp.CollectionID)
fieldsMap := make(map[string]*schemapb.FieldSchema)
for _, field := range descResp.Schema.Fields {
fieldsMap[field.Name] = field
}
for _, structField := range descResp.Schema.StructArrayFields {
for _, field := range structField.Fields {
fieldsMap[field.Name] = field
}
}
assert.Equal(t, len(fieldsMap), len(structSchema.Fields)+2)
for _, field := range structSchema.Fields {
fSchema, ok := fieldsMap[field.Name]
assert.True(t, ok)
assert.True(t, proto.Equal(field, fSchema))
}
for _, structField := range structSchema.StructArrayFields {
for _, field := range structField.Fields {
fSchema, ok := fieldsMap[field.Name]
assert.True(t, ok)
assert.True(t, proto.Equal(field, fSchema))
}
}
statsResp, err := proxy.GetCollectionStatistics(ctx, &milvuspb.GetCollectionStatisticsRequest{
Base: nil,
DbName: dbName,
CollectionName: structCollectionName,
})
assert.NoError(t, err)
assert.Equal(t, commonpb.ErrorCode_Success, statsResp.GetStatus().GetErrorCode())
showResp, err := proxy.ShowCollections(ctx, &milvuspb.ShowCollectionsRequest{
Base: nil,
DbName: dbName,
TimeStamp: 0,
Type: milvuspb.ShowType_All,
})
assert.NoError(t, err)
assert.True(t, merr.Ok(showResp.GetStatus()))
assert.Contains(t, showResp.CollectionNames, structCollectionName)
})
testServer.gracefulStop()
wg.Wait()
log.Info("case done")
+1
View File
@@ -153,6 +153,7 @@ func (node *CachedProxyServiceProvider) DescribeCollection(ctx context.Context,
Fields: lo.Filter(c.schema.CollectionSchema.Fields, func(field *schemapb.FieldSchema, _ int) bool {
return !field.IsDynamic
}),
StructArrayFields: c.schema.CollectionSchema.StructArrayFields,
EnableDynamicField: c.schema.CollectionSchema.EnableDynamicField,
Properties: c.schema.CollectionSchema.Properties,
Functions: c.schema.CollectionSchema.Functions,
+71 -24
View File
@@ -280,6 +280,16 @@ func (t *createCollectionTask) validatePartitionKey(ctx context.Context) error {
}
}
// Fields in StructArrayFields should not be partition key
for _, field := range t.schema.StructArrayFields {
for _, subField := range field.Fields {
if subField.GetIsPartitionKey() {
return merr.WrapErrCollectionIllegalSchema(t.CollectionName,
fmt.Sprintf("partition key is not supported for struct field, field name = %s", subField.Name))
}
}
}
mustPartitionKey := Params.ProxyCfg.MustUsePartitionKey.GetAsBool()
if mustPartitionKey && idx == -1 {
return merr.WrapErrParameterInvalidMsg("partition key must be set when creating the collection" +
@@ -315,6 +325,15 @@ func (t *createCollectionTask) validateClusteringKey(ctx context.Context) error
}
}
// Fields in StructArrayFields should not be clustering key
for _, field := range t.schema.StructArrayFields {
for _, subField := range field.Fields {
if subField.GetIsClusteringKey() {
return merr.WrapErrCollectionIllegalSchema(t.CollectionName,
fmt.Sprintf("clustering key is not supported for struct field, field name = %s", subField.Name))
}
}
}
if idx != -1 {
log.Ctx(ctx).Info("create collection with clustering key",
zap.String("collectionName", t.CollectionName),
@@ -342,7 +361,8 @@ func (t *createCollectionTask) PreExecute(ctx context.Context) error {
return fmt.Errorf("maximum shards's number should be limited to %d", Params.ProxyCfg.MaxShardNum.GetAsInt())
}
if len(t.schema.Fields) > Params.ProxyCfg.MaxFieldNum.GetAsInt() {
totalFieldsNum := typeutil.GetTotalFieldsNum(t.schema)
if totalFieldsNum > Params.ProxyCfg.MaxFieldNum.GetAsInt() {
return fmt.Errorf("maximum field's number should be limited to %d", Params.ProxyCfg.MaxFieldNum.GetAsInt())
}
@@ -361,7 +381,7 @@ func (t *createCollectionTask) PreExecute(ctx context.Context) error {
}
// validate whether field names duplicates
if err := validateDuplicatedFieldName(t.schema.Fields); err != nil {
if err := validateDuplicatedFieldName(t.schema); err != nil {
return err
}
@@ -406,6 +426,12 @@ func (t *createCollectionTask) PreExecute(ctx context.Context) error {
}
}
for _, structArrayField := range t.schema.StructArrayFields {
if err := ValidateStructArrayField(structArrayField, t.schema); err != nil {
return err
}
}
if err := validateMultipleVectorFields(t.schema); err != nil {
return err
}
@@ -762,11 +788,12 @@ func (t *describeCollectionTask) Execute(ctx context.Context) error {
t.result = &milvuspb.DescribeCollectionResponse{
Status: merr.Success(),
Schema: &schemapb.CollectionSchema{
Name: "",
Description: "",
AutoID: false,
Fields: make([]*schemapb.FieldSchema, 0),
Functions: make([]*schemapb.FunctionSchema, 0),
Name: "",
Description: "",
AutoID: false,
Fields: make([]*schemapb.FieldSchema, 0),
Functions: make([]*schemapb.FunctionSchema, 0),
StructArrayFields: make([]*schemapb.StructArrayFieldSchema, 0),
},
CollectionID: 0,
VirtualChannelNames: nil,
@@ -813,28 +840,44 @@ func (t *describeCollectionTask) Execute(ctx context.Context) error {
t.result.NumPartitions = result.NumPartitions
t.result.UpdateTimestamp = result.UpdateTimestamp
t.result.UpdateTimestampStr = result.UpdateTimestampStr
copyFieldSchema := func(field *schemapb.FieldSchema) *schemapb.FieldSchema {
return &schemapb.FieldSchema{
FieldID: field.FieldID,
Name: field.Name,
IsPrimaryKey: field.IsPrimaryKey,
AutoID: field.AutoID,
Description: field.Description,
DataType: field.DataType,
TypeParams: field.TypeParams,
IndexParams: field.IndexParams,
IsDynamic: field.IsDynamic,
IsPartitionKey: field.IsPartitionKey,
IsClusteringKey: field.IsClusteringKey,
DefaultValue: field.DefaultValue,
ElementType: field.ElementType,
Nullable: field.Nullable,
IsFunctionOutput: field.IsFunctionOutput,
}
}
for _, field := range result.Schema.Fields {
if field.IsDynamic {
continue
}
if field.FieldID >= common.StartOfUserFieldID {
t.result.Schema.Fields = append(t.result.Schema.Fields, &schemapb.FieldSchema{
FieldID: field.FieldID,
Name: field.Name,
IsPrimaryKey: field.IsPrimaryKey,
AutoID: field.AutoID,
Description: field.Description,
DataType: field.DataType,
TypeParams: field.TypeParams,
IndexParams: field.IndexParams,
IsDynamic: field.IsDynamic,
IsPartitionKey: field.IsPartitionKey,
IsClusteringKey: field.IsClusteringKey,
DefaultValue: field.DefaultValue,
ElementType: field.ElementType,
Nullable: field.Nullable,
IsFunctionOutput: field.IsFunctionOutput,
})
t.result.Schema.Fields = append(t.result.Schema.Fields, copyFieldSchema(field))
}
}
for i, structArrayField := range result.Schema.StructArrayFields {
t.result.Schema.StructArrayFields = append(t.result.Schema.StructArrayFields, &schemapb.StructArrayFieldSchema{
FieldID: structArrayField.FieldID,
Name: structArrayField.Name,
Description: structArrayField.Description,
Fields: make([]*schemapb.FieldSchema, 0, len(structArrayField.Fields)),
})
for _, field := range structArrayField.Fields {
t.result.Schema.StructArrayFields[i].Fields = append(t.result.Schema.StructArrayFields[i].Fields, copyFieldSchema(field))
}
}
@@ -2016,6 +2059,8 @@ func (t *loadCollectionTask) Execute(ctx context.Context) (err error) {
}
}
// todo(SpadeA): check vector field in StructArrayField when index is implemented
if len(unindexedVecFields) != 0 {
errMsg := fmt.Sprintf("there is no vector index on field: %v, please create index firstly", unindexedVecFields)
log.Debug(errMsg)
@@ -2276,6 +2321,8 @@ func (t *loadPartitionsTask) Execute(ctx context.Context) error {
}
}
// todo(SpadeA): check vector field in StructArrayField when index is implemented
if len(unindexedVecFields) != 0 {
errMsg := fmt.Sprintf("there is no vector index on field: %v, please create index firstly", unindexedVecFields)
log.Ctx(ctx).Debug(errMsg)
+13 -2
View File
@@ -213,10 +213,21 @@ func (it *insertTask) PreExecute(ctx context.Context) error {
}
}
err = checkAndFlattenStructFieldData(it.schema, it.insertMsg)
if err != nil {
return err
}
allFields := make([]*schemapb.FieldSchema, 0, len(it.schema.Fields)+5)
allFields = append(allFields, it.schema.Fields...)
for _, structField := range it.schema.GetStructArrayFields() {
allFields = append(allFields, structField.GetFields()...)
}
// check primaryFieldData whether autoID is true or not
// set rowIDs as primary data if autoID == true
// TODO(dragondriver): in fact, NumRows is not trustable, we should check all input fields
it.result.IDs, err = checkPrimaryFieldData(it.schema, it.insertMsg)
it.result.IDs, err = checkPrimaryFieldData(allFields, it.schema, it.insertMsg)
log := log.Ctx(ctx).With(zap.String("collectionName", collectionName))
if err != nil {
log.Warn("check primary field data and hash primary key failed",
@@ -225,7 +236,7 @@ func (it *insertTask) PreExecute(ctx context.Context) error {
}
// check varchar/text with analyzer was utf-8 format
err = checkInputUtf8Compatiable(it.schema, it.insertMsg)
err = checkInputUtf8Compatiable(allFields, it.insertMsg)
if err != nil {
log.Warn("check varchar/text format failed", zap.Error(err))
return err
+72
View File
@@ -111,6 +111,20 @@ func translateToOutputFieldIDs(outputFields []string, schema *schemapb.Collectio
break
}
}
if !fieldFound {
structFieldLoop:
for _, structField := range schema.StructArrayFields {
for _, field := range structField.Fields {
if reqField == field.Name {
outputFieldIDs = append(outputFieldIDs, field.FieldID)
fieldFound = true
break structFieldLoop
}
}
}
}
if !fieldFound {
return nil, fmt.Errorf("field %s not exist", reqField)
}
@@ -539,6 +553,62 @@ func (t *queryTask) Execute(ctx context.Context) error {
return nil
}
// FieldsData in results are flattened, so we need to reconstruct the struct fields
func reconstructStructFieldData(results *milvuspb.QueryResults, schema *schemapb.CollectionSchema) {
if len(results.OutputFields) == 1 && results.OutputFields[0] == "count(*)" {
return
}
if len(schema.StructArrayFields) == 0 {
return
}
regularFieldIDs := make(map[int64]interface{})
subFieldToStructMap := make(map[int64]int64)
groupedStructFields := make(map[int64][]*schemapb.FieldData)
structFieldNames := make(map[int64]string)
reconstructedOutputFields := make([]string, 0, len(results.FieldsData))
// record all regular field IDs
for _, field := range schema.Fields {
regularFieldIDs[field.GetFieldID()] = nil
}
// build the mapping from sub-field ID to struct field ID
for _, structField := range schema.StructArrayFields {
for _, subField := range structField.GetFields() {
subFieldToStructMap[subField.GetFieldID()] = structField.GetFieldID()
}
structFieldNames[structField.GetFieldID()] = structField.GetName()
}
fieldsData := make([]*schemapb.FieldData, 0, len(results.FieldsData))
for _, field := range results.FieldsData {
fieldID := field.GetFieldId()
if _, ok := regularFieldIDs[fieldID]; ok {
fieldsData = append(fieldsData, field)
reconstructedOutputFields = append(reconstructedOutputFields, field.GetFieldName())
} else {
structFieldID := subFieldToStructMap[fieldID]
groupedStructFields[structFieldID] = append(groupedStructFields[structFieldID], field)
}
}
for structFieldID, fields := range groupedStructFields {
fieldData := &schemapb.FieldData{
FieldName: structFieldNames[structFieldID],
FieldId: structFieldID,
Type: schemapb.DataType_ArrayOfStruct,
Field: &schemapb.FieldData_StructArrays{StructArrays: &schemapb.StructArrayField{Fields: fields}},
}
fieldsData = append(fieldsData, fieldData)
reconstructedOutputFields = append(reconstructedOutputFields, structFieldNames[structFieldID])
}
results.FieldsData = fieldsData
results.OutputFields = reconstructedOutputFields
}
func (t *queryTask) PostExecute(ctx context.Context) error {
tr := timerecord.NewTimeRecorder("queryTask PostExecute")
defer func() {
@@ -580,6 +650,8 @@ func (t *queryTask) PostExecute(ctx context.Context) error {
return err
}
t.result.OutputFields = t.userOutputFields
reconstructStructFieldData(t.result, t.schema.CollectionSchema)
primaryFieldSchema, err := t.schema.GetPkField()
if err != nil {
log.Warn("failed to get primary field schema", zap.Error(err))
+467
View File
@@ -1292,3 +1292,470 @@ func TestQueryTask_CanSkipAllocTimestamp(t *testing.T) {
assert.True(t, skip)
})
}
func Test_reconstructStructFieldData(t *testing.T) {
t.Run("count(*) query - should return early", func(t *testing.T) {
results := &milvuspb.QueryResults{
OutputFields: []string{"count(*)"},
FieldsData: []*schemapb.FieldData{
{
FieldName: "count(*)",
FieldId: 0,
Type: schemapb.DataType_Int64,
},
},
}
schema := &schemapb.CollectionSchema{
StructArrayFields: []*schemapb.StructArrayFieldSchema{
{
FieldID: 102,
Name: "test_struct",
Fields: []*schemapb.FieldSchema{
{
FieldID: 1021,
Name: "sub_field",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_Int32,
},
},
},
},
}
originalFieldsData := make([]*schemapb.FieldData, len(results.FieldsData))
copy(originalFieldsData, results.FieldsData)
originalOutputFields := make([]string, len(results.OutputFields))
copy(originalOutputFields, results.OutputFields)
reconstructStructFieldData(results, schema)
// Should not modify anything for count(*) query
assert.Equal(t, originalFieldsData, results.FieldsData)
assert.Equal(t, originalOutputFields, results.OutputFields)
})
t.Run("no struct array fields - should return early", func(t *testing.T) {
results := &milvuspb.QueryResults{
OutputFields: []string{"field1", "field2"},
FieldsData: []*schemapb.FieldData{
{
FieldName: "field1",
FieldId: 100,
Type: schemapb.DataType_Int64,
},
{
FieldName: "field2",
FieldId: 101,
Type: schemapb.DataType_VarChar,
},
},
}
schema := &schemapb.CollectionSchema{
StructArrayFields: []*schemapb.StructArrayFieldSchema{},
}
originalFieldsData := make([]*schemapb.FieldData, len(results.FieldsData))
copy(originalFieldsData, results.FieldsData)
originalOutputFields := make([]string, len(results.OutputFields))
copy(originalOutputFields, results.OutputFields)
reconstructStructFieldData(results, schema)
// Should not modify anything when no struct array fields
assert.Equal(t, originalFieldsData, results.FieldsData)
assert.Equal(t, originalOutputFields, results.OutputFields)
})
t.Run("reconstruct single struct field", func(t *testing.T) {
// Create mock data
subField1Data := &schemapb.FieldData{
FieldName: "sub_int_array",
FieldId: 1021,
Type: schemapb.DataType_Array,
Field: &schemapb.FieldData_Scalars{
Scalars: &schemapb.ScalarField{
Data: &schemapb.ScalarField_ArrayData{
ArrayData: &schemapb.ArrayArray{
ElementType: schemapb.DataType_Int32,
Data: []*schemapb.ScalarField{
{
Data: &schemapb.ScalarField_IntData{
IntData: &schemapb.IntArray{Data: []int32{1, 2, 3}},
},
},
},
},
},
},
},
}
subField2Data := &schemapb.FieldData{
FieldName: "sub_text_array",
FieldId: 1022,
Type: schemapb.DataType_Array,
Field: &schemapb.FieldData_Scalars{
Scalars: &schemapb.ScalarField{
Data: &schemapb.ScalarField_ArrayData{
ArrayData: &schemapb.ArrayArray{
ElementType: schemapb.DataType_VarChar,
Data: []*schemapb.ScalarField{
{
Data: &schemapb.ScalarField_StringData{
StringData: &schemapb.StringArray{Data: []string{"hello", "world"}},
},
},
},
},
},
},
},
}
results := &milvuspb.QueryResults{
OutputFields: []string{"sub_int_array", "sub_text_array"},
FieldsData: []*schemapb.FieldData{subField1Data, subField2Data},
}
schema := &schemapb.CollectionSchema{
Fields: []*schemapb.FieldSchema{
{
FieldID: 100,
Name: "pk",
IsPrimaryKey: true,
DataType: schemapb.DataType_Int64,
},
},
StructArrayFields: []*schemapb.StructArrayFieldSchema{
{
FieldID: 102,
Name: "test_struct",
Fields: []*schemapb.FieldSchema{
{
FieldID: 1021,
Name: "sub_int_array",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_Int32,
},
{
FieldID: 1022,
Name: "sub_text_array",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_VarChar,
},
},
},
},
}
reconstructStructFieldData(results, schema)
// Check result
assert.Len(t, results.FieldsData, 1, "Should only have one reconstructed struct field")
assert.Len(t, results.OutputFields, 1, "Output fields should only have one")
structField := results.FieldsData[0]
assert.Equal(t, "test_struct", structField.FieldName)
assert.Equal(t, int64(102), structField.FieldId)
assert.Equal(t, schemapb.DataType_ArrayOfStruct, structField.Type)
assert.Equal(t, "test_struct", results.OutputFields[0])
// Check fields inside struct
structArrays := structField.GetStructArrays()
assert.NotNil(t, structArrays)
assert.Len(t, structArrays.Fields, 2, "Struct should contain 2 sub fields")
// Check sub fields
var foundIntField, foundTextField bool
for _, field := range structArrays.Fields {
switch field.FieldId {
case 1021:
assert.Equal(t, "sub_int_array", field.FieldName)
assert.Equal(t, schemapb.DataType_Array, field.Type)
foundIntField = true
case 1022:
assert.Equal(t, "sub_text_array", field.FieldName)
assert.Equal(t, schemapb.DataType_Array, field.Type)
foundTextField = true
}
}
assert.True(t, foundIntField, "Should find int array field")
assert.True(t, foundTextField, "Should find text array field")
})
t.Run("mixed regular and struct fields", func(t *testing.T) {
// Create regular field data
regularField := &schemapb.FieldData{
FieldName: "regular_field",
FieldId: 100,
Type: schemapb.DataType_Int64,
Field: &schemapb.FieldData_Scalars{
Scalars: &schemapb.ScalarField{
Data: &schemapb.ScalarField_LongData{
LongData: &schemapb.LongArray{Data: []int64{1, 2, 3}},
},
},
},
}
// Create struct sub field data
subFieldData := &schemapb.FieldData{
FieldName: "sub_field",
FieldId: 1021,
Type: schemapb.DataType_Array,
Field: &schemapb.FieldData_Scalars{
Scalars: &schemapb.ScalarField{
Data: &schemapb.ScalarField_ArrayData{
ArrayData: &schemapb.ArrayArray{
ElementType: schemapb.DataType_Int32,
Data: []*schemapb.ScalarField{
{
Data: &schemapb.ScalarField_IntData{
IntData: &schemapb.IntArray{Data: []int32{10, 20}},
},
},
},
},
},
},
},
}
results := &milvuspb.QueryResults{
OutputFields: []string{"regular_field", "sub_field"},
FieldsData: []*schemapb.FieldData{regularField, subFieldData},
}
schema := &schemapb.CollectionSchema{
Fields: []*schemapb.FieldSchema{
{
FieldID: 100,
Name: "regular_field",
DataType: schemapb.DataType_Int64,
},
},
StructArrayFields: []*schemapb.StructArrayFieldSchema{
{
FieldID: 102,
Name: "test_struct",
Fields: []*schemapb.FieldSchema{
{
FieldID: 1021,
Name: "sub_field",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_Int32,
},
},
},
},
}
reconstructStructFieldData(results, schema)
// Check result: should have 2 fields (1 regular + 1 reconstructed struct)
assert.Len(t, results.FieldsData, 2)
assert.Len(t, results.OutputFields, 2)
// Check regular and struct fields both exist
var foundRegularField, foundStructField bool
for i, field := range results.FieldsData {
switch field.FieldId {
case 100:
assert.Equal(t, "regular_field", field.FieldName)
assert.Equal(t, schemapb.DataType_Int64, field.Type)
assert.Equal(t, "regular_field", results.OutputFields[i])
foundRegularField = true
case 102:
assert.Equal(t, "test_struct", field.FieldName)
assert.Equal(t, schemapb.DataType_ArrayOfStruct, field.Type)
assert.Equal(t, "test_struct", results.OutputFields[i])
foundStructField = true
}
}
assert.True(t, foundRegularField, "Should find regular field")
assert.True(t, foundStructField, "Should find reconstructed struct field")
})
t.Run("multiple struct fields", func(t *testing.T) {
// Create sub field for first struct
struct1SubField := &schemapb.FieldData{
FieldName: "struct1_sub",
FieldId: 1021,
Type: schemapb.DataType_Array,
}
// Create sub fields for second struct
struct2SubField1 := &schemapb.FieldData{
FieldName: "struct2_sub1",
FieldId: 1031,
Type: schemapb.DataType_Array,
}
struct2SubField2 := &schemapb.FieldData{
FieldName: "struct2_sub2",
FieldId: 1032,
Type: schemapb.DataType_Array,
}
results := &milvuspb.QueryResults{
OutputFields: []string{"struct1_sub", "struct2_sub1", "struct2_sub2"},
FieldsData: []*schemapb.FieldData{struct1SubField, struct2SubField1, struct2SubField2},
}
schema := &schemapb.CollectionSchema{
StructArrayFields: []*schemapb.StructArrayFieldSchema{
{
FieldID: 102,
Name: "struct1",
Fields: []*schemapb.FieldSchema{
{
FieldID: 1021,
Name: "struct1_sub",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_Int32,
},
},
},
{
FieldID: 103,
Name: "struct2",
Fields: []*schemapb.FieldSchema{
{
FieldID: 1031,
Name: "struct2_sub1",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_VarChar,
},
{
FieldID: 1032,
Name: "struct2_sub2",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_Float,
},
},
},
},
}
reconstructStructFieldData(results, schema)
// Check result: should have 2 reconstructed struct fields
assert.Len(t, results.FieldsData, 2)
assert.Len(t, results.OutputFields, 2)
// Check both struct fields are reconstructed correctly
foundStruct1, foundStruct2 := false, false
for i, field := range results.FieldsData {
switch field.FieldId {
case 102:
assert.Equal(t, "struct1", field.FieldName)
assert.Equal(t, schemapb.DataType_ArrayOfStruct, field.Type)
assert.Equal(t, "struct1", results.OutputFields[i])
structArrays := field.GetStructArrays()
assert.NotNil(t, structArrays)
assert.Len(t, structArrays.Fields, 1)
assert.Equal(t, int64(1021), structArrays.Fields[0].FieldId)
foundStruct1 = true
case 103:
assert.Equal(t, "struct2", field.FieldName)
assert.Equal(t, schemapb.DataType_ArrayOfStruct, field.Type)
assert.Equal(t, "struct2", results.OutputFields[i])
structArrays := field.GetStructArrays()
assert.NotNil(t, structArrays)
assert.Len(t, structArrays.Fields, 2)
// Check struct2 contains two sub fields
subFieldIds := make([]int64, 0, 2)
for _, subField := range structArrays.Fields {
subFieldIds = append(subFieldIds, subField.FieldId)
}
assert.Contains(t, subFieldIds, int64(1031))
assert.Contains(t, subFieldIds, int64(1032))
foundStruct2 = true
}
}
assert.True(t, foundStruct1, "Should find struct1")
assert.True(t, foundStruct2, "Should find struct2")
})
t.Run("empty fields data", func(t *testing.T) {
results := &milvuspb.QueryResults{
OutputFields: []string{},
FieldsData: []*schemapb.FieldData{},
}
schema := &schemapb.CollectionSchema{
StructArrayFields: []*schemapb.StructArrayFieldSchema{
{
FieldID: 102,
Name: "test_struct",
Fields: []*schemapb.FieldSchema{
{
FieldID: 1021,
Name: "sub_field",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_Int32,
},
},
},
},
}
reconstructStructFieldData(results, schema)
// Empty data should remain unchanged
assert.Len(t, results.FieldsData, 0)
assert.Len(t, results.OutputFields, 0)
})
t.Run("no matching sub fields", func(t *testing.T) {
// Field data does not match any struct definition
regularField := &schemapb.FieldData{
FieldName: "regular_field",
FieldId: 200, // Not in any struct
Type: schemapb.DataType_Int64,
}
results := &milvuspb.QueryResults{
OutputFields: []string{"regular_field"},
FieldsData: []*schemapb.FieldData{regularField},
}
schema := &schemapb.CollectionSchema{
Fields: []*schemapb.FieldSchema{
{
FieldID: 200,
Name: "regular_field",
DataType: schemapb.DataType_Int64,
},
},
StructArrayFields: []*schemapb.StructArrayFieldSchema{
{
FieldID: 102,
Name: "test_struct",
Fields: []*schemapb.FieldSchema{
{
FieldID: 1021,
Name: "sub_field",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_Int32,
},
},
},
},
}
reconstructStructFieldData(results, schema)
// Should only keep the regular field, no struct field
assert.Len(t, results.FieldsData, 1)
assert.Len(t, results.OutputFields, 1)
assert.Equal(t, int64(200), results.FieldsData[0].FieldId)
assert.Equal(t, "regular_field", results.OutputFields[0])
})
}
+7
View File
@@ -863,6 +863,13 @@ func (t *searchTask) estimateResultSize(nq int64, topK int64) (int64, error) {
vectorOutputFields := lo.Filter(t.schema.GetFields(), func(field *schemapb.FieldSchema, _ int) bool {
return lo.Contains(t.translatedOutputFields, field.GetName()) && typeutil.IsVectorType(field.GetDataType())
})
for _, structArrayField := range t.schema.GetStructArrayFields() {
for _, field := range structArrayField.GetFields() {
if lo.Contains(t.translatedOutputFields, field.GetName()) && typeutil.IsVectorType(field.GetDataType()) {
vectorOutputFields = append(vectorOutputFields, field)
}
}
}
// Currently, we get vectors by requery. Once we support getting vectors from search,
// searches with small result size could no longer need requery.
if len(vectorOutputFields) > 0 {
+368 -22
View File
@@ -72,6 +72,7 @@ const (
testBinaryVecField = "bvec"
testFloat16VecField = "f16vec"
testBFloat16VecField = "bf16vec"
testStructArrayField = "structArray"
testVecDim = 128
testMaxVarCharLength = 100
)
@@ -87,6 +88,7 @@ func genCollectionSchema(collectionName string) *schemapb.CollectionSchema {
testBinaryVecField,
testFloat16VecField,
testBFloat16VecField,
testStructArrayField,
testVecDim,
collectionName)
}
@@ -234,7 +236,7 @@ func constructCollectionSchemaByDataType(collectionName string, fieldName2DataTy
func constructCollectionSchemaWithAllType(
boolField, int32Field, int64Field, floatField, doubleField string,
floatVecField, binaryVecField, float16VecField, bfloat16VecField string,
floatVecField, binaryVecField, float16VecField, bfloat16VecField, structArrayField string,
dim int,
collectionName string,
) *schemapb.CollectionSchema {
@@ -349,30 +351,70 @@ func constructCollectionSchemaWithAllType(
AutoID: false,
}
if enableMultipleVectorFields {
return &schemapb.CollectionSchema{
Name: collectionName,
Description: "",
AutoID: false,
// StructArrayField schema for testing
structArrayFields := []*schemapb.StructArrayFieldSchema{
{
FieldID: 0,
Name: structArrayField,
Description: "test struct array field",
Fields: []*schemapb.FieldSchema{
b,
i32,
i64,
f,
d,
fVec,
bVec,
f16Vec,
bf16Vec,
{
FieldID: 0,
Name: "sub_varchar_array",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_VarChar,
TypeParams: []*commonpb.KeyValuePair{
{
Key: common.MaxLengthKey,
Value: strconv.Itoa(testMaxVarCharLength),
},
{
Key: common.MaxCapacityKey,
Value: "100",
},
},
},
{
FieldID: 0,
Name: "sub_vector_array",
DataType: schemapb.DataType_ArrayOfVector,
ElementType: schemapb.DataType_FloatVector,
TypeParams: []*commonpb.KeyValuePair{
{
Key: common.DimKey,
Value: strconv.Itoa(dim),
},
{
Key: common.MaxCapacityKey,
Value: "10",
},
},
},
},
}
},
}
return &schemapb.CollectionSchema{
Name: collectionName,
Description: "",
AutoID: false,
Fields: []*schemapb.FieldSchema{
schema := &schemapb.CollectionSchema{
Name: collectionName,
Description: "",
AutoID: false,
StructArrayFields: structArrayFields,
}
if enableMultipleVectorFields {
schema.Fields = []*schemapb.FieldSchema{
b,
i32,
i64,
f,
d,
fVec,
bVec,
f16Vec,
bf16Vec,
}
} else {
schema.Fields = []*schemapb.FieldSchema{
b,
i32,
i64,
@@ -380,8 +422,10 @@ func constructCollectionSchemaWithAllType(
d,
fVec,
// bVec,
},
}
}
return schema
}
func constructPlaceholderGroup(
@@ -1280,6 +1324,48 @@ func TestCreateCollectionTask(t *testing.T) {
} else {
assert.Error(t, err)
}
task.CreateCollectionRequest = reqBackup
// Test StructArrayField validation
structArrayFieldSchema := constructCollectionSchemaWithStructArrayField(collectionName+"_struct", testStructArrayField, false)
// Test valid StructArrayField
validStructSchema, err := proto.Marshal(structArrayFieldSchema)
assert.NoError(t, err)
task.CreateCollectionRequest.Schema = validStructSchema
err = task.PreExecute(ctx)
assert.NoError(t, err)
// Test invalid StructArrayField - empty fields
invalidStructSchema := proto.Clone(structArrayFieldSchema).(*schemapb.CollectionSchema)
invalidStructSchema.StructArrayFields[0].Fields = []*schemapb.FieldSchema{} // Empty fields
invalidStructSchemaBytes, err := proto.Marshal(invalidStructSchema)
assert.NoError(t, err)
task.CreateCollectionRequest.Schema = invalidStructSchemaBytes
err = task.PreExecute(ctx)
assert.Error(t, err)
// Test invalid StructArrayField - invalid field name
invalidStructSchema2 := proto.Clone(structArrayFieldSchema).(*schemapb.CollectionSchema)
invalidStructSchema2.StructArrayFields[0].Fields[0].Name = "$invalid" // Invalid field name
invalidStructSchema2Bytes, err := proto.Marshal(invalidStructSchema2)
assert.NoError(t, err)
task.CreateCollectionRequest.Schema = invalidStructSchema2Bytes
err = task.PreExecute(ctx)
assert.Error(t, err)
// Test invalid StructArrayField - non-array/non-vector-array sub-field
invalidStructSchema3 := proto.Clone(structArrayFieldSchema).(*schemapb.CollectionSchema)
invalidStructSchema3.StructArrayFields[0].Fields[0].DataType = schemapb.DataType_Int64 // Should be Array or ArrayOfVector
invalidStructSchema3Bytes, err := proto.Marshal(invalidStructSchema3)
assert.NoError(t, err)
task.CreateCollectionRequest.Schema = invalidStructSchema3Bytes
err = task.PreExecute(ctx)
assert.Error(t, err)
// Restore original schema for remaining tests
task.CreateCollectionRequest = reqBackup
})
t.Run("specify dynamic field", func(t *testing.T) {
@@ -4912,3 +4998,263 @@ func TestAlterCollectionField(t *testing.T) {
})
}
}
// constructCollectionSchemaWithStructArrayField constructs a collection schema specifically for testing StructArrayField
func constructCollectionSchemaWithStructArrayField(collectionName string, structArrayFieldName string, autoID bool) *schemapb.CollectionSchema {
// Primary key field
pkField := &schemapb.FieldSchema{
FieldID: 100,
Name: testInt64Field,
IsPrimaryKey: true,
Description: "primary key field",
DataType: schemapb.DataType_Int64,
AutoID: autoID,
}
// Vector field
vecField := &schemapb.FieldSchema{
FieldID: 101,
Name: testFloatVecField,
IsPrimaryKey: false,
Description: "float vector field",
DataType: schemapb.DataType_FloatVector,
TypeParams: []*commonpb.KeyValuePair{
{
Key: common.DimKey,
Value: strconv.Itoa(testVecDim),
},
},
}
// StructArrayField with various sub-fields for comprehensive testing
structArrayField := &schemapb.StructArrayFieldSchema{
FieldID: 102,
Name: structArrayFieldName,
Description: "struct array field for testing",
Fields: []*schemapb.FieldSchema{
{
FieldID: 1021,
Name: "sub_text_array",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_VarChar,
TypeParams: []*commonpb.KeyValuePair{
{
Key: common.MaxLengthKey,
Value: strconv.Itoa(testMaxVarCharLength),
},
{
Key: common.MaxCapacityKey,
Value: "50",
},
},
},
{
FieldID: 1022,
Name: "sub_int_array",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_Int32,
TypeParams: []*commonpb.KeyValuePair{
{
Key: common.MaxCapacityKey,
Value: "20",
},
},
},
{
FieldID: 1023,
Name: "sub_float_vector_array",
DataType: schemapb.DataType_ArrayOfVector,
ElementType: schemapb.DataType_FloatVector,
TypeParams: []*commonpb.KeyValuePair{
{
Key: common.DimKey,
Value: strconv.Itoa(testVecDim),
},
{
Key: common.MaxCapacityKey,
Value: "5",
},
},
},
},
}
return &schemapb.CollectionSchema{
Name: collectionName,
Description: "test collection with struct array field",
AutoID: autoID,
Fields: []*schemapb.FieldSchema{pkField, vecField},
StructArrayFields: []*schemapb.StructArrayFieldSchema{structArrayField},
}
}
// TestCreateCollectionTaskWithStructArrayField tests creating collections with StructArrayField
func TestCreateCollectionTaskWithStructArrayField(t *testing.T) {
mix := NewMixCoordMock()
ctx := context.Background()
shardsNum := common.DefaultShardsNum
prefix := "TestCreateCollectionTaskWithStructArrayField"
dbName := ""
collectionName := prefix + funcutil.GenRandomStr()
// Test with StructArrayField
schema := constructCollectionSchemaWithStructArrayField(collectionName, testStructArrayField, false)
marshaledSchema, err := proto.Marshal(schema)
assert.NoError(t, err)
task := &createCollectionTask{
Condition: NewTaskCondition(ctx),
CreateCollectionRequest: &milvuspb.CreateCollectionRequest{
Base: nil,
DbName: dbName,
CollectionName: collectionName,
Schema: marshaledSchema,
ShardsNum: shardsNum,
},
ctx: ctx,
mixCoord: mix,
result: nil,
schema: nil,
}
t.Run("create collection with struct array field", func(t *testing.T) {
err := task.OnEnqueue()
assert.NoError(t, err)
err = task.PreExecute(ctx)
assert.NoError(t, err)
// Verify schema contains StructArrayFields
assert.NotNil(t, task.schema)
assert.Len(t, task.schema.StructArrayFields, 1)
structArrayField := task.schema.StructArrayFields[0]
assert.Equal(t, testStructArrayField, structArrayField.Name)
assert.Len(t, structArrayField.Fields, 3)
// Verify sub-fields in StructArrayField
subFields := structArrayField.Fields
// sub_text_array
assert.Equal(t, "sub_text_array", subFields[0].Name)
assert.Equal(t, schemapb.DataType_Array, subFields[0].DataType)
assert.Equal(t, schemapb.DataType_VarChar, subFields[0].ElementType)
// sub_int_array
assert.Equal(t, "sub_int_array", subFields[1].Name)
assert.Equal(t, schemapb.DataType_Array, subFields[1].DataType)
assert.Equal(t, schemapb.DataType_Int32, subFields[1].ElementType)
// sub_float_vector_array
assert.Equal(t, "sub_float_vector_array", subFields[2].Name)
assert.Equal(t, schemapb.DataType_ArrayOfVector, subFields[2].DataType)
assert.Equal(t, schemapb.DataType_FloatVector, subFields[2].ElementType)
err = task.Execute(ctx)
assert.NoError(t, err)
assert.Equal(t, commonpb.ErrorCode_Success, task.result.ErrorCode)
err = task.PostExecute(ctx)
assert.NoError(t, err)
})
t.Run("validate struct array field constraints", func(t *testing.T) {
// Test invalid sub-field in StructArrayField
invalidSchema := constructCollectionSchemaWithStructArrayField(collectionName+"_invalid", testStructArrayField, false)
// Add an invalid sub-field (nested array)
invalidSubField := &schemapb.FieldSchema{
FieldID: 1024,
Name: "invalid_nested_array",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_Array, // This should be invalid - nested arrays
}
invalidSchema.StructArrayFields[0].Fields = append(invalidSchema.StructArrayFields[0].Fields, invalidSubField)
invalidMarshaledSchema, err := proto.Marshal(invalidSchema)
assert.NoError(t, err)
invalidTask := &createCollectionTask{
Condition: NewTaskCondition(ctx),
CreateCollectionRequest: &milvuspb.CreateCollectionRequest{
Base: nil,
DbName: dbName,
CollectionName: collectionName + "_invalid",
Schema: invalidMarshaledSchema,
ShardsNum: shardsNum,
},
ctx: ctx,
mixCoord: mix,
result: nil,
schema: nil,
}
err = invalidTask.OnEnqueue()
assert.NoError(t, err)
err = invalidTask.PreExecute(ctx)
assert.Error(t, err) // Should fail due to nested array validation
})
}
// TestDescribeCollectionTaskWithStructArrayField tests describing collections with StructArrayField
func TestDescribeCollectionTaskWithStructArrayField(t *testing.T) {
mix := NewMixCoordMock()
ctx := context.Background()
prefix := "TestDescribeCollectionTaskWithStructArrayField"
collectionName := prefix + funcutil.GenRandomStr()
// Create collection with StructArrayField first
schema := constructCollectionSchemaWithStructArrayField(collectionName, testStructArrayField, true)
marshaledSchema, err := proto.Marshal(schema)
assert.NoError(t, err)
createTask := &createCollectionTask{
Condition: NewTaskCondition(ctx),
CreateCollectionRequest: &milvuspb.CreateCollectionRequest{
CollectionName: collectionName,
Schema: marshaledSchema,
},
ctx: ctx,
mixCoord: mix,
}
err = createTask.OnEnqueue()
assert.NoError(t, err)
err = createTask.PreExecute(ctx)
assert.NoError(t, err)
err = createTask.Execute(ctx)
assert.NoError(t, err)
// Now test describe collection
describeTask := &describeCollectionTask{
Condition: NewTaskCondition(ctx),
DescribeCollectionRequest: &milvuspb.DescribeCollectionRequest{
CollectionName: collectionName,
},
ctx: ctx,
mixCoord: mix,
}
t.Run("describe collection with struct array field", func(t *testing.T) {
err := describeTask.PreExecute(ctx)
assert.NoError(t, err)
err = describeTask.Execute(ctx)
assert.NoError(t, err)
assert.NotNil(t, describeTask.result)
assert.Equal(t, commonpb.ErrorCode_Success, describeTask.result.Status.ErrorCode)
// Verify StructArrayFields are returned in describe response
resultSchema := describeTask.result.Schema
assert.NotNil(t, resultSchema)
assert.Len(t, resultSchema.StructArrayFields, 1)
structArrayField := resultSchema.StructArrayFields[0]
assert.Equal(t, testStructArrayField, structArrayField.Name)
assert.Len(t, structArrayField.Fields, 3)
err = describeTask.PostExecute(ctx)
assert.NoError(t, err)
})
}
+13 -3
View File
@@ -205,10 +205,20 @@ func (it *upsertTask) insertPreExecute(ctx context.Context) error {
}
}
err := checkAndFlattenStructFieldData(it.schema.CollectionSchema, it.upsertMsg.InsertMsg)
if err != nil {
return err
}
allFields := make([]*schemapb.FieldSchema, 0, len(it.schema.Fields)+5)
allFields = append(allFields, it.schema.Fields...)
for _, structField := range it.schema.GetStructArrayFields() {
allFields = append(allFields, structField.GetFields()...)
}
// use the passed pk as new pk when autoID == false
// automatic generate pk as new pk wehen autoID == true
var err error
it.result.IDs, it.oldIDs, err = checkUpsertPrimaryFieldData(it.schema.CollectionSchema, it.upsertMsg.InsertMsg)
it.result.IDs, it.oldIDs, err = checkUpsertPrimaryFieldData(allFields, it.schema.CollectionSchema, it.upsertMsg.InsertMsg)
log := log.Ctx(ctx).With(zap.String("collectionName", it.upsertMsg.InsertMsg.CollectionName))
if err != nil {
log.Warn("check primary field data and hash primary key failed when upsert",
@@ -217,7 +227,7 @@ func (it *upsertTask) insertPreExecute(ctx context.Context) error {
}
// check varchar/text with analyzer was utf-8 format
err = checkInputUtf8Compatiable(it.schema.CollectionSchema, it.upsertMsg.InsertMsg)
err = checkInputUtf8Compatiable(allFields, it.upsertMsg.InsertMsg)
if err != nil {
log.Warn("check varchar/text format failed", zap.Error(err))
return err
+258 -101
View File
@@ -439,14 +439,31 @@ func validateVectorFieldMetricType(field *schemapb.FieldSchema) error {
return fmt.Errorf(`index param "metric_type" is not specified for index float vector %s`, field.GetName())
}
func validateDuplicatedFieldName(fields []*schemapb.FieldSchema) error {
func validateDuplicatedFieldName(schema *schemapb.CollectionSchema) error {
names := make(map[string]bool)
for _, field := range fields {
_, ok := names[field.Name]
validateFieldNames := func(name string) error {
_, ok := names[name]
if ok {
return errors.Newf("duplicated field name %s found", field.GetName())
return errors.Newf("duplicated field name %s found", name)
}
names[name] = true
return nil
}
for _, field := range schema.Fields {
if err := validateFieldNames(field.Name); err != nil {
return err
}
}
for _, structArrayField := range schema.StructArrayFields {
if err := validateFieldNames(structArrayField.Name); err != nil {
return err
}
for _, field := range structArrayField.Fields {
if err := validateFieldNames(field.Name); err != nil {
return err
}
}
names[field.Name] = true
}
return nil
}
@@ -477,6 +494,14 @@ func validateFieldType(schema *schemapb.CollectionSchema) error {
}
}
}
for _, structArrayField := range schema.StructArrayFields {
for _, field := range structArrayField.Fields {
if field.GetDataType() != schemapb.DataType_Array && field.GetDataType() != schemapb.DataType_ArrayOfVector {
return errors.Newf("fields in StructArrayField must be Array or ArrayOfVector, field name = %s, field type = %s",
field.GetName(), field.GetDataType().String())
}
}
}
return nil
}
@@ -494,6 +519,13 @@ func ValidateFieldAutoID(coll *schemapb.CollectionSchema) error {
}
}
}
for _, structArrayField := range coll.StructArrayFields {
for _, field := range structArrayField.Fields {
if field.AutoID {
return errors.Newf("autoID is not supported for struct field, field name = %s", field.Name)
}
}
}
return nil
}
@@ -527,6 +559,11 @@ func ValidateField(field *schemapb.FieldSchema, schema *schemapb.CollectionSchem
return err
}
}
if field.DataType == schemapb.DataType_ArrayOfVector {
return fmt.Errorf("array of vector can only be in the struct array field, field name: %s", field.Name)
}
// TODO should remove the index params in the field schema
indexParams := funcutil.KeyValuePair2Map(field.GetIndexParams())
if err = ValidateAutoIndexMmapConfig(isVectorType, indexParams); err != nil {
@@ -539,6 +576,68 @@ func ValidateField(field *schemapb.FieldSchema, schema *schemapb.CollectionSchem
return nil
}
func ValidateFieldsInStruct(field *schemapb.FieldSchema, schema *schemapb.CollectionSchema) error {
// validate field name
var err error
if err := validateFieldName(field.Name); err != nil {
return err
}
if field.DataType != schemapb.DataType_Array && field.DataType != schemapb.DataType_ArrayOfVector {
return fmt.Errorf("Fields in StructArrayField can only be array or array of struct, but field %s is %s", field.Name, field.DataType.String())
}
if field.ElementType == schemapb.DataType_ArrayOfStruct || field.ElementType == schemapb.DataType_ArrayOfVector ||
field.ElementType == schemapb.DataType_Array {
return fmt.Errorf("Nested array is not supported %s", field.Name)
}
if field.DataType == schemapb.DataType_Array {
if typeutil.IsVectorType(field.GetElementType()) {
return fmt.Errorf("Inconsistent schema: element type of array field %s is a vector type", field.Name)
}
} else {
if !typeutil.IsVectorType(field.GetElementType()) {
return fmt.Errorf("Inconsistent schema: element type of array field %s is not a vector type", field.Name)
}
err = validateDimension(field)
if err != nil {
return err
}
}
// valid max length per row parameters
// if max_length not specified, return error
if field.ElementType == schemapb.DataType_VarChar {
err = validateMaxLengthPerRow(schema.Name, field)
if err != nil {
return err
}
}
// todo(SpadeA): make nullable field in struct array supported
if field.GetNullable() {
return fmt.Errorf("nullable is not supported for fields in struct array now, fieldName = %s", field.Name)
}
// todo(SpadeA): add more check when index is enabled
return nil
}
func ValidateStructArrayField(structArrayField *schemapb.StructArrayFieldSchema, schema *schemapb.CollectionSchema) error {
if len(structArrayField.Fields) == 0 {
return fmt.Errorf("struct array field %s has no sub-fields", structArrayField.Name)
}
for _, subField := range structArrayField.Fields {
if err := ValidateFieldsInStruct(subField, schema); err != nil {
return err
}
}
return nil
}
func validateMultiAnalyzerParams(params string, coll *schemapb.CollectionSchema) error {
var m map[string]json.RawMessage
var analyzerMap map[string]json.RawMessage
@@ -667,6 +766,15 @@ func validatePrimaryKey(coll *schemapb.CollectionSchema) error {
if idx == -1 {
return errors.New("primary key is not specified")
}
for _, structArrayField := range coll.StructArrayFields {
for _, field := range structArrayField.Fields {
if field.IsPrimaryKey {
return errors.Newf("primary key is not supported for struct field, field name = %s", field.Name)
}
}
}
return nil
}
@@ -723,93 +831,6 @@ func validateMetricType(dataType schemapb.DataType, metricTypeStrRaw string) err
return fmt.Errorf("data_type %s mismatch with metric_type %s", dataType.String(), metricTypeStrRaw)
}
func validateSchema(coll *schemapb.CollectionSchema) error {
autoID := coll.AutoID
primaryIdx := -1
idMap := make(map[int64]int) // fieldId -> idx
nameMap := make(map[string]int) // name -> idx
for idx, field := range coll.Fields {
// check system field
if field.FieldID < 100 {
// System Fields, not injected yet
return fmt.Errorf("fieldID(%d) that is less than 100 is reserved for system fields: %s", field.FieldID, field.Name)
}
// primary key detector
if field.IsPrimaryKey {
if autoID {
return errors.New("autoId forbids primary key")
} else if primaryIdx != -1 {
return fmt.Errorf("there are more than one primary key, field name = %s, %s", coll.Fields[primaryIdx].Name, field.Name)
}
if field.DataType != schemapb.DataType_Int64 {
return errors.New("type of primary key should be int64")
}
primaryIdx = idx
}
// check unique
elemIdx, ok := idMap[field.FieldID]
if ok {
return fmt.Errorf("duplicate field ids: %d", coll.Fields[elemIdx].FieldID)
}
idMap[field.FieldID] = idx
elemIdx, ok = nameMap[field.Name]
if ok {
return fmt.Errorf("duplicate field names: %s", coll.Fields[elemIdx].Name)
}
nameMap[field.Name] = idx
isVec, err3 := isVector(field.DataType)
if err3 != nil {
return err3
}
if isVec {
indexKv, err1 := RepeatedKeyValToMap(field.IndexParams)
if err1 != nil {
return err1
}
typeKv, err2 := RepeatedKeyValToMap(field.TypeParams)
if err2 != nil {
return err2
}
if !typeutil.IsSparseFloatVectorType(field.DataType) {
dimStr, ok := typeKv[common.DimKey]
if !ok {
return fmt.Errorf("dim not found in type_params for vector field %s(%d)", field.Name, field.FieldID)
}
dim, err := strconv.Atoi(dimStr)
if err != nil || dim < 0 {
return fmt.Errorf("invalid dim; %s", dimStr)
}
}
metricTypeStr, ok := indexKv[common.MetricTypeKey]
if ok {
err4 := validateMetricType(field.DataType, metricTypeStr)
if err4 != nil {
return err4
}
}
// in C++, default type will be specified
// do nothing
} else {
if len(field.IndexParams) != 0 {
return fmt.Errorf("index params is not empty for scalar field: %s(%d)", field.Name, field.FieldID)
}
if len(field.TypeParams) != 0 {
return fmt.Errorf("type params is not empty for scalar field: %s(%d)", field.Name, field.FieldID)
}
}
}
if !autoID && primaryIdx == -1 {
return errors.New("primary key is required for non autoid mode")
}
return nil
}
func validateFunction(coll *schemapb.CollectionSchema) error {
nameMap := lo.SliceToMap(coll.GetFields(), func(field *schemapb.FieldSchema) (string, *schemapb.FieldSchema) {
return field.GetName(), field
@@ -988,6 +1009,8 @@ func validateMultipleVectorFields(schema *schemapb.CollectionSchema) error {
}
}
// todo(Spadea): should be there any check between vectors in struct fields?
return nil
}
@@ -1019,6 +1042,21 @@ func validateLoadFieldsList(schema *schemapb.CollectionSchema) error {
}
}
for _, structArrayField := range schema.StructArrayFields {
for _, field := range structArrayField.Fields {
shouldLoad, err := common.ShouldFieldBeLoaded(field.GetTypeParams())
if err != nil {
return err
}
if shouldLoad {
if typeutil.IsVectorType(field.ElementType) {
vectorCnt++
}
continue
}
}
}
if vectorCnt == 0 {
return merr.WrapErrParameterInvalidMsg("cannot config all vector field(s) skip loading")
}
@@ -1122,6 +1160,13 @@ func fillFieldPropertiesBySchema(columns []*schemapb.FieldData, schema *schemapb
}
}
for _, structField := range schema.GetStructArrayFields() {
for _, field := range structField.GetFields() {
fieldName2Schema[field.Name] = field
expectColumnNum++
}
}
if len(columns) != expectColumnNum {
return fmt.Errorf("len(columns) mismatch the expectColumnNum, expectColumnNum: %d, len(columns): %d",
expectColumnNum, len(columns))
@@ -1140,6 +1185,13 @@ func fillFieldPropertiesBySchema(columns []*schemapb.FieldData, schema *schemapb
" collectionName:%s", fieldData.FieldName, schema.Name)
}
fd.Scalars.GetArrayData().ElementType = fieldSchema.ElementType
} else if fieldData.Type == schemapb.DataType_ArrayOfVector {
fd, ok := fieldData.Field.(*schemapb.FieldData_Vectors)
if !ok {
return fmt.Errorf("field convert FieldData_Vectors fail in fieldData, fieldName: %s,"+
" collectionName:%s", fieldData.FieldName, schema.Name)
}
fd.Vectors.GetVectorArray().ElementType = fieldSchema.ElementType
}
} else {
return fmt.Errorf("fieldName %v not exist in collection schema", fieldData.FieldName)
@@ -1503,6 +1555,18 @@ func translateOutputFields(outputFields []string, schema *schemaInfo, removePkFi
allFieldNameMap[field.Name] = field
}
// User may specify a struct array field or some specific fields in the struct array field
for _, subStruct := range schema.StructArrayFields {
for _, field := range subStruct.Fields {
allFieldNameMap[field.Name] = field
}
}
structArrayNameToFields := make(map[string][]*schemapb.FieldSchema)
for _, subStruct := range schema.StructArrayFields {
structArrayNameToFields[subStruct.Name] = subStruct.Fields
}
userRequestedPkFieldExplicitly := false
for _, outputFieldName := range outputFields {
@@ -1520,6 +1584,15 @@ func translateOutputFields(outputFields []string, schema *schemaInfo, removePkFi
}
useAllDyncamicFields = true
} else {
if structArrayField, ok := structArrayNameToFields[outputFieldName]; ok {
for _, field := range structArrayField {
if schema.CanRetrieveRawFieldData(field) {
resultFieldNameMap[field.Name] = true
userOutputFieldsMap[field.Name] = true
}
}
continue
}
if field, ok := allFieldNameMap[outputFieldName]; ok {
if !schema.CanRetrieveRawFieldData(field) {
return nil, nil, nil, false, fmt.Errorf("not allowed to retrieve raw data of field %s", outputFieldName)
@@ -1651,7 +1724,7 @@ func isPartitionLoaded(ctx context.Context, mc types.MixCoordClient, collID int6
return true, nil
}
func checkFieldsDataBySchema(schema *schemapb.CollectionSchema, insertMsg *msgstream.InsertMsg, inInsert bool) error {
func checkFieldsDataBySchema(allFields []*schemapb.FieldSchema, schema *schemapb.CollectionSchema, insertMsg *msgstream.InsertMsg, inInsert bool) error {
log := log.With(zap.String("collection", schema.GetName()))
primaryKeyNum := 0
autoGenFieldNum := 0
@@ -1665,7 +1738,7 @@ func checkFieldsDataBySchema(schema *schemapb.CollectionSchema, insertMsg *msgst
dataNameSet.Insert(fieldName)
}
for _, fieldSchema := range schema.Fields {
for _, fieldSchema := range allFields {
if fieldSchema.AutoID && !fieldSchema.IsPrimaryKey {
log.Warn("not primary key field, but set autoID true", zap.String("field", fieldSchema.GetName()))
return merr.WrapErrParameterInvalidMsg("only primary key could be with AutoID enabled")
@@ -1707,7 +1780,7 @@ func checkFieldsDataBySchema(schema *schemapb.CollectionSchema, insertMsg *msgst
zap.Int64("primaryKeyNum", int64(primaryKeyNum)))
return merr.WrapErrParameterInvalidMsg("more than 1 primary keys not supported, got %d", primaryKeyNum)
}
expectedNum := len(schema.Fields)
expectedNum := len(allFields)
actualNum := len(insertMsg.FieldsData) + autoGenFieldNum
if expectedNum != actualNum {
@@ -1718,7 +1791,91 @@ func checkFieldsDataBySchema(schema *schemapb.CollectionSchema, insertMsg *msgst
return nil
}
func checkPrimaryFieldData(schema *schemapb.CollectionSchema, insertMsg *msgstream.InsertMsg) (*schemapb.IDs, error) {
// checkAndFlattenStructFieldData verifies the array length of the struct array field data in the insert message
// and then flattens the data so that data node and query node have not to handle the struct array field data.
func checkAndFlattenStructFieldData(schema *schemapb.CollectionSchema, insertMsg *msgstream.InsertMsg) error {
structSchemaMap := make(map[string]*schemapb.StructArrayFieldSchema, len(schema.GetStructArrayFields()))
for _, structField := range schema.GetStructArrayFields() {
structSchemaMap[structField.Name] = structField
}
fieldSchemaMap := make(map[string]*schemapb.FieldSchema, len(schema.GetFields()))
for _, fieldSchema := range schema.GetFields() {
fieldSchemaMap[fieldSchema.Name] = fieldSchema
}
structFieldCount := 0
flattenedFields := make([]*schemapb.FieldData, 0, len(insertMsg.GetFieldsData())+5)
for _, fieldData := range insertMsg.GetFieldsData() {
if _, ok := fieldSchemaMap[fieldData.FieldName]; ok {
flattenedFields = append(flattenedFields, fieldData)
continue
}
structSchema, ok := structSchemaMap[fieldData.FieldName]
if !ok {
return fmt.Errorf("fieldName %v not exist in collection schema, fieldType %v, fieldId %v", fieldData.FieldName, fieldData.Type, fieldData.FieldId)
}
structFieldCount++
structArrays, ok := fieldData.Field.(*schemapb.FieldData_StructArrays)
if !ok {
return fmt.Errorf("field convert FieldData_StructArrays fail in fieldData, fieldName: %s,"+
" collectionName:%s", fieldData.FieldName, schema.Name)
}
if len(structArrays.StructArrays.Fields) != len(structSchema.GetFields()) {
return fmt.Errorf("length of fields of struct field mismatch length of the fields in schema, fieldName: %s,"+
" collectionName:%s, fieldData fields length:%d, schema fields length:%d",
fieldData.FieldName, schema.Name, len(structArrays.StructArrays.Fields), len(structSchema.GetFields()))
}
// Check the array length of the struct array field data
expectedArrayLen := -1
for _, subField := range structArrays.StructArrays.Fields {
var currentArrayLen int
switch subFieldData := subField.Field.(type) {
case *schemapb.FieldData_Scalars:
if scalarArray := subFieldData.Scalars.GetArrayData(); scalarArray != nil {
currentArrayLen = len(scalarArray.Data)
} else {
return fmt.Errorf("scalar array data is nil in struct field '%s', sub-field '%s'",
fieldData.FieldName, subField.FieldName)
}
case *schemapb.FieldData_Vectors:
if vectorArray := subFieldData.Vectors.GetVectorArray(); vectorArray != nil {
currentArrayLen = len(vectorArray.Data)
} else {
return fmt.Errorf("vector array data is nil in struct field '%s', sub-field '%s'",
fieldData.FieldName, subField.FieldName)
}
default:
return fmt.Errorf("unexpected field data type in struct array field, fieldName: %s", fieldData.FieldName)
}
if expectedArrayLen == -1 {
expectedArrayLen = currentArrayLen
} else if currentArrayLen != expectedArrayLen {
return fmt.Errorf("inconsistent array length in struct field '%s': expected %d, got %d for sub-field '%s'",
fieldData.FieldName, expectedArrayLen, currentArrayLen, subField.FieldName)
}
flattenedFields = append(flattenedFields, subField)
}
}
if len(schema.GetStructArrayFields()) != structFieldCount {
return fmt.Errorf("the number of struct array fields is not the same as needed, expected: %d, actual: %d",
len(schema.GetStructArrayFields()), structFieldCount)
}
insertMsg.FieldsData = flattenedFields
return nil
}
func checkPrimaryFieldData(allFields []*schemapb.FieldSchema, schema *schemapb.CollectionSchema, insertMsg *msgstream.InsertMsg) (*schemapb.IDs, error) {
log := log.With(zap.String("collectionName", insertMsg.CollectionName))
rowNums := uint32(insertMsg.NRows())
// TODO(dragondriver): in fact, NumRows is not trustable, we should check all input fields
@@ -1726,7 +1883,7 @@ func checkPrimaryFieldData(schema *schemapb.CollectionSchema, insertMsg *msgstre
return nil, merr.WrapErrParameterInvalid("invalid num_rows", fmt.Sprint(rowNums), "num_rows should be greater than 0")
}
if err := checkFieldsDataBySchema(schema, insertMsg, true); err != nil {
if err := checkFieldsDataBySchema(allFields, schema, insertMsg, true); err != nil {
return nil, err
}
@@ -1780,8 +1937,8 @@ func checkPrimaryFieldData(schema *schemapb.CollectionSchema, insertMsg *msgstre
// for some varchar with analzyer
// we need check char format before insert it to message queue
// now only support utf-8
func checkInputUtf8Compatiable(schema *schemapb.CollectionSchema, insertMsg *msgstream.InsertMsg) error {
checkeFields := lo.FilterMap(schema.GetFields(), func(field *schemapb.FieldSchema, _ int) (int64, bool) {
func checkInputUtf8Compatiable(allFields []*schemapb.FieldSchema, insertMsg *msgstream.InsertMsg) error {
checkeFields := lo.FilterMap(allFields, func(field *schemapb.FieldSchema, _ int) (int64, bool) {
if field.DataType == schemapb.DataType_VarChar {
return field.GetFieldID(), true
}
@@ -1819,7 +1976,7 @@ func checkInputUtf8Compatiable(schema *schemapb.CollectionSchema, insertMsg *msg
return nil
}
func checkUpsertPrimaryFieldData(schema *schemapb.CollectionSchema, insertMsg *msgstream.InsertMsg) (*schemapb.IDs, *schemapb.IDs, error) {
func checkUpsertPrimaryFieldData(allFields []*schemapb.FieldSchema, schema *schemapb.CollectionSchema, insertMsg *msgstream.InsertMsg) (*schemapb.IDs, *schemapb.IDs, error) {
log := log.With(zap.String("collectionName", insertMsg.CollectionName))
rowNums := uint32(insertMsg.NRows())
// TODO(dragondriver): in fact, NumRows is not trustable, we should check all input fields
@@ -1827,7 +1984,7 @@ func checkUpsertPrimaryFieldData(schema *schemapb.CollectionSchema, insertMsg *m
return nil, nil, merr.WrapErrParameterInvalid("invalid num_rows", fmt.Sprint(rowNums), "num_rows should be greater than 0")
}
if err := checkFieldsDataBySchema(schema, insertMsg, false); err != nil {
if err := checkFieldsDataBySchema(allFields, schema, insertMsg, false); err != nil {
return nil, nil, err
}
+539 -213
View File
@@ -316,15 +316,40 @@ func TestValidateVectorFieldMetricType(t *testing.T) {
}
func TestValidateDuplicatedFieldName(t *testing.T) {
fields := []*schemapb.FieldSchema{
{Name: "abc"},
{Name: "def"},
schema := &schemapb.CollectionSchema{
Fields: []*schemapb.FieldSchema{
{Name: "abc"},
{Name: "def"},
},
}
assert.Nil(t, validateDuplicatedFieldName(fields))
fields = append(fields, &schemapb.FieldSchema{
assert.Nil(t, validateDuplicatedFieldName(schema))
schema.Fields = append(schema.Fields, &schemapb.FieldSchema{
Name: "abc",
})
assert.NotNil(t, validateDuplicatedFieldName(fields))
assert.NotNil(t, validateDuplicatedFieldName(schema))
}
func TestValidateDuplicatedFieldNameWithStructArrayField(t *testing.T) {
schema := &schemapb.CollectionSchema{
Fields: []*schemapb.FieldSchema{
{Name: "abc"},
{Name: "def"},
},
StructArrayFields: []*schemapb.StructArrayFieldSchema{
{
Name: "struct1",
Fields: []*schemapb.FieldSchema{
{Name: "abc2"},
{Name: "def2"},
},
},
},
}
assert.Nil(t, validateDuplicatedFieldName(schema))
schema.StructArrayFields[0].Fields = append(schema.StructArrayFields[0].Fields, &schemapb.FieldSchema{
Name: "abc",
})
assert.NotNil(t, validateDuplicatedFieldName(schema))
}
func TestValidatePrimaryKey(t *testing.T) {
@@ -543,183 +568,6 @@ func TestValidateFieldType(t *testing.T) {
}
}
func TestValidateSchema(t *testing.T) {
coll := &schemapb.CollectionSchema{
Name: "coll1",
Description: "",
AutoID: false,
Fields: nil,
}
assert.NotNil(t, validateSchema(coll))
pf1 := &schemapb.FieldSchema{
Name: "f1",
FieldID: 100,
IsPrimaryKey: false,
Description: "",
DataType: schemapb.DataType_Int64,
TypeParams: nil,
IndexParams: nil,
}
coll.Fields = append(coll.Fields, pf1)
assert.NotNil(t, validateSchema(coll))
pf1.IsPrimaryKey = true
assert.Nil(t, validateSchema(coll))
pf1.DataType = schemapb.DataType_Int32
assert.NotNil(t, validateSchema(coll))
pf1.DataType = schemapb.DataType_Int64
assert.Nil(t, validateSchema(coll))
pf2 := &schemapb.FieldSchema{
Name: "f2",
FieldID: 101,
IsPrimaryKey: true,
Description: "",
DataType: schemapb.DataType_Int64,
TypeParams: nil,
IndexParams: nil,
}
coll.Fields = append(coll.Fields, pf2)
assert.NotNil(t, validateSchema(coll))
pf2.IsPrimaryKey = false
assert.Nil(t, validateSchema(coll))
pf2.Name = "f1"
assert.NotNil(t, validateSchema(coll))
pf2.Name = "f2"
assert.Nil(t, validateSchema(coll))
pf2.FieldID = 100
assert.NotNil(t, validateSchema(coll))
pf2.FieldID = 101
assert.Nil(t, validateSchema(coll))
pf2.DataType = -1
assert.NotNil(t, validateSchema(coll))
pf2.DataType = schemapb.DataType_FloatVector
assert.NotNil(t, validateSchema(coll))
pf2.DataType = schemapb.DataType_Int64
assert.Nil(t, validateSchema(coll))
tp3Good := []*commonpb.KeyValuePair{
{
Key: common.DimKey,
Value: "128",
},
}
tp3Bad1 := []*commonpb.KeyValuePair{
{
Key: common.DimKey,
Value: "asdfa",
},
}
tp3Bad2 := []*commonpb.KeyValuePair{
{
Key: common.DimKey,
Value: "-1",
},
}
tp3Bad3 := []*commonpb.KeyValuePair{
{
Key: "dimX",
Value: "128",
},
}
tp3Bad4 := []*commonpb.KeyValuePair{
{
Key: common.DimKey,
Value: "128",
},
{
Key: common.DimKey,
Value: "64",
},
}
ip3Good := []*commonpb.KeyValuePair{
{
Key: common.MetricTypeKey,
Value: "IP",
},
}
ip3Bad1 := []*commonpb.KeyValuePair{
{
Key: common.MetricTypeKey,
Value: "JACCARD",
},
}
ip3Bad2 := []*commonpb.KeyValuePair{
{
Key: common.MetricTypeKey,
Value: "xxxxxx",
},
}
ip3Bad3 := []*commonpb.KeyValuePair{
{
Key: common.MetricTypeKey,
Value: "L2",
},
{
Key: common.MetricTypeKey,
Value: "IP",
},
}
pf3 := &schemapb.FieldSchema{
Name: "f3",
FieldID: 102,
IsPrimaryKey: false,
Description: "",
DataType: schemapb.DataType_FloatVector,
TypeParams: tp3Good,
IndexParams: ip3Good,
}
coll.Fields = append(coll.Fields, pf3)
assert.Nil(t, validateSchema(coll))
pf3.TypeParams = tp3Bad1
assert.NotNil(t, validateSchema(coll))
pf3.TypeParams = tp3Bad2
assert.NotNil(t, validateSchema(coll))
pf3.TypeParams = tp3Bad3
assert.NotNil(t, validateSchema(coll))
pf3.TypeParams = tp3Bad4
assert.NotNil(t, validateSchema(coll))
pf3.TypeParams = tp3Good
assert.Nil(t, validateSchema(coll))
pf3.IndexParams = ip3Bad1
assert.NotNil(t, validateSchema(coll))
pf3.IndexParams = ip3Bad2
assert.NotNil(t, validateSchema(coll))
pf3.IndexParams = ip3Bad3
assert.NotNil(t, validateSchema(coll))
pf3.IndexParams = ip3Good
assert.Nil(t, validateSchema(coll))
}
func TestValidateMultipleVectorFields(t *testing.T) {
// case1, no vector field
schema1 := &schemapb.CollectionSchema{}
@@ -1181,7 +1029,7 @@ func Test_InsertTaskcheckFieldsDataBySchema(t *testing.T) {
},
}
err = checkFieldsDataBySchema(task.schema, task.insertMsg, true)
err = checkFieldsDataBySchema(task.schema.Fields, task.schema, task.insertMsg, true)
assert.Equal(t, nil, err)
assert.Equal(t, len(task.insertMsg.FieldsData), 0)
})
@@ -1211,7 +1059,7 @@ func Test_InsertTaskcheckFieldsDataBySchema(t *testing.T) {
},
}
err = checkFieldsDataBySchema(task.schema, task.insertMsg, true)
err = checkFieldsDataBySchema(task.schema.Fields, task.schema, task.insertMsg, true)
assert.ErrorIs(t, merr.ErrParameterInvalid, err)
})
@@ -1252,7 +1100,7 @@ func Test_InsertTaskcheckFieldsDataBySchema(t *testing.T) {
},
}
err = checkFieldsDataBySchema(task.schema, task.insertMsg, true)
err = checkFieldsDataBySchema(task.schema.Fields, task.schema, task.insertMsg, true)
assert.Equal(t, nil, err)
assert.Equal(t, len(task.insertMsg.FieldsData), 2)
})
@@ -1282,7 +1130,7 @@ func Test_InsertTaskcheckFieldsDataBySchema(t *testing.T) {
},
}
err = checkFieldsDataBySchema(task.schema, task.insertMsg, true)
err = checkFieldsDataBySchema(task.schema.Fields, task.schema, task.insertMsg, true)
assert.Equal(t, nil, err)
assert.Equal(t, len(task.insertMsg.FieldsData), 0)
})
@@ -1311,7 +1159,7 @@ func Test_InsertTaskcheckFieldsDataBySchema(t *testing.T) {
},
}
err = checkFieldsDataBySchema(task.schema, task.insertMsg, true)
err = checkFieldsDataBySchema(task.schema.Fields, task.schema, task.insertMsg, true)
assert.ErrorIs(t, merr.ErrParameterInvalid, err)
})
@@ -1345,7 +1193,7 @@ func Test_InsertTaskcheckFieldsDataBySchema(t *testing.T) {
},
}
err = checkFieldsDataBySchema(task.schema, task.insertMsg, true)
err = checkFieldsDataBySchema(task.schema.Fields, task.schema, task.insertMsg, true)
assert.ErrorIs(t, merr.ErrParameterInvalid, err)
})
@@ -1383,7 +1231,7 @@ func Test_InsertTaskcheckFieldsDataBySchema(t *testing.T) {
},
}
err = checkFieldsDataBySchema(task.schema, task.insertMsg, true)
err = checkFieldsDataBySchema(task.schema.Fields, task.schema, task.insertMsg, true)
assert.ErrorIs(t, merr.ErrParameterInvalid, err)
})
@@ -1417,7 +1265,7 @@ func Test_InsertTaskcheckFieldsDataBySchema(t *testing.T) {
},
}
err = checkFieldsDataBySchema(task.schema, task.insertMsg, true)
err = checkFieldsDataBySchema(task.schema.Fields, task.schema, task.insertMsg, true)
assert.ErrorIs(t, merr.ErrParameterInvalid, err)
})
@@ -1451,7 +1299,7 @@ func Test_InsertTaskcheckFieldsDataBySchema(t *testing.T) {
},
}
err = checkFieldsDataBySchema(task.schema, task.insertMsg, true)
err = checkFieldsDataBySchema(task.schema.Fields, task.schema, task.insertMsg, true)
assert.ErrorIs(t, merr.ErrParameterInvalid, err)
})
@@ -1484,7 +1332,7 @@ func Test_InsertTaskcheckFieldsDataBySchema(t *testing.T) {
},
}
err = checkFieldsDataBySchema(task.schema, task.insertMsg, false)
err = checkFieldsDataBySchema(task.schema.Fields, task.schema, task.insertMsg, false)
assert.ErrorIs(t, merr.ErrParameterInvalid, err)
})
t.Run("normal when upsert", func(t *testing.T) {
@@ -1527,7 +1375,7 @@ func Test_InsertTaskcheckFieldsDataBySchema(t *testing.T) {
},
}
err = checkFieldsDataBySchema(task.schema, task.insertMsg, false)
err = checkFieldsDataBySchema(task.schema.Fields, task.schema, task.insertMsg, false)
assert.NoError(t, err)
task = insertTask{
@@ -1568,7 +1416,7 @@ func Test_InsertTaskcheckFieldsDataBySchema(t *testing.T) {
},
},
}
err = checkFieldsDataBySchema(task.schema, task.insertMsg, false)
err = checkFieldsDataBySchema(task.schema.Fields, task.schema, task.insertMsg, false)
assert.NoError(t, err)
})
@@ -1611,7 +1459,7 @@ func Test_InsertTaskcheckFieldsDataBySchema(t *testing.T) {
},
}
err = checkFieldsDataBySchema(task.schema, task.insertMsg, true)
err = checkFieldsDataBySchema(task.schema.Fields, task.schema, task.insertMsg, true)
assert.ErrorIs(t, merr.ErrParameterInvalid, err)
assert.Equal(t, len(task.insertMsg.FieldsData), 2)
@@ -1654,7 +1502,7 @@ func Test_InsertTaskcheckFieldsDataBySchema(t *testing.T) {
},
}
err = checkFieldsDataBySchema(task.schema, task.insertMsg, true)
err = checkFieldsDataBySchema(task.schema.Fields, task.schema, task.insertMsg, true)
assert.NoError(t, err)
assert.Equal(t, len(task.insertMsg.FieldsData), 2)
paramtable.Get().Reset(Params.ProxyCfg.SkipAutoIDCheck.Key)
@@ -1686,7 +1534,7 @@ func Test_InsertTaskCheckPrimaryFieldData(t *testing.T) {
},
}
_, err := checkPrimaryFieldData(case1.schema, case1.insertMsg)
_, err := checkPrimaryFieldData(case1.schema.Fields, case1.schema, case1.insertMsg)
assert.NotEqual(t, nil, err)
// the num of passed fields is less than needed
@@ -1727,7 +1575,7 @@ func Test_InsertTaskCheckPrimaryFieldData(t *testing.T) {
Status: merr.Success(),
},
}
_, err = checkPrimaryFieldData(case2.schema, case2.insertMsg)
_, err = checkPrimaryFieldData(case2.schema.Fields, case2.schema, case2.insertMsg)
assert.NotEqual(t, nil, err)
// autoID == false, no primary field schema
@@ -1767,7 +1615,7 @@ func Test_InsertTaskCheckPrimaryFieldData(t *testing.T) {
Status: merr.Success(),
},
}
_, err = checkPrimaryFieldData(case3.schema, case3.insertMsg)
_, err = checkPrimaryFieldData(case3.schema.Fields, case3.schema, case3.insertMsg)
assert.NotEqual(t, nil, err)
// autoID == true, has primary field schema, but primary field data exist
@@ -1814,7 +1662,7 @@ func Test_InsertTaskCheckPrimaryFieldData(t *testing.T) {
case4.schema.Fields[0].IsPrimaryKey = true
case4.schema.Fields[0].AutoID = true
case4.insertMsg.FieldsData[0] = newScalarFieldData(case4.schema.Fields[0], case4.schema.Fields[0].Name, 10)
_, err = checkPrimaryFieldData(case4.schema, case4.insertMsg)
_, err = checkPrimaryFieldData(case4.schema.Fields, case4.schema, case4.insertMsg)
assert.NotEqual(t, nil, err)
// autoID == true, has primary field schema, but DataType don't match
@@ -1822,7 +1670,7 @@ func Test_InsertTaskCheckPrimaryFieldData(t *testing.T) {
case4.schema.Fields[0].IsPrimaryKey = false
case4.schema.Fields[1].IsPrimaryKey = true
case4.schema.Fields[1].AutoID = true
_, err = checkPrimaryFieldData(case4.schema, case4.insertMsg)
_, err = checkPrimaryFieldData(case4.schema.Fields, case4.schema, case4.insertMsg)
assert.NotEqual(t, nil, err)
}
@@ -1850,7 +1698,7 @@ func Test_UpsertTaskCheckPrimaryFieldData(t *testing.T) {
Status: merr.Success(),
},
}
_, _, err := checkUpsertPrimaryFieldData(task.schema, task.insertMsg)
_, _, err := checkUpsertPrimaryFieldData(task.schema.Fields, task.schema, task.insertMsg)
assert.NotEqual(t, nil, err)
})
@@ -1894,7 +1742,7 @@ func Test_UpsertTaskCheckPrimaryFieldData(t *testing.T) {
Status: merr.Success(),
},
}
_, _, err := checkUpsertPrimaryFieldData(task.schema, task.insertMsg)
_, _, err := checkUpsertPrimaryFieldData(task.schema.Fields, task.schema, task.insertMsg)
assert.NotEqual(t, nil, err)
})
@@ -1935,7 +1783,7 @@ func Test_UpsertTaskCheckPrimaryFieldData(t *testing.T) {
Status: merr.Success(),
},
}
_, _, err := checkUpsertPrimaryFieldData(task.schema, task.insertMsg)
_, _, err := checkUpsertPrimaryFieldData(task.schema.Fields, task.schema, task.insertMsg)
assert.NotEqual(t, nil, err)
})
@@ -1980,7 +1828,7 @@ func Test_UpsertTaskCheckPrimaryFieldData(t *testing.T) {
Status: merr.Success(),
},
}
_, _, err := checkUpsertPrimaryFieldData(task.schema, task.insertMsg)
_, _, err := checkUpsertPrimaryFieldData(task.schema.Fields, task.schema, task.insertMsg)
assert.NotEqual(t, nil, err)
})
@@ -2031,7 +1879,7 @@ func Test_UpsertTaskCheckPrimaryFieldData(t *testing.T) {
Status: merr.Success(),
},
}
_, _, err := checkUpsertPrimaryFieldData(task.schema, task.insertMsg)
_, _, err := checkUpsertPrimaryFieldData(task.schema.Fields, task.schema, task.insertMsg)
assert.NotEqual(t, nil, err)
})
@@ -2072,7 +1920,7 @@ func Test_UpsertTaskCheckPrimaryFieldData(t *testing.T) {
Status: merr.Success(),
},
}
_, _, err := checkUpsertPrimaryFieldData(task.schema, task.insertMsg)
_, _, err := checkUpsertPrimaryFieldData(task.schema.Fields, task.schema, task.insertMsg)
assert.NoError(t, nil, err)
// autoid==false
@@ -2111,7 +1959,7 @@ func Test_UpsertTaskCheckPrimaryFieldData(t *testing.T) {
Status: merr.Success(),
},
}
_, _, err = checkUpsertPrimaryFieldData(task.schema, task.insertMsg)
_, _, err = checkUpsertPrimaryFieldData(task.schema.Fields, task.schema, task.insertMsg)
assert.NoError(t, nil, err)
})
@@ -2162,7 +2010,7 @@ func Test_UpsertTaskCheckPrimaryFieldData(t *testing.T) {
Status: merr.Success(),
},
}
_, _, err := checkUpsertPrimaryFieldData(task.schema, task.insertMsg)
_, _, err := checkUpsertPrimaryFieldData(task.schema.Fields, task.schema, task.insertMsg)
newPK := task.insertMsg.FieldsData[0].GetScalars().GetLongData().GetData()
assert.Equal(t, newPK, task.insertMsg.RowIDs)
assert.NoError(t, nil, err)
@@ -3482,7 +3330,7 @@ func TestCheckVarcharFormat(t *testing.T) {
},
}
err := checkInputUtf8Compatiable(schema, data)
err := checkInputUtf8Compatiable(schema.Fields, data)
assert.NoError(t, err)
// invalid data
@@ -3504,7 +3352,7 @@ func TestCheckVarcharFormat(t *testing.T) {
}},
},
}
err = checkInputUtf8Compatiable(schema, data)
err = checkInputUtf8Compatiable(schema.Fields, data)
assert.Error(t, err)
}
@@ -3547,6 +3395,484 @@ func BenchmarkCheckVarcharFormat(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
checkInputUtf8Compatiable(schema, data)
checkInputUtf8Compatiable(schema.Fields, data)
}
}
func TestCheckAndFlattenStructFieldData(t *testing.T) {
createTestSchema := func(name string, structFields []*schemapb.StructArrayFieldSchema, normalFields []*schemapb.FieldSchema) *schemapb.CollectionSchema {
return &schemapb.CollectionSchema{
Name: name,
Description: "test collection with struct array fields",
StructArrayFields: structFields,
Fields: normalFields,
}
}
createTestInsertMsg := func(collectionName string, fieldsData []*schemapb.FieldData) *msgstream.InsertMsg {
return &msgstream.InsertMsg{
BaseMsg: msgstream.BaseMsg{},
InsertRequest: &msgpb.InsertRequest{
Base: &commonpb.MsgBase{
MsgType: commonpb.MsgType_Insert,
},
CollectionName: collectionName,
FieldsData: fieldsData,
},
}
}
createScalarArrayFieldData := func(fieldName string, data []*schemapb.ScalarField) *schemapb.FieldData {
return &schemapb.FieldData{
FieldName: fieldName,
Type: schemapb.DataType_Array,
Field: &schemapb.FieldData_Scalars{
Scalars: &schemapb.ScalarField{
Data: &schemapb.ScalarField_ArrayData{
ArrayData: &schemapb.ArrayArray{
Data: data,
},
},
},
},
}
}
createVectorArrayFieldData := func(fieldName string, data []*schemapb.VectorField) *schemapb.FieldData {
return &schemapb.FieldData{
FieldName: fieldName,
Type: schemapb.DataType_ArrayOfVector,
Field: &schemapb.FieldData_Vectors{
Vectors: &schemapb.VectorField{
Data: &schemapb.VectorField_VectorArray{
VectorArray: &schemapb.VectorArray{
Data: data,
},
},
},
},
}
}
createStructArrayFieldData := func(fieldName string, subFields []*schemapb.FieldData) *schemapb.FieldData {
return &schemapb.FieldData{
FieldName: fieldName,
Type: schemapb.DataType_ArrayOfStruct,
Field: &schemapb.FieldData_StructArrays{
StructArrays: &schemapb.StructArrayField{
Fields: subFields,
},
},
}
}
createNormalFieldData := func(fieldName string, dataType schemapb.DataType) *schemapb.FieldData {
return &schemapb.FieldData{
FieldName: fieldName,
Type: dataType,
Field: &schemapb.FieldData_Scalars{
Scalars: &schemapb.ScalarField{
Data: &schemapb.ScalarField_LongData{
LongData: &schemapb.LongArray{Data: []int64{1, 2, 3}},
},
},
},
}
}
t.Run("success - valid single struct array field", func(t *testing.T) {
structField := &schemapb.StructArrayFieldSchema{
Name: "user_info",
Fields: []*schemapb.FieldSchema{
{
Name: "age_array",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_Int32,
},
{
Name: "score_array",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_Float,
},
},
}
schema := createTestSchema("test_collection", []*schemapb.StructArrayFieldSchema{structField}, nil)
ageArrayData := createScalarArrayFieldData("age_array", []*schemapb.ScalarField{
{Data: &schemapb.ScalarField_IntData{IntData: &schemapb.IntArray{Data: []int32{20, 25}}}},
{Data: &schemapb.ScalarField_IntData{IntData: &schemapb.IntArray{Data: []int32{30, 35}}}},
})
scoreArrayData := createScalarArrayFieldData("score_array", []*schemapb.ScalarField{
{Data: &schemapb.ScalarField_FloatData{FloatData: &schemapb.FloatArray{Data: []float32{85.5, 90.0}}}},
{Data: &schemapb.ScalarField_FloatData{FloatData: &schemapb.FloatArray{Data: []float32{88.5, 92.0}}}},
})
structFieldData := createStructArrayFieldData("user_info", []*schemapb.FieldData{
ageArrayData, scoreArrayData,
})
insertMsg := createTestInsertMsg("test_collection", []*schemapb.FieldData{structFieldData})
err := checkAndFlattenStructFieldData(schema, insertMsg)
assert.NoError(t, err)
assert.Len(t, insertMsg.FieldsData, 2)
assert.Equal(t, "age_array", insertMsg.FieldsData[0].FieldName)
assert.Equal(t, "score_array", insertMsg.FieldsData[1].FieldName)
})
t.Run("success - valid struct array field with vector arrays", func(t *testing.T) {
structField := &schemapb.StructArrayFieldSchema{
Name: "embedding_info",
Fields: []*schemapb.FieldSchema{
{
Name: "embeddings",
DataType: schemapb.DataType_ArrayOfVector,
ElementType: schemapb.DataType_FloatVector,
},
},
}
schema := createTestSchema("test_collection", []*schemapb.StructArrayFieldSchema{structField}, nil)
vectorArrayData := createVectorArrayFieldData("embeddings", []*schemapb.VectorField{
{Data: &schemapb.VectorField_FloatVector{FloatVector: &schemapb.FloatArray{Data: []float32{1.0, 2.0}}}},
{Data: &schemapb.VectorField_FloatVector{FloatVector: &schemapb.FloatArray{Data: []float32{3.0, 4.0}}}},
})
structFieldData := createStructArrayFieldData("embedding_info", []*schemapb.FieldData{vectorArrayData})
insertMsg := createTestInsertMsg("test_collection", []*schemapb.FieldData{structFieldData})
err := checkAndFlattenStructFieldData(schema, insertMsg)
assert.NoError(t, err)
assert.Len(t, insertMsg.FieldsData, 1)
assert.Equal(t, "embeddings", insertMsg.FieldsData[0].FieldName)
assert.Equal(t, schemapb.DataType_ArrayOfVector, insertMsg.FieldsData[0].Type)
})
t.Run("success - multiple struct array fields", func(t *testing.T) {
structField1 := &schemapb.StructArrayFieldSchema{
Name: "struct1",
Fields: []*schemapb.FieldSchema{
{Name: "field1", DataType: schemapb.DataType_Array},
},
}
structField2 := &schemapb.StructArrayFieldSchema{
Name: "struct2",
Fields: []*schemapb.FieldSchema{
{Name: "field2", DataType: schemapb.DataType_Array},
{Name: "field3", DataType: schemapb.DataType_ArrayOfVector},
},
}
schema := createTestSchema("test_collection", []*schemapb.StructArrayFieldSchema{structField1, structField2}, nil)
field1Data := createScalarArrayFieldData("field1", []*schemapb.ScalarField{
{Data: &schemapb.ScalarField_IntData{IntData: &schemapb.IntArray{Data: []int32{1}}}},
})
field2Data := createScalarArrayFieldData("field2", []*schemapb.ScalarField{
{Data: &schemapb.ScalarField_IntData{IntData: &schemapb.IntArray{Data: []int32{2}}}},
})
field3Data := createVectorArrayFieldData("field3", []*schemapb.VectorField{
{Data: &schemapb.VectorField_FloatVector{FloatVector: &schemapb.FloatArray{Data: []float32{1.0}}}},
})
struct1Data := createStructArrayFieldData("struct1", []*schemapb.FieldData{field1Data})
struct2Data := createStructArrayFieldData("struct2", []*schemapb.FieldData{field2Data, field3Data})
insertMsg := createTestInsertMsg("test_collection", []*schemapb.FieldData{struct1Data, struct2Data})
err := checkAndFlattenStructFieldData(schema, insertMsg)
assert.NoError(t, err)
assert.Len(t, insertMsg.FieldsData, 3)
fieldNames := make([]string, len(insertMsg.FieldsData))
for i, field := range insertMsg.FieldsData {
fieldNames[i] = field.FieldName
}
assert.Contains(t, fieldNames, "field1")
assert.Contains(t, fieldNames, "field2")
assert.Contains(t, fieldNames, "field3")
})
t.Run("success - mixed normal and struct fields", func(t *testing.T) {
normalField := &schemapb.FieldSchema{
Name: "id",
DataType: schemapb.DataType_Int64,
}
structField := &schemapb.StructArrayFieldSchema{
Name: "metadata",
Fields: []*schemapb.FieldSchema{
{Name: "tags", DataType: schemapb.DataType_Array},
},
}
schema := createTestSchema("test_collection", []*schemapb.StructArrayFieldSchema{structField}, []*schemapb.FieldSchema{normalField})
normalFieldData := createNormalFieldData("id", schemapb.DataType_Int64)
tagsData := createScalarArrayFieldData("tags", []*schemapb.ScalarField{
{Data: &schemapb.ScalarField_StringData{StringData: &schemapb.StringArray{Data: []string{"tag1", "tag2"}}}},
})
structFieldData := createStructArrayFieldData("metadata", []*schemapb.FieldData{tagsData})
insertMsg := createTestInsertMsg("test_collection", []*schemapb.FieldData{normalFieldData, structFieldData})
err := checkAndFlattenStructFieldData(schema, insertMsg)
assert.NoError(t, err)
assert.Len(t, insertMsg.FieldsData, 2)
fieldNames := make([]string, len(insertMsg.FieldsData))
for i, field := range insertMsg.FieldsData {
fieldNames[i] = field.FieldName
}
assert.Contains(t, fieldNames, "id")
assert.Contains(t, fieldNames, "tags")
})
t.Run("success - empty struct array fields", func(t *testing.T) {
normalField := &schemapb.FieldSchema{
Name: "normal_field",
DataType: schemapb.DataType_Int64,
}
schema := createTestSchema("test_collection", []*schemapb.StructArrayFieldSchema{}, []*schemapb.FieldSchema{normalField})
normalFieldData := createNormalFieldData("normal_field", schemapb.DataType_Int64)
insertMsg := createTestInsertMsg("test_collection", []*schemapb.FieldData{normalFieldData})
err := checkAndFlattenStructFieldData(schema, insertMsg)
assert.NoError(t, err)
assert.Len(t, insertMsg.FieldsData, 1)
assert.Equal(t, "normal_field", insertMsg.FieldsData[0].FieldName)
})
t.Run("error - struct field not found in schema", func(t *testing.T) {
schema := createTestSchema("test_collection", []*schemapb.StructArrayFieldSchema{}, nil)
structFieldData := createStructArrayFieldData("non_existent_struct", []*schemapb.FieldData{})
insertMsg := createTestInsertMsg("test_collection", []*schemapb.FieldData{structFieldData})
err := checkAndFlattenStructFieldData(schema, insertMsg)
assert.Error(t, err)
assert.Contains(t, err.Error(), "fieldName non_existent_struct not exist in collection schema")
})
t.Run("error - invalid field type conversion", func(t *testing.T) {
structField := &schemapb.StructArrayFieldSchema{
Name: "valid_struct",
Fields: []*schemapb.FieldSchema{},
}
schema := createTestSchema("test_collection", []*schemapb.StructArrayFieldSchema{structField}, nil)
invalidFieldData := &schemapb.FieldData{
FieldName: "valid_struct",
Type: schemapb.DataType_ArrayOfStruct,
Field: &schemapb.FieldData_Scalars{
Scalars: &schemapb.ScalarField{},
},
}
insertMsg := createTestInsertMsg("test_collection", []*schemapb.FieldData{invalidFieldData})
err := checkAndFlattenStructFieldData(schema, insertMsg)
assert.Error(t, err)
assert.Contains(t, err.Error(), "field convert FieldData_StructArrays fail")
assert.Contains(t, err.Error(), "valid_struct")
})
t.Run("error - field count mismatch", func(t *testing.T) {
structField := &schemapb.StructArrayFieldSchema{
Name: "test_struct",
Fields: []*schemapb.FieldSchema{
{Name: "field1", DataType: schemapb.DataType_Array},
{Name: "field2", DataType: schemapb.DataType_Array},
},
}
schema := createTestSchema("test_collection", []*schemapb.StructArrayFieldSchema{structField}, nil)
field1Data := createScalarArrayFieldData("field1", []*schemapb.ScalarField{})
structFieldData := createStructArrayFieldData("test_struct", []*schemapb.FieldData{field1Data})
insertMsg := createTestInsertMsg("test_collection", []*schemapb.FieldData{structFieldData})
err := checkAndFlattenStructFieldData(schema, insertMsg)
assert.Error(t, err)
assert.Contains(t, err.Error(), "length of fields of struct field mismatch")
assert.Contains(t, err.Error(), "fieldData fields length:1, schema fields length:2")
})
t.Run("error - scalar array data is nil", func(t *testing.T) {
structField := &schemapb.StructArrayFieldSchema{
Name: "test_struct",
Fields: []*schemapb.FieldSchema{
{Name: "field1", DataType: schemapb.DataType_Array},
},
}
schema := createTestSchema("test_collection", []*schemapb.StructArrayFieldSchema{structField}, nil)
nilScalarFieldData := &schemapb.FieldData{
FieldName: "field1",
Type: schemapb.DataType_Array,
Field: &schemapb.FieldData_Scalars{
Scalars: &schemapb.ScalarField{
Data: &schemapb.ScalarField_ArrayData{
ArrayData: nil,
},
},
},
}
structFieldData := createStructArrayFieldData("test_struct", []*schemapb.FieldData{nilScalarFieldData})
insertMsg := createTestInsertMsg("test_collection", []*schemapb.FieldData{structFieldData})
err := checkAndFlattenStructFieldData(schema, insertMsg)
assert.Error(t, err)
assert.Contains(t, err.Error(), "scalar array data is nil in struct field")
assert.Contains(t, err.Error(), "test_struct")
assert.Contains(t, err.Error(), "field1")
})
t.Run("error - vector array data is nil", func(t *testing.T) {
structField := &schemapb.StructArrayFieldSchema{
Name: "test_struct",
Fields: []*schemapb.FieldSchema{
{Name: "vectors", DataType: schemapb.DataType_ArrayOfVector},
},
}
schema := createTestSchema("test_collection", []*schemapb.StructArrayFieldSchema{structField}, nil)
nilVectorFieldData := &schemapb.FieldData{
FieldName: "vectors",
Type: schemapb.DataType_ArrayOfVector,
Field: &schemapb.FieldData_Vectors{
Vectors: &schemapb.VectorField{
Data: &schemapb.VectorField_VectorArray{
VectorArray: nil,
},
},
},
}
structFieldData := createStructArrayFieldData("test_struct", []*schemapb.FieldData{nilVectorFieldData})
insertMsg := createTestInsertMsg("test_collection", []*schemapb.FieldData{structFieldData})
err := checkAndFlattenStructFieldData(schema, insertMsg)
assert.Error(t, err)
assert.Contains(t, err.Error(), "vector array data is nil in struct field")
assert.Contains(t, err.Error(), "test_struct")
assert.Contains(t, err.Error(), "vectors")
})
t.Run("error - unsupported field data type", func(t *testing.T) {
structField := &schemapb.StructArrayFieldSchema{
Name: "test_struct",
Fields: []*schemapb.FieldSchema{
{Name: "field1", DataType: schemapb.DataType_Array},
},
}
schema := createTestSchema("test_collection", []*schemapb.StructArrayFieldSchema{structField}, nil)
unsupportedFieldData := &schemapb.FieldData{
FieldName: "field1",
Type: schemapb.DataType_Array,
Field: &schemapb.FieldData_StructArrays{
StructArrays: &schemapb.StructArrayField{},
},
}
structFieldData := createStructArrayFieldData("test_struct", []*schemapb.FieldData{unsupportedFieldData})
insertMsg := createTestInsertMsg("test_collection", []*schemapb.FieldData{structFieldData})
err := checkAndFlattenStructFieldData(schema, insertMsg)
assert.Error(t, err)
assert.Contains(t, err.Error(), "unexpected field data type in struct array field")
assert.Contains(t, err.Error(), "test_struct")
})
t.Run("error - inconsistent array length", func(t *testing.T) {
structField := &schemapb.StructArrayFieldSchema{
Name: "test_struct",
Fields: []*schemapb.FieldSchema{
{Name: "field1", DataType: schemapb.DataType_Array},
{Name: "field2", DataType: schemapb.DataType_Array},
},
}
schema := createTestSchema("test_collection", []*schemapb.StructArrayFieldSchema{structField}, nil)
field1Data := createScalarArrayFieldData("field1", []*schemapb.ScalarField{
{Data: &schemapb.ScalarField_IntData{IntData: &schemapb.IntArray{Data: []int32{1, 2}}}},
{Data: &schemapb.ScalarField_IntData{IntData: &schemapb.IntArray{Data: []int32{3, 4}}}},
})
field2Data := createScalarArrayFieldData("field2", []*schemapb.ScalarField{
{Data: &schemapb.ScalarField_IntData{IntData: &schemapb.IntArray{Data: []int32{5, 6}}}},
{Data: &schemapb.ScalarField_IntData{IntData: &schemapb.IntArray{Data: []int32{7, 8}}}},
{Data: &schemapb.ScalarField_IntData{IntData: &schemapb.IntArray{Data: []int32{9, 10}}}},
})
structFieldData := createStructArrayFieldData("test_struct", []*schemapb.FieldData{field1Data, field2Data})
insertMsg := createTestInsertMsg("test_collection", []*schemapb.FieldData{structFieldData})
err := checkAndFlattenStructFieldData(schema, insertMsg)
assert.Error(t, err)
assert.Contains(t, err.Error(), "inconsistent array length in struct field")
assert.Contains(t, err.Error(), "expected 2, got 3")
assert.Contains(t, err.Error(), "field2")
})
t.Run("error - struct field count mismatch", func(t *testing.T) {
structField1 := &schemapb.StructArrayFieldSchema{Name: "struct1", Fields: []*schemapb.FieldSchema{}}
structField2 := &schemapb.StructArrayFieldSchema{Name: "struct2", Fields: []*schemapb.FieldSchema{}}
schema := createTestSchema("test_collection", []*schemapb.StructArrayFieldSchema{structField1, structField2}, nil)
structFieldData := createStructArrayFieldData("struct1", []*schemapb.FieldData{})
insertMsg := createTestInsertMsg("test_collection", []*schemapb.FieldData{structFieldData})
err := checkAndFlattenStructFieldData(schema, insertMsg)
assert.Error(t, err)
assert.Contains(t, err.Error(), "the number of struct array fields is not the same as needed")
assert.Contains(t, err.Error(), "expected: 2, actual: 1")
})
t.Run("edge case - empty struct fields", func(t *testing.T) {
structField := &schemapb.StructArrayFieldSchema{
Name: "empty_struct",
Fields: []*schemapb.FieldSchema{},
}
schema := createTestSchema("test_collection", []*schemapb.StructArrayFieldSchema{structField}, nil)
structFieldData := createStructArrayFieldData("empty_struct", []*schemapb.FieldData{})
insertMsg := createTestInsertMsg("test_collection", []*schemapb.FieldData{structFieldData})
err := checkAndFlattenStructFieldData(schema, insertMsg)
assert.NoError(t, err)
assert.Len(t, insertMsg.FieldsData, 0)
})
t.Run("edge case - single element arrays", func(t *testing.T) {
structField := &schemapb.StructArrayFieldSchema{
Name: "single_element_struct",
Fields: []*schemapb.FieldSchema{
{Name: "single_field", DataType: schemapb.DataType_Array},
},
}
schema := createTestSchema("test_collection", []*schemapb.StructArrayFieldSchema{structField}, nil)
singleFieldData := createScalarArrayFieldData("single_field", []*schemapb.ScalarField{
{Data: &schemapb.ScalarField_IntData{IntData: &schemapb.IntArray{Data: []int32{42}}}},
})
structFieldData := createStructArrayFieldData("single_element_struct", []*schemapb.FieldData{singleFieldData})
insertMsg := createTestInsertMsg("test_collection", []*schemapb.FieldData{structFieldData})
err := checkAndFlattenStructFieldData(schema, insertMsg)
assert.NoError(t, err)
assert.Len(t, insertMsg.FieldsData, 1)
assert.Equal(t, "single_field", insertMsg.FieldsData[0].FieldName)
})
}
func TestValidateFieldsInStruct(t *testing.T) {
// todo(SpadeA): add test cases
}
+62 -40
View File
@@ -124,6 +124,13 @@ func (v *validateUtil) Validate(data []*schemapb.FieldData, helper *typeutil.Sch
if err := v.checkArrayFieldData(field, fieldSchema); err != nil {
return err
}
case schemapb.DataType_ArrayOfVector:
if err := v.checkArrayOfVectorFieldData(field, fieldSchema); err != nil {
return err
}
case schemapb.DataType_ArrayOfStruct:
panic("unreachable, array of struct should have been flattened")
default:
}
@@ -332,71 +339,59 @@ func (v *validateUtil) fillWithNullValue(field *schemapb.FieldData, fieldSchema
return err
}
if !fieldSchema.GetNullable() {
return nil
}
switch field.Field.(type) {
case *schemapb.FieldData_Scalars:
switch sd := field.GetScalars().GetData().(type) {
case *schemapb.ScalarField_BoolData:
if fieldSchema.GetNullable() {
sd.BoolData.Data, err = fillWithNullValueImpl(sd.BoolData.Data, field.GetValidData())
if err != nil {
return err
}
sd.BoolData.Data, err = fillWithNullValueImpl(sd.BoolData.Data, field.GetValidData())
if err != nil {
return err
}
case *schemapb.ScalarField_IntData:
if fieldSchema.GetNullable() {
sd.IntData.Data, err = fillWithNullValueImpl(sd.IntData.Data, field.GetValidData())
if err != nil {
return err
}
sd.IntData.Data, err = fillWithNullValueImpl(sd.IntData.Data, field.GetValidData())
if err != nil {
return err
}
case *schemapb.ScalarField_LongData:
if fieldSchema.GetNullable() {
sd.LongData.Data, err = fillWithNullValueImpl(sd.LongData.Data, field.GetValidData())
if err != nil {
return err
}
sd.LongData.Data, err = fillWithNullValueImpl(sd.LongData.Data, field.GetValidData())
if err != nil {
return err
}
case *schemapb.ScalarField_FloatData:
if fieldSchema.GetNullable() {
sd.FloatData.Data, err = fillWithNullValueImpl(sd.FloatData.Data, field.GetValidData())
if err != nil {
return err
}
sd.FloatData.Data, err = fillWithNullValueImpl(sd.FloatData.Data, field.GetValidData())
if err != nil {
return err
}
case *schemapb.ScalarField_DoubleData:
if fieldSchema.GetNullable() {
sd.DoubleData.Data, err = fillWithNullValueImpl(sd.DoubleData.Data, field.GetValidData())
if err != nil {
return err
}
sd.DoubleData.Data, err = fillWithNullValueImpl(sd.DoubleData.Data, field.GetValidData())
if err != nil {
return err
}
case *schemapb.ScalarField_StringData:
if fieldSchema.GetNullable() {
sd.StringData.Data, err = fillWithNullValueImpl(sd.StringData.Data, field.GetValidData())
if err != nil {
return err
}
sd.StringData.Data, err = fillWithNullValueImpl(sd.StringData.Data, field.GetValidData())
if err != nil {
return err
}
case *schemapb.ScalarField_ArrayData:
if fieldSchema.GetNullable() {
sd.ArrayData.Data, err = fillWithNullValueImpl(sd.ArrayData.Data, field.GetValidData())
if err != nil {
return err
}
sd.ArrayData.Data, err = fillWithNullValueImpl(sd.ArrayData.Data, field.GetValidData())
if err != nil {
return err
}
case *schemapb.ScalarField_JsonData:
if fieldSchema.GetNullable() {
sd.JsonData.Data, err = fillWithNullValueImpl(sd.JsonData.Data, field.GetValidData())
if err != nil {
return err
}
sd.JsonData.Data, err = fillWithNullValueImpl(sd.JsonData.Data, field.GetValidData())
if err != nil {
return err
}
default:
@@ -895,6 +890,33 @@ func (v *validateUtil) checkArrayFieldData(field *schemapb.FieldData, fieldSchem
return v.checkArrayElement(data, fieldSchema)
}
func (v *validateUtil) checkArrayOfVectorFieldData(field *schemapb.FieldData, fieldSchema *schemapb.FieldSchema) error {
data := field.GetVectors().GetVectorArray()
if data == nil {
elementTypeStr := fieldSchema.GetElementType().String()
msg := fmt.Sprintf("array of vector field '%v' is illegal, array type mismatch", field.GetFieldName())
expectStr := fmt.Sprintf("need %s array", elementTypeStr)
return merr.WrapErrParameterInvalid(expectStr, "got nil", msg)
}
switch fieldSchema.GetElementType() {
case schemapb.DataType_FloatVector:
for _, vector := range data.GetData() {
floatVector := vector.GetFloatVector()
if floatVector == nil {
msg := fmt.Sprintf("array of vector field '%v' is illegal, array type mismatch", field.GetFieldName())
return merr.WrapErrParameterInvalid("need float vector array", "got nil", msg)
}
if v.checkNAN {
return typeutil.VerifyFloats32(floatVector.GetData())
}
}
return nil
default:
panic("not implemented")
}
}
func verifyLengthPerRow[E interface{ ~string | ~[]byte }](strArr []E, maxLength int64) (int, bool) {
for i, s := range strArr {
if int64(len(s)) > maxLength {
@@ -236,6 +236,12 @@ func (m *CollectionManager) upgradeLoadFields(ctx context.Context, collection *q
return fieldSchema.GetFieldID(), !common.IsSystemField(fieldSchema.GetFieldID())
})
for _, structArrayField := range resp.GetSchema().GetStructArrayFields() {
for _, field := range structArrayField.GetFields() {
collection.LoadFields = append(collection.LoadFields, field.GetFieldID())
}
}
// put updated meta back to store
err = m.putCollection(ctx, true, &Collection{
CollectionLoadInfo: collection,
+12
View File
@@ -143,6 +143,18 @@ func packLoadSegmentRequest(
loadScope = querypb.LoadScope_Delta
}
// todo(SpadeA): consider struct fields
// field mmap enabled if collection-level mmap enabled or the field mmap enabled
collectionMmapEnabled, exist := common.IsMmapDataEnabled(collectionProperties...)
for _, field := range schema.GetFields() {
if exist {
field.TypeParams = append(field.TypeParams, &commonpb.KeyValuePair{
Key: common.MmapEnabledKey,
Value: strconv.FormatBool(collectionMmapEnabled),
})
}
}
schema = applyCollectionMmapSetting(schema, collectionProperties)
return &querypb.LoadSegmentsRequest{
@@ -292,6 +292,11 @@ func NewCollection(collectionID int64, schema *schemapb.CollectionSchema, indexM
loadFieldIDs = typeutil.NewSet(loadMetaInfo.GetLoadFields()...)
} else {
loadFieldIDs = typeutil.NewSet(lo.Map(loadSchema.GetFields(), func(field *schemapb.FieldSchema, _ int) int64 { return field.GetFieldID() })...)
for _, structArrayField := range loadSchema.GetStructArrayFields() {
for _, subField := range structArrayField.GetFields() {
loadFieldIDs.Insert(subField.GetFieldID())
}
}
}
isGpuIndex := false
@@ -756,6 +756,7 @@ func separateLoadInfoV2(loadInfo *querypb.SegmentLoadInfo, schema *schemapb.Coll
}
unindexedTextFields := make(map[int64]struct{})
// todo(SpadeA): consider struct fields when index is ready
for _, field := range schema.GetFields() {
h := typeutil.CreateFieldSchemaHelper(field)
_, textIndexExist := textIndexedInfo[field.GetFieldID()]
@@ -1667,6 +1668,18 @@ func (loader *segmentLoader) getFieldType(collectionID, fieldID int64) (schemapb
return field.GetDataType(), nil
}
}
for _, structField := range collection.Schema().GetStructArrayFields() {
if structField.GetFieldID() == fieldID {
return schemapb.DataType_ArrayOfStruct, nil
}
for _, subField := range structField.GetFields() {
if subField.GetFieldID() == fieldID {
return subField.GetDataType(), nil
}
}
}
return 0, merr.WrapErrFieldNotFound(fieldID)
}
+7
View File
@@ -259,6 +259,13 @@ func getFieldSchema(schema *schemapb.CollectionSchema, fieldID int64) (*schemapb
return field, nil
}
}
for _, structArrayField := range schema.StructArrayFields {
for _, subField := range structArrayField.Fields {
if subField.FieldID == fieldID {
return subField, nil
}
}
}
return nil, fmt.Errorf("field %d not found in schema", fieldID)
}
+6 -5
View File
@@ -250,11 +250,12 @@ func (b *ServerBroker) BroadcastAlteredCollection(ctx context.Context, req *milv
dcReq := &datapb.AlterCollectionRequest{
CollectionID: req.GetCollectionID(),
Schema: &schemapb.CollectionSchema{
Name: colMeta.Name,
Description: colMeta.Description,
AutoID: colMeta.AutoID,
Fields: model.MarshalFieldModels(colMeta.Fields),
Functions: model.MarshalFunctionModels(colMeta.Functions),
Name: colMeta.Name,
Description: colMeta.Description,
AutoID: colMeta.AutoID,
Fields: model.MarshalFieldModels(colMeta.Fields),
StructArrayFields: model.MarshalStructArrayFieldModels(colMeta.StructArrayFields),
Functions: model.MarshalFunctionModels(colMeta.Functions),
},
PartitionIDs: partitionIDs,
StartPositions: colMeta.StartPositions,
+33 -8
View File
@@ -173,6 +173,10 @@ func (t *createCollectionTask) validateSchema(ctx context.Context, schema *schem
return err
}
if err := checkStructArrayFieldSchema(schema.GetStructArrayFields()); err != nil {
return err
}
if hasSystemFields(schema, []string{RowIDFieldName, TimeStampFieldName, MetaFieldName}) {
log.Ctx(ctx).Error("schema contains system field",
zap.String("RowIDFieldName", RowIDFieldName),
@@ -181,16 +185,34 @@ func (t *createCollectionTask) validateSchema(ctx context.Context, schema *schem
msg := fmt.Sprintf("schema contains system field: %s, %s, %s", RowIDFieldName, TimeStampFieldName, MetaFieldName)
return merr.WrapErrParameterInvalid("schema don't contains system field", "contains", msg)
}
if err := validateStructArrayFieldDataType(schema.GetStructArrayFields()); err != nil {
return err
}
return validateFieldDataType(schema.GetFields())
}
func (t *createCollectionTask) assignFieldAndFunctionID(schema *schemapb.CollectionSchema) error {
name2id := map[string]int64{}
for idx, field := range schema.GetFields() {
idx := 0
for _, field := range schema.GetFields() {
field.FieldID = int64(idx + StartOfUserFieldID)
idx++
name2id[field.GetName()] = field.GetFieldID()
}
for _, structArrayField := range schema.GetStructArrayFields() {
structArrayField.FieldID = int64(idx + StartOfUserFieldID)
idx++
for _, field := range structArrayField.GetFields() {
field.FieldID = int64(idx + StartOfUserFieldID)
idx++
}
}
for fidx, function := range schema.GetFunctions() {
function.InputFieldIds = make([]int64, len(function.InputFieldNames))
function.Id = int64(fidx) + StartOfUserFunctionID
@@ -211,6 +233,7 @@ func (t *createCollectionTask) assignFieldAndFunctionID(schema *schemapb.Collect
function.OutputFieldIds[idx] = fieldId
}
}
return nil
}
@@ -515,6 +538,7 @@ func (t *createCollectionTask) Execute(ctx context.Context) error {
Description: t.schema.Description,
AutoID: t.schema.AutoID,
Fields: model.UnmarshalFieldModels(t.schema.Fields),
StructArrayFields: model.UnmarshalStructArrayFieldModels(t.schema.StructArrayFields),
Functions: model.UnmarshalFunctionModels(t.schema.Functions),
VirtualChannelNames: vchanNames,
PhysicalChannelNames: chanNames,
@@ -622,13 +646,14 @@ func executeCreateCollectionTaskSteps(ctx context.Context,
vChannels: col.VirtualChannelNames,
startPositions: col.StartPositions,
schema: &schemapb.CollectionSchema{
Name: col.Name,
DbName: col.DBName,
Description: col.Description,
AutoID: col.AutoID,
Fields: model.MarshalFieldModels(col.Fields),
Properties: col.Properties,
Functions: model.MarshalFunctionModels(col.Functions),
Name: col.Name,
DbName: col.DBName,
Description: col.Description,
AutoID: col.AutoID,
Fields: model.MarshalFieldModels(col.Fields),
StructArrayFields: model.MarshalStructArrayFieldModels(col.StructArrayFields),
Properties: col.Properties,
Functions: model.MarshalFunctionModels(col.Functions),
},
dbProperties: dbProperties,
},
@@ -653,6 +653,281 @@ func Test_createCollectionTask_validateSchema(t *testing.T) {
err := task.validateSchema(context.TODO(), schema)
assert.NoError(t, err)
})
t.Run("struct array field - empty fields", func(t *testing.T) {
collectionName := funcutil.GenRandomStr()
task := createCollectionTask{
Req: &milvuspb.CreateCollectionRequest{
Base: &commonpb.MsgBase{MsgType: commonpb.MsgType_CreateCollection},
CollectionName: collectionName,
},
}
schema := &schemapb.CollectionSchema{
Name: collectionName,
StructArrayFields: []*schemapb.StructArrayFieldSchema{
{
Name: "struct_field",
Fields: []*schemapb.FieldSchema{},
},
},
}
err := task.validateSchema(context.TODO(), schema)
assert.Error(t, err)
assert.Contains(t, err.Error(), "empty fields in StructArrayField")
})
t.Run("struct array field - vector type with nullable", func(t *testing.T) {
collectionName := funcutil.GenRandomStr()
task := createCollectionTask{
Req: &milvuspb.CreateCollectionRequest{
Base: &commonpb.MsgBase{MsgType: commonpb.MsgType_CreateCollection},
CollectionName: collectionName,
},
}
schema := &schemapb.CollectionSchema{
Name: collectionName,
StructArrayFields: []*schemapb.StructArrayFieldSchema{
{
Name: "struct_field",
Fields: []*schemapb.FieldSchema{
{
Name: "vector_array_field",
DataType: schemapb.DataType_ArrayOfVector,
ElementType: schemapb.DataType_FloatVector,
Nullable: true,
},
},
},
},
}
err := task.validateSchema(context.TODO(), schema)
assert.Error(t, err)
assert.Contains(t, err.Error(), "vector type not support null")
})
t.Run("struct array field - field with default value", func(t *testing.T) {
collectionName := funcutil.GenRandomStr()
task := createCollectionTask{
Req: &milvuspb.CreateCollectionRequest{
Base: &commonpb.MsgBase{MsgType: commonpb.MsgType_CreateCollection},
CollectionName: collectionName,
},
}
schema := &schemapb.CollectionSchema{
Name: collectionName,
StructArrayFields: []*schemapb.StructArrayFieldSchema{
{
Name: "struct_field",
Fields: []*schemapb.FieldSchema{
{
Name: "array_field",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_Int32,
DefaultValue: &schemapb.ValueField{
Data: &schemapb.ValueField_IntData{
IntData: 1,
},
},
},
},
},
},
}
err := task.validateSchema(context.TODO(), schema)
assert.Error(t, err)
assert.Contains(t, err.Error(), "fields in struct array field not support default_value")
})
t.Run("struct array field - duplicate type params", func(t *testing.T) {
collectionName := funcutil.GenRandomStr()
task := createCollectionTask{
Req: &milvuspb.CreateCollectionRequest{
Base: &commonpb.MsgBase{MsgType: commonpb.MsgType_CreateCollection},
CollectionName: collectionName,
},
}
schema := &schemapb.CollectionSchema{
Name: collectionName,
StructArrayFields: []*schemapb.StructArrayFieldSchema{
{
Name: "struct_field",
Fields: []*schemapb.FieldSchema{
{
Name: "array_field",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_VarChar,
TypeParams: []*commonpb.KeyValuePair{
{Key: common.MaxLengthKey, Value: "100"},
{Key: common.MaxLengthKey, Value: "200"},
},
},
},
},
},
}
err := task.validateSchema(context.TODO(), schema)
assert.Error(t, err)
assert.Contains(t, err.Error(), "duplicated type param key")
})
t.Run("struct array field - duplicate index params", func(t *testing.T) {
collectionName := funcutil.GenRandomStr()
task := createCollectionTask{
Req: &milvuspb.CreateCollectionRequest{
Base: &commonpb.MsgBase{MsgType: commonpb.MsgType_CreateCollection},
CollectionName: collectionName,
},
}
schema := &schemapb.CollectionSchema{
Name: collectionName,
StructArrayFields: []*schemapb.StructArrayFieldSchema{
{
Name: "struct_field",
Fields: []*schemapb.FieldSchema{
{
Name: "vector_array_field",
DataType: schemapb.DataType_ArrayOfVector,
ElementType: schemapb.DataType_FloatVector,
IndexParams: []*commonpb.KeyValuePair{
{Key: common.MetricTypeKey, Value: "L2"},
{Key: common.MetricTypeKey, Value: "IP"},
},
},
},
},
},
}
err := task.validateSchema(context.TODO(), schema)
assert.Error(t, err)
assert.Contains(t, err.Error(), "duplicated index param key")
})
t.Run("struct array field - invalid data type", func(t *testing.T) {
collectionName := funcutil.GenRandomStr()
task := createCollectionTask{
Req: &milvuspb.CreateCollectionRequest{
Base: &commonpb.MsgBase{MsgType: commonpb.MsgType_CreateCollection},
CollectionName: collectionName,
},
}
schema := &schemapb.CollectionSchema{
Name: collectionName,
StructArrayFields: []*schemapb.StructArrayFieldSchema{
{
Name: "struct_field",
Fields: []*schemapb.FieldSchema{
{
Name: "invalid_field",
DataType: schemapb.DataType_Int64,
ElementType: schemapb.DataType_Int64,
},
},
},
},
}
err := task.validateSchema(context.TODO(), schema)
assert.Error(t, err)
assert.Contains(t, err.Error(), "Fields in StructArrayField can only be array or array of vector")
})
t.Run("struct array field - nested array", func(t *testing.T) {
collectionName := funcutil.GenRandomStr()
task := createCollectionTask{
Req: &milvuspb.CreateCollectionRequest{
Base: &commonpb.MsgBase{MsgType: commonpb.MsgType_CreateCollection},
CollectionName: collectionName,
},
}
schema := &schemapb.CollectionSchema{
Name: collectionName,
StructArrayFields: []*schemapb.StructArrayFieldSchema{
{
Name: "struct_field",
Fields: []*schemapb.FieldSchema{
{
Name: "nested_array",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_Array,
},
},
},
},
}
err := task.validateSchema(context.TODO(), schema)
assert.Error(t, err)
assert.Contains(t, err.Error(), "Nested array is not supported")
})
t.Run("struct array field - invalid element type", func(t *testing.T) {
collectionName := funcutil.GenRandomStr()
task := createCollectionTask{
Req: &milvuspb.CreateCollectionRequest{
Base: &commonpb.MsgBase{MsgType: commonpb.MsgType_CreateCollection},
CollectionName: collectionName,
},
}
schema := &schemapb.CollectionSchema{
Name: collectionName,
StructArrayFields: []*schemapb.StructArrayFieldSchema{
{
Name: "struct_field",
Fields: []*schemapb.FieldSchema{
{
Name: "array_field",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_None,
},
},
},
},
}
err := task.validateSchema(context.TODO(), schema)
assert.Error(t, err)
assert.Contains(t, err.Error(), "field data type: None is not supported")
})
t.Run("struct array field - valid case", func(t *testing.T) {
collectionName := funcutil.GenRandomStr()
task := createCollectionTask{
Req: &milvuspb.CreateCollectionRequest{
Base: &commonpb.MsgBase{MsgType: commonpb.MsgType_CreateCollection},
CollectionName: collectionName,
},
}
schema := &schemapb.CollectionSchema{
Name: collectionName,
StructArrayFields: []*schemapb.StructArrayFieldSchema{
{
Name: "struct_field",
Fields: []*schemapb.FieldSchema{
{
Name: "text_array",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_VarChar,
TypeParams: []*commonpb.KeyValuePair{
{Key: common.MaxLengthKey, Value: "100"},
},
},
{
Name: "int_array",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_Int32,
},
{
Name: "vector_array",
DataType: schemapb.DataType_ArrayOfVector,
ElementType: schemapb.DataType_FloatVector,
TypeParams: []*commonpb.KeyValuePair{
{Key: common.DimKey, Value: "128"},
},
},
},
},
},
}
err := task.validateSchema(context.TODO(), schema)
assert.NoError(t, err)
})
}
func Test_createCollectionTask_prepareSchema(t *testing.T) {
@@ -1012,6 +1287,7 @@ func Test_createCollectionTask_Execute(t *testing.T) {
Description: schema.Description,
AutoID: schema.AutoID,
Fields: model.UnmarshalFieldModels(schema.GetFields()),
StructArrayFields: model.UnmarshalStructArrayFieldModels(schema.GetStructArrayFields()),
VirtualChannelNames: channels.virtualChannels,
PhysicalChannelNames: channels.physicalChannels,
}
+6 -5
View File
@@ -543,11 +543,12 @@ func (mt *MetaTable) RemoveCollection(ctx context.Context, collectionID UniqueID
ctx1 := contextutil.WithTenantID(ctx, Params.CommonCfg.ClusterName.GetValue())
aliases := mt.listAliasesByID(collectionID)
newColl := &model.Collection{
CollectionID: collectionID,
Partitions: model.ClonePartitions(coll.Partitions),
Fields: model.CloneFields(coll.Fields),
Aliases: aliases,
DBID: coll.DBID,
CollectionID: collectionID,
Partitions: model.ClonePartitions(coll.Partitions),
Fields: model.CloneFields(coll.Fields),
StructArrayFields: model.CloneStructArrayFields(coll.StructArrayFields),
Aliases: aliases,
DBID: coll.DBID,
}
if err := mt.catalog.DropCollection(ctx1, newColl, ts); err != nil {
return err
+1
View File
@@ -1175,6 +1175,7 @@ func convertModelToDesc(collInfo *model.Collection, aliases []string, dbName str
Description: collInfo.Description,
AutoID: collInfo.AutoID,
Fields: model.MarshalFieldModels(collInfo.Fields),
StructArrayFields: model.MarshalStructArrayFieldModels(collInfo.StructArrayFields),
Functions: model.MarshalFunctionModels(collInfo.Functions),
EnableDynamicField: collInfo.EnableDynamicField,
Properties: collInfo.Properties,
+1
View File
@@ -496,6 +496,7 @@ func (s *WriteSchemaChangeWALStep) Execute(ctx context.Context) ([]nestedStep, e
Description: s.collection.Description,
AutoID: s.collection.AutoID,
Fields: model.MarshalFieldModels(s.collection.Fields),
StructArrayFields: model.MarshalStructArrayFieldModels(s.collection.StructArrayFields),
Functions: model.MarshalFunctionModels(s.collection.Functions),
EnableDynamicField: s.collection.EnableDynamicField,
Properties: s.collection.Properties,
+61 -1
View File
@@ -384,6 +384,14 @@ func CheckTimeTickLagExceeded(ctx context.Context, mixcoord types.MixCoord, maxD
func checkFieldSchema(fieldSchemas []*schemapb.FieldSchema) error {
for _, fieldSchema := range fieldSchemas {
if fieldSchema.GetDataType() == schemapb.DataType_ArrayOfStruct {
msg := fmt.Sprintf("Invalid field type, type:%s, name:%s", fieldSchema.GetDataType().String(), fieldSchema.GetName())
return merr.WrapErrParameterInvalidMsg(msg)
}
if fieldSchema.GetDataType() == schemapb.DataType_ArrayOfVector {
msg := fmt.Sprintf("ArrayOfVector is only supported in struct array field, type:%s, name:%s", fieldSchema.GetDataType().String(), fieldSchema.GetName())
return merr.WrapErrParameterInvalidMsg(msg)
}
if fieldSchema.GetNullable() && typeutil.IsVectorType(fieldSchema.GetDataType()) {
msg := fmt.Sprintf("vector type not support null, type:%s, name:%s", fieldSchema.GetDataType().String(), fieldSchema.GetName())
return merr.WrapErrParameterInvalidMsg(msg)
@@ -466,6 +474,37 @@ func checkFieldSchema(fieldSchemas []*schemapb.FieldSchema) error {
return nil
}
func checkStructArrayFieldSchema(schemas []*schemapb.StructArrayFieldSchema) error {
for _, schema := range schemas {
// todo(SpadeA): check struct array field schema
for _, field := range schema.GetFields() {
if field.IsPartitionKey || field.IsPrimaryKey {
msg := fmt.Sprintf("partition key or primary key can not be in struct array field. data type:%s, element type:%s, name:%s",
field.DataType.String(), field.ElementType.String(), field.Name)
return merr.WrapErrParameterInvalidMsg(msg)
}
if field.GetNullable() && typeutil.IsVectorType(field.ElementType) {
msg := fmt.Sprintf("vector type not support null, data type:%s, element type:%s, name:%s",
field.DataType.String(), field.ElementType.String(), field.Name)
return merr.WrapErrParameterInvalidMsg(msg)
}
if field.GetDefaultValue() != nil {
msg := fmt.Sprintf("fields in struct array field not support default_value, data type:%s, element type:%s, name:%s",
field.DataType.String(), field.ElementType.String(), field.Name)
return merr.WrapErrParameterInvalidMsg(msg)
}
if err := checkDupKvPairs(field.GetTypeParams(), "type"); err != nil {
return err
}
if err := checkDupKvPairs(field.GetIndexParams(), "index"); err != nil {
return err
}
}
}
return nil
}
func checkDupKvPairs(params []*commonpb.KeyValuePair, paramType string) error {
set := typeutil.NewSet[string]()
for _, kv := range params {
@@ -480,7 +519,28 @@ func checkDupKvPairs(params []*commonpb.KeyValuePair, paramType string) error {
func validateFieldDataType(fieldSchemas []*schemapb.FieldSchema) error {
for _, field := range fieldSchemas {
if _, ok := schemapb.DataType_name[int32(field.GetDataType())]; !ok || field.GetDataType() == schemapb.DataType_None {
return merr.WrapErrParameterInvalid("valid field", fmt.Sprintf("field data type: %s is not supported", field.GetDataType()))
return merr.WrapErrParameterInvalid("Invalid field", fmt.Sprintf("field data type: %s is not supported", field.GetDataType()))
}
}
return nil
}
func validateStructArrayFieldDataType(fieldSchemas []*schemapb.StructArrayFieldSchema) error {
for _, field := range fieldSchemas {
if len(field.Fields) == 0 {
return merr.WrapErrParameterInvalid("Invalid field", "empty fields in StructArrayField")
}
for _, subField := range field.GetFields() {
if subField.GetDataType() != schemapb.DataType_Array && subField.GetDataType() != schemapb.DataType_ArrayOfVector {
return fmt.Errorf("Fields in StructArrayField can only be array or array of vector, but field %s is %s", subField.Name, subField.DataType.String())
}
if subField.GetElementType() == schemapb.DataType_ArrayOfStruct || subField.GetElementType() == schemapb.DataType_ArrayOfVector ||
subField.GetElementType() == schemapb.DataType_Array {
return fmt.Errorf("Nested array is not supported %s", subField.Name)
}
if _, ok := schemapb.DataType_name[int32(subField.GetElementType())]; !ok || subField.GetElementType() == schemapb.DataType_None {
return merr.WrapErrParameterInvalid("Invalid field", fmt.Sprintf("field data type: %s is not supported", subField.GetElementType()))
}
}
}
return nil
+11 -3
View File
@@ -359,13 +359,21 @@ func (b *RecordBuilder) Build() Record {
}
func NewRecordBuilder(schema *schemapb.CollectionSchema) *RecordBuilder {
builders := make([]array.Builder, len(schema.Fields))
for i, field := range schema.Fields {
// assumes 5 sub fields per StructArrayField
fields := make([]*schemapb.FieldSchema, 0, len(schema.Fields)+len(schema.StructArrayFields)*5)
fields = append(fields, schema.Fields...)
for _, sf := range schema.StructArrayFields {
fields = append(fields, sf.Fields...)
}
builders := make([]array.Builder, len(fields))
for i, field := range fields {
dim, _ := typeutil.GetDim(field)
builders[i] = array.NewBuilder(memory.DefaultAllocator, serdeMap[field.DataType].arrowType(int(dim)))
}
return &RecordBuilder{
fields: schema.Fields,
fields: fields,
builders: builders,
}
}
+71 -9
View File
@@ -255,7 +255,7 @@ func (insertCodec *InsertCodec) Serialize(partitionID UniqueID, segmentID Unique
}
}
for _, field := range insertCodec.Schema.Schema.Fields {
serializeField := func(field *schemapb.FieldSchema) error {
// check insert data contain this field
// must be all missing or all exists
allExists := true
@@ -269,14 +269,14 @@ func (insertCodec *InsertCodec) Serialize(partitionID UniqueID, segmentID Unique
// found missing block
if !allExists {
if !field.GetNullable() {
return nil, errors.Newf("field %d(%s) missing and field not nullable", field.GetFieldID(), field.GetName())
return errors.Newf("field %d(%s) missing and field not nullable", field.GetFieldID(), field.GetName())
}
// segment must be in same schema
if !allMissing {
return nil, errors.Newf("segment must not be heterogeneous, all blocks must contain all fields or none, abnormal field %d(%s)", field.GetFieldID(), field.GetName())
return errors.Newf("segment must not be heterogeneous, all blocks must contain all fields or none, abnormal field %d(%s)", field.GetFieldID(), field.GetName())
}
log.Info("Skip field nullable missing field, could be schema change", zap.Int64("fieldId", field.GetFieldID()), zap.String("fieldName", field.GetName()))
continue
return nil
}
// encode fields
@@ -288,14 +288,14 @@ func (insertCodec *InsertCodec) Serialize(partitionID UniqueID, segmentID Unique
if typeutil.IsVectorType(field.DataType) && !typeutil.IsSparseFloatVectorType(field.DataType) {
dim, err := typeutil.GetDim(field)
if err != nil {
return nil, err
return err
}
opts = append(opts, WithDim(int(dim)))
}
eventWriter, err := writer.NextInsertEventWriter(opts...)
if err != nil {
writer.Close()
return nil, err
return err
}
eventWriter.SetEventTimestamp(startTs, endTs)
eventWriter.Reserve(int(rowNum))
@@ -309,7 +309,7 @@ func (insertCodec *InsertCodec) Serialize(partitionID UniqueID, segmentID Unique
if err = AddFieldDataToPayload(eventWriter, field.DataType, singleData); err != nil {
eventWriter.Close()
writer.Close()
return nil, err
return err
}
writer.AddExtra(originalSizeKey, fmt.Sprintf("%v", blockMemorySize))
writer.SetEventTimeStamp(startTs, endTs)
@@ -319,14 +319,14 @@ func (insertCodec *InsertCodec) Serialize(partitionID UniqueID, segmentID Unique
if err != nil {
eventWriter.Close()
writer.Close()
return nil, err
return err
}
buffer, err := writer.GetBuffer()
if err != nil {
eventWriter.Close()
writer.Close()
return nil, err
return err
}
blobKey := fmt.Sprintf("%d", field.FieldID)
blobs = append(blobs, &Blob{
@@ -337,6 +337,21 @@ func (insertCodec *InsertCodec) Serialize(partitionID UniqueID, segmentID Unique
})
eventWriter.Close()
writer.Close()
return nil
}
for _, field := range insertCodec.Schema.Schema.Fields {
if err := serializeField(field); err != nil {
return nil, err
}
}
for _, structField := range insertCodec.Schema.Schema.StructArrayFields {
for _, field := range structField.Fields {
if err := serializeField(field); err != nil {
return nil, err
}
}
}
return blobs, nil
@@ -427,6 +442,13 @@ func AddFieldDataToPayload(eventWriter *insertEventWriter, dataType schemapb.Dat
if err = eventWriter.AddInt8VectorToPayload(singleData.(*Int8VectorFieldData).Data, singleData.(*Int8VectorFieldData).Dim); err != nil {
return err
}
case schemapb.DataType_ArrayOfVector:
// todo(SpadeA): optimize the serialization method
for _, singleArray := range singleData.(*VectorArrayFieldData).Data {
if err = eventWriter.AddOneVectorArrayToPayload(singleArray); err != nil {
return err
}
}
default:
return fmt.Errorf("undefined data type %d", dataType)
}
@@ -517,6 +539,27 @@ func (insertCodec *InsertCodec) DeserializeInto(fieldBinlogs []*Blob, rowNum int
return collectionID, partitionID, segmentID, nil
}
func GetVectorElementType(data *schemapb.VectorField) schemapb.DataType {
switch data.Data.(type) {
case *schemapb.VectorField_FloatVector:
return schemapb.DataType_FloatVector
case *schemapb.VectorField_BinaryVector:
return schemapb.DataType_BinaryVector
case *schemapb.VectorField_Float16Vector:
return schemapb.DataType_Float16Vector
case *schemapb.VectorField_Bfloat16Vector:
return schemapb.DataType_BFloat16Vector
case *schemapb.VectorField_Int8Vector:
return schemapb.DataType_Int8Vector
case *schemapb.VectorField_SparseFloatVector:
return schemapb.DataType_SparseFloatVector
case *schemapb.VectorField_VectorArray:
panic("unexpect vector element type")
default:
panic("unreacheable")
}
}
func AddInsertData(dataType schemapb.DataType, data interface{}, insertData *InsertData, fieldID int64, rowNum int, eventReader *EventReader, dim int, validData []bool) (dataLength int, err error) {
fieldData := insertData.Data[fieldID]
switch dataType {
@@ -731,6 +774,25 @@ func AddInsertData(dataType schemapb.DataType, data interface{}, insertData *Ins
insertData.Data[fieldID] = int8VectorFieldData
return length, nil
case schemapb.DataType_ArrayOfVector:
singleData := data.([]*schemapb.VectorField)
if len(singleData) == 0 {
return 0, nil
}
if fieldData == nil {
fieldData = &VectorArrayFieldData{
Data: make([]*schemapb.VectorField, 0, rowNum),
Dim: singleData[0].Dim,
ElementType: GetVectorElementType(singleData[0]),
}
}
VectorArrayFieldData := fieldData.(*VectorArrayFieldData)
VectorArrayFieldData.Data = append(VectorArrayFieldData.Data, singleData...)
insertData.Data[fieldID] = VectorArrayFieldData
return len(singleData), nil
default:
return 0, fmt.Errorf("undefined data type %d", dataType)
}
+180 -24
View File
@@ -35,27 +35,30 @@ import (
)
const (
CollectionID = 1
PartitionID = 1
SegmentID = 1
RowIDField = 0
TimestampField = 1
BoolField = 100
Int8Field = 101
Int16Field = 102
Int32Field = 103
Int64Field = 104
FloatField = 105
DoubleField = 106
StringField = 107
BinaryVectorField = 108
FloatVectorField = 109
ArrayField = 110
JSONField = 111
Float16VectorField = 112
BFloat16VectorField = 113
SparseFloatVectorField = 114
Int8VectorField = 115
CollectionID = 1
PartitionID = 1
SegmentID = 1
RowIDField = 0
TimestampField = 1
BoolField = 100
Int8Field = 101
Int16Field = 102
Int32Field = 103
Int64Field = 104
FloatField = 105
DoubleField = 106
StringField = 107
BinaryVectorField = 108
FloatVectorField = 109
ArrayField = 110
JSONField = 111
Float16VectorField = 112
BFloat16VectorField = 113
SparseFloatVectorField = 114
Int8VectorField = 115
StructField = 116
StructSubInt32Field = 117
StructSubFloatVectorField = 118
)
func assertTestData(t *testing.T, i int, value *Value) {
@@ -614,6 +617,34 @@ func genTestCollectionMeta() *etcdpb.CollectionMeta {
},
},
},
StructArrayFields: []*schemapb.StructArrayFieldSchema{
{
FieldID: StructField,
Name: "field_struct",
Fields: []*schemapb.FieldSchema{
{
FieldID: StructSubInt32Field,
Name: "field_sub_int",
Description: "int",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_Int32,
},
{
FieldID: StructSubFloatVectorField,
Name: "field_sub_float_vector",
Description: "float_vector",
DataType: schemapb.DataType_ArrayOfVector,
ElementType: schemapb.DataType_FloatVector,
TypeParams: []*commonpb.KeyValuePair{
{
Key: common.DimKey,
Value: "2",
},
},
},
},
},
},
},
}
}
@@ -763,6 +794,39 @@ func TestInsertCodec(t *testing.T) {
Data: []int8{-4, -5, -6, -7, -4, -5, -6, -7},
Dim: 4,
},
StructSubInt32Field: &ArrayFieldData{
ElementType: schemapb.DataType_Int32,
Data: []*schemapb.ScalarField{
{
Data: &schemapb.ScalarField_IntData{
IntData: &schemapb.IntArray{Data: []int32{3, 2, 1, 0}},
},
},
{
Data: &schemapb.ScalarField_IntData{
IntData: &schemapb.IntArray{Data: []int32{6, 5, 4, 3}},
},
},
},
},
StructSubFloatVectorField: &VectorArrayFieldData{
Dim: 2,
ElementType: schemapb.DataType_FloatVector,
Data: []*schemapb.VectorField{
{
Dim: 2,
Data: &schemapb.VectorField_FloatVector{
FloatVector: &schemapb.FloatArray{Data: []float32{3, 2, 1, 0}},
},
},
{
Dim: 2,
Data: &schemapb.VectorField_FloatVector{
FloatVector: &schemapb.FloatArray{Data: []float32{6, 5, 4, 3}},
},
},
},
},
},
}
@@ -830,6 +894,39 @@ func TestInsertCodec(t *testing.T) {
Data: []int8{0, 1, 2, 3, 0, 1, 2, 3},
Dim: 4,
},
StructSubInt32Field: &ArrayFieldData{
ElementType: schemapb.DataType_Int32,
Data: []*schemapb.ScalarField{
{
Data: &schemapb.ScalarField_IntData{
IntData: &schemapb.IntArray{Data: []int32{33, 22, 11, 0}},
},
},
{
Data: &schemapb.ScalarField_IntData{
IntData: &schemapb.IntArray{Data: []int32{66, 55, 44, 33}},
},
},
},
},
StructSubFloatVectorField: &VectorArrayFieldData{
Dim: 2,
ElementType: schemapb.DataType_FloatVector,
Data: []*schemapb.VectorField{
{
Dim: 2,
Data: &schemapb.VectorField_FloatVector{
FloatVector: &schemapb.FloatArray{Data: []float32{33, 22, 11, 0}},
},
},
{
Dim: 2,
Data: &schemapb.VectorField_FloatVector{
FloatVector: &schemapb.FloatArray{Data: []float32{66, 55, 44, 33}},
},
},
},
},
ArrayField: &ArrayFieldData{
ElementType: schemapb.DataType_Int32,
Data: []*schemapb.ScalarField{
@@ -876,9 +973,11 @@ func TestInsertCodec(t *testing.T) {
Contents: [][]byte{},
},
},
Int8VectorField: &Int8VectorFieldData{[]int8{}, 4},
ArrayField: &ArrayFieldData{schemapb.DataType_Int32, []*schemapb.ScalarField{}, nil, false},
JSONField: &JSONFieldData{[][]byte{}, nil, false},
Int8VectorField: &Int8VectorFieldData{[]int8{}, 4},
StructSubInt32Field: &ArrayFieldData{schemapb.DataType_Int32, []*schemapb.ScalarField{}, nil, false},
ArrayField: &ArrayFieldData{schemapb.DataType_Int32, []*schemapb.ScalarField{}, nil, false},
JSONField: &JSONFieldData{[][]byte{}, nil, false},
StructSubFloatVectorField: &VectorArrayFieldData{0, schemapb.DataType_FloatVector, []*schemapb.VectorField{}},
},
}
b, err := insertCodec.Serialize(PartitionID, SegmentID, insertDataEmpty)
@@ -953,6 +1052,20 @@ func TestInsertCodec(t *testing.T) {
}
assert.EqualValues(t, int32ArrayList, resultArrayList)
structInt32ArrayList := [][]int32{{33, 22, 11, 0}, {66, 55, 44, 33}, {3, 2, 1, 0}, {6, 5, 4, 3}}
resultStructInt32ArrayList := [][]int32{}
for _, v := range resultData.Data[StructSubInt32Field].(*ArrayFieldData).Data {
resultStructInt32ArrayList = append(resultStructInt32ArrayList, v.GetIntData().GetData())
}
assert.EqualValues(t, structInt32ArrayList, resultStructInt32ArrayList)
structFloatVectorArrayList := [][]float32{{33, 22, 11, 0}, {66, 55, 44, 33}, {3, 2, 1, 0}, {6, 5, 4, 3}}
resultStructFloatVectorArrayList := [][]float32{}
for _, v := range resultData.Data[StructSubFloatVectorField].(*VectorArrayFieldData).Data {
resultStructFloatVectorArrayList = append(resultStructFloatVectorArrayList, v.GetFloatVector().GetData())
}
assert.EqualValues(t, structFloatVectorArrayList, resultStructFloatVectorArrayList)
assert.Equal(t,
[][]byte{
[]byte(`{"batch":1}`),
@@ -1237,6 +1350,28 @@ func TestMemorySize(t *testing.T) {
},
},
},
StructSubInt32Field: &ArrayFieldData{
ElementType: schemapb.DataType_Int32,
Data: []*schemapb.ScalarField{
{
Data: &schemapb.ScalarField_IntData{
IntData: &schemapb.IntArray{Data: []int32{3, 2, 1, 0}},
},
},
},
},
StructSubFloatVectorField: &VectorArrayFieldData{
Dim: 2,
ElementType: schemapb.DataType_FloatVector,
Data: []*schemapb.VectorField{
{
Dim: 2,
Data: &schemapb.VectorField_FloatVector{
FloatVector: &schemapb.FloatArray{Data: []float32{3, 2, 1, 0}},
},
},
},
},
JSONField: &JSONFieldData{
Data: [][]byte{
[]byte(`{"batch":1}`),
@@ -1261,6 +1396,8 @@ func TestMemorySize(t *testing.T) {
assert.Equal(t, insertData1.Data[Int8VectorField].GetMemorySize(), 8)
assert.Equal(t, insertData1.Data[ArrayField].GetMemorySize(), 3*4+1)
assert.Equal(t, insertData1.Data[JSONField].GetMemorySize(), len([]byte(`{"batch":1}`))+16+1)
assert.Equal(t, insertData1.Data[StructSubInt32Field].GetMemorySize(), 4*4+1)
assert.Equal(t, insertData1.Data[StructSubFloatVectorField].GetMemorySize(), 4*4+4)
insertData2 := &InsertData{
Data: map[int64]FieldData{
@@ -1350,6 +1487,11 @@ func TestMemorySize(t *testing.T) {
Float16VectorField: &Float16VectorFieldData{[]byte{}, 4},
BFloat16VectorField: &BFloat16VectorFieldData{[]byte{}, 4},
Int8VectorField: &Int8VectorFieldData{[]int8{}, 4},
StructSubFloatVectorField: &VectorArrayFieldData{
Dim: 2,
ElementType: schemapb.DataType_FloatVector,
Data: []*schemapb.VectorField{},
},
},
}
@@ -1368,6 +1510,7 @@ func TestMemorySize(t *testing.T) {
assert.Equal(t, insertDataEmpty.Data[Float16VectorField].GetMemorySize(), 4)
assert.Equal(t, insertDataEmpty.Data[BFloat16VectorField].GetMemorySize(), 4)
assert.Equal(t, insertDataEmpty.Data[Int8VectorField].GetMemorySize(), 4)
assert.Equal(t, insertDataEmpty.Data[StructSubFloatVectorField].GetMemorySize(), 0)
}
func TestDeleteData(t *testing.T) {
@@ -1463,4 +1606,17 @@ func TestAddFieldDataToPayload(t *testing.T) {
assert.Error(t, err)
err = AddFieldDataToPayload(e, schemapb.DataType_Int8Vector, &Int8VectorFieldData{[]int8{}, 4})
assert.Error(t, err)
err = AddFieldDataToPayload(e, schemapb.DataType_ArrayOfVector, &VectorArrayFieldData{
Dim: 2,
ElementType: schemapb.DataType_FloatVector,
Data: []*schemapb.VectorField{
{
Dim: 2,
Data: &schemapb.VectorField_FloatVector{
FloatVector: &schemapb.FloatArray{Data: []float32{1, 2, 3, 4}},
},
},
},
})
assert.Error(t, err)
}
+12 -1
View File
@@ -25,6 +25,7 @@ import (
type DataSorter struct {
InsertCodec *InsertCodec
InsertData *InsertData
AllFields []*schemapb.FieldSchema
}
// getRowIDFieldData returns auto generated row id Field
@@ -50,7 +51,14 @@ func (ds *DataSorter) Len() int {
// Swap swaps each field's i-th and j-th element
func (ds *DataSorter) Swap(i, j int) {
for _, field := range ds.InsertCodec.Schema.Schema.Fields {
if ds.AllFields == nil {
allFields := ds.InsertCodec.Schema.Schema.Fields
for _, field := range ds.InsertCodec.Schema.Schema.StructArrayFields {
allFields = append(allFields, field.Fields...)
}
ds.AllFields = allFields
}
for _, field := range ds.AllFields {
singleData, has := ds.InsertData.Data[field.FieldID]
if !has {
continue
@@ -117,6 +125,9 @@ func (ds *DataSorter) Swap(i, j int) {
case schemapb.DataType_SparseFloatVector:
fieldData := singleData.(*SparseFloatVectorFieldData)
fieldData.Contents[i], fieldData.Contents[j] = fieldData.Contents[j], fieldData.Contents[i]
case schemapb.DataType_ArrayOfVector:
fieldData := singleData.(*VectorArrayFieldData)
fieldData.Data[i], fieldData.Data[j] = fieldData.Data[j], fieldData.Data[i]
default:
errMsg := "undefined data type " + string(field.DataType)
panic(errMsg)
+46
View File
@@ -144,6 +144,21 @@ func TestDataSorter(t *testing.T) {
DataType: schemapb.DataType_SparseFloatVector,
},
},
StructArrayFields: []*schemapb.StructArrayFieldSchema{
{
FieldID: 113,
Name: "field_struct",
Fields: []*schemapb.FieldSchema{
{
FieldID: 114,
Name: "field_sturct_float_vector",
Description: "float",
DataType: schemapb.DataType_ArrayOfVector,
ElementType: schemapb.DataType_FloatVector,
},
},
},
},
},
}
@@ -206,6 +221,30 @@ func TestDataSorter(t *testing.T) {
},
},
},
114: &VectorArrayFieldData{
Dim: 2,
ElementType: schemapb.DataType_FloatVector,
Data: []*schemapb.VectorField{
{
Dim: 2,
Data: &schemapb.VectorField_FloatVector{
FloatVector: &schemapb.FloatArray{Data: []float32{1, 2, 3, 4}},
},
},
{
Dim: 2,
Data: &schemapb.VectorField_FloatVector{
FloatVector: &schemapb.FloatArray{Data: []float32{5, 6, 7, 8, 9, 10}},
},
},
{
Dim: 2,
Data: &schemapb.VectorField_FloatVector{
FloatVector: &schemapb.FloatArray{Data: []float32{11, 12, 13, 14, 15, 16}},
},
},
},
},
},
}
@@ -278,6 +317,13 @@ func TestDataSorter(t *testing.T) {
typeutil.CreateSparseFloatRow([]uint32{10, 20, 30}, []float32{2.1, 2.2, 2.3}),
},
}, &dataSorter.InsertData.Data[112].(*SparseFloatVectorFieldData).SparseFloatArray)
assert.Equal(t, []float32{11, 12, 13, 14, 15, 16},
dataSorter.InsertData.Data[114].(*VectorArrayFieldData).Data[0].Data.(*schemapb.VectorField_FloatVector).FloatVector.Data)
assert.Equal(t, []float32{1, 2, 3, 4},
dataSorter.InsertData.Data[114].(*VectorArrayFieldData).Data[1].Data.(*schemapb.VectorField_FloatVector).FloatVector.Data)
assert.Equal(t, []float32{5, 6, 7, 8, 9, 10},
dataSorter.InsertData.Data[114].(*VectorArrayFieldData).Data[2].Data.(*schemapb.VectorField_FloatVector).FloatVector.Data)
}
func TestDataSorter_Len(t *testing.T) {
+122 -7
View File
@@ -66,30 +66,45 @@ func NewInsertDataWithCap(schema *schemapb.CollectionSchema, cap int, withFuncti
Data: make(map[FieldID]FieldData),
}
for _, field := range schema.Fields {
appendField := func(field *schemapb.FieldSchema) error {
if field.IsPrimaryKey && field.GetNullable() {
return nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("primary key field should not be nullable (field: %s)", field.Name))
return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("primary key field should not be nullable (field: %s)", field.Name))
}
if field.IsPartitionKey && field.GetNullable() {
return nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("partition key field should not be nullable (field: %s)", field.Name))
return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("partition key field should not be nullable (field: %s)", field.Name))
}
if field.IsFunctionOutput {
if field.IsPrimaryKey || field.IsPartitionKey {
return nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("function output field should not be primary key or partition key (field: %s)", field.Name))
return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("function output field should not be primary key or partition key (field: %s)", field.Name))
}
if field.GetNullable() {
return nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("function output field should not be nullable (field: %s)", field.Name))
return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("function output field should not be nullable (field: %s)", field.Name))
}
if !withFunctionOutput {
continue
return nil
}
}
fieldData, err := NewFieldData(field.DataType, field, cap)
if err != nil {
return nil, err
return err
}
idata.Data[field.FieldID] = fieldData
return nil
}
for _, field := range schema.Fields {
if err := appendField(field); err != nil {
return nil, err
}
}
for _, structField := range schema.StructArrayFields {
for _, field := range structField.GetFields() {
if err := appendField(field); err != nil {
return nil, err
}
}
}
return idata, nil
}
@@ -341,6 +356,12 @@ func NewFieldData(dataType schemapb.DataType, fieldSchema *schemapb.FieldSchema,
data.ValidData = make([]bool, 0, cap)
}
return data, nil
case schemapb.DataType_ArrayOfVector:
data := &VectorArrayFieldData{
Data: make([]*schemapb.VectorField, 0, cap),
ElementType: fieldSchema.GetElementType(),
}
return data, nil
default:
return nil, fmt.Errorf("Unexpected schema data type: %d", dataType)
}
@@ -424,6 +445,12 @@ type Int8VectorFieldData struct {
Dim int
}
type VectorArrayFieldData struct {
Dim int64
ElementType schemapb.DataType
Data []*schemapb.VectorField
}
func (dst *SparseFloatVectorFieldData) AppendAllRows(src *SparseFloatVectorFieldData) {
if len(src.Contents) == 0 {
return
@@ -453,6 +480,9 @@ func (data *BFloat16VectorFieldData) RowNum() int {
}
func (data *SparseFloatVectorFieldData) RowNum() int { return len(data.Contents) }
func (data *Int8VectorFieldData) RowNum() int { return len(data.Data) / data.Dim }
func (data *VectorArrayFieldData) RowNum() int {
return len(data.Data)
}
// GetRow implements FieldData.GetRow
func (data *BoolFieldData) GetRow(i int) any {
@@ -549,6 +579,10 @@ func (data *Int8VectorFieldData) GetRow(i int) interface{} {
return data.Data[i*data.Dim : (i+1)*data.Dim]
}
func (data *VectorArrayFieldData) GetRow(i int) interface{} {
return data.Data[i]
}
func (data *BoolFieldData) GetDataRows() any { return data.Data }
func (data *Int8FieldData) GetDataRows() any { return data.Data }
func (data *Int16FieldData) GetDataRows() any { return data.Data }
@@ -565,6 +599,7 @@ func (data *Float16VectorFieldData) GetDataRows() any { return data.Data }
func (data *BFloat16VectorFieldData) GetDataRows() any { return data.Data }
func (data *SparseFloatVectorFieldData) GetDataRows() any { return data.Contents }
func (data *Int8VectorFieldData) GetDataRows() any { return data.Data }
func (data *VectorArrayFieldData) GetDataRows() any { return data.Data }
// AppendRow implements FieldData.AppendRow
func (data *BoolFieldData) AppendRow(row interface{}) error {
@@ -798,6 +833,15 @@ func (data *Int8VectorFieldData) AppendRow(row interface{}) error {
return nil
}
func (data *VectorArrayFieldData) AppendRow(row interface{}) error {
v, ok := row.(*schemapb.VectorField)
if !ok {
return merr.WrapErrParameterInvalid("[]*schemapb.VectorField", row, "Wrong row type")
}
data.Data = append(data.Data, v)
return nil
}
func (data *BoolFieldData) AppendRows(dataRows interface{}, validDataRows interface{}) error {
err := data.AppendDataRows(dataRows)
if err != nil {
@@ -930,6 +974,14 @@ func (data *Int8VectorFieldData) AppendRows(dataRows interface{}, validDataRows
return data.AppendValidDataRows(validDataRows)
}
func (data *VectorArrayFieldData) AppendRows(dataRows interface{}, validDataRows interface{}) error {
err := data.AppendDataRows(dataRows)
if err != nil {
return err
}
return data.AppendValidDataRows(validDataRows)
}
func (data *BoolFieldData) AppendDataRows(rows interface{}) error {
v, ok := rows.([]bool)
if !ok {
@@ -1096,6 +1148,15 @@ func (data *Int8VectorFieldData) AppendDataRows(rows interface{}) error {
return nil
}
func (data *VectorArrayFieldData) AppendDataRows(rows interface{}) error {
v, ok := rows.([]*schemapb.VectorField)
if !ok {
return merr.WrapErrParameterInvalid("[]*schemapb.VectorField", rows, "Wrong rows type")
}
data.Data = append(data.Data, v...)
return nil
}
func (data *BoolFieldData) AppendValidDataRows(rows interface{}) error {
if rows == nil {
return nil
@@ -1230,6 +1291,19 @@ func (data *BinaryVectorFieldData) AppendValidDataRows(rows interface{}) error {
return nil
}
func (data *VectorArrayFieldData) AppendValidDataRows(rows interface{}) error {
if rows != nil {
v, ok := rows.([]bool)
if !ok {
return merr.WrapErrParameterInvalid("[]bool", rows, "Wrong rows type")
}
if len(v) != 0 {
return merr.WrapErrParameterInvalidMsg("not support Nullable in vector")
}
}
return nil
}
// AppendValidDataRows appends FLATTEN vectors to field data.
func (data *FloatVectorFieldData) AppendValidDataRows(rows interface{}) error {
if rows != nil {
@@ -1338,6 +1412,35 @@ func (data *SparseFloatVectorFieldData) GetMemorySize() int {
func (data *Int8VectorFieldData) GetMemorySize() int { return binary.Size(data.Data) + 4 }
func GetVectorSize(vector *schemapb.VectorField, vectorType schemapb.DataType) int {
size := 0
switch vectorType {
case schemapb.DataType_BinaryVector:
size += binary.Size(vector.GetBinaryVector()) + 4
case schemapb.DataType_FloatVector:
size += binary.Size(vector.GetFloatVector().Data) + 4
case schemapb.DataType_Float16Vector:
size += binary.Size(vector.GetFloat16Vector()) + 4
case schemapb.DataType_BFloat16Vector:
size += binary.Size(vector.GetBfloat16Vector()) + 4
case schemapb.DataType_SparseFloatVector:
panic("not implemented")
case schemapb.DataType_Int8Vector:
size += binary.Size(vector.GetInt8Vector()) + 4
default:
panic("unreachable")
}
return size
}
func (data *VectorArrayFieldData) GetMemorySize() int {
var size int
for _, val := range data.Data {
size += GetVectorSize(val, data.ElementType)
}
return size
}
// GetDataType implements FieldData.GetDataType
func (data *BoolFieldData) GetDataType() schemapb.DataType { return schemapb.DataType_Bool }
func (data *Int8FieldData) GetDataType() schemapb.DataType { return schemapb.DataType_Int8 }
@@ -1373,6 +1476,10 @@ func (data *Int8VectorFieldData) GetDataType() schemapb.DataType {
return schemapb.DataType_Int8Vector
}
func (data *VectorArrayFieldData) GetDataType() schemapb.DataType {
return schemapb.DataType_ArrayOfVector
}
// why not binary.Size(data) directly? binary.Size(data) return -1
// binary.Size returns how many bytes Write would generate to encode the value v, which
// must be a fixed-size value or a slice of fixed-size values, or a pointer to such data.
@@ -1458,6 +1565,10 @@ func (data *SparseFloatVectorFieldData) GetRowSize(i int) int {
return len(data.Contents[i])
}
func (data *VectorArrayFieldData) GetRowSize(i int) int {
return GetVectorSize(data.Data[i], data.ElementType)
}
func (data *BoolFieldData) GetNullable() bool {
return data.Nullable
}
@@ -1521,3 +1632,7 @@ func (data *ArrayFieldData) GetNullable() bool {
func (data *JSONFieldData) GetNullable() bool {
return data.Nullable
}
func (data *VectorArrayFieldData) GetNullable() bool {
return false
}
+45 -6
View File
@@ -115,15 +115,15 @@ func (s *InsertDataSuite) TestInsertData() {
s.Run("init by New", func() {
s.True(s.iDataEmpty.IsEmpty())
s.Equal(0, s.iDataEmpty.GetRowNum())
s.Equal(32, s.iDataEmpty.GetMemorySize())
s.Equal(33, s.iDataEmpty.GetMemorySize())
s.False(s.iDataOneRow.IsEmpty())
s.Equal(1, s.iDataOneRow.GetRowNum())
s.Equal(199, s.iDataOneRow.GetMemorySize())
s.Equal(240, s.iDataOneRow.GetMemorySize())
s.False(s.iDataTwoRows.IsEmpty())
s.Equal(2, s.iDataTwoRows.GetRowNum())
s.Equal(364, s.iDataTwoRows.GetMemorySize())
s.Equal(433, s.iDataTwoRows.GetMemorySize())
for _, field := range s.iDataTwoRows.Data {
s.Equal(2, field.RowNum())
@@ -153,6 +153,8 @@ func (s *InsertDataSuite) TestMemorySize() {
s.Equal(s.iDataEmpty.Data[BFloat16VectorField].GetMemorySize(), 4)
s.Equal(s.iDataEmpty.Data[SparseFloatVectorField].GetMemorySize(), 0)
s.Equal(s.iDataEmpty.Data[Int8VectorField].GetMemorySize(), 4)
s.Equal(s.iDataEmpty.Data[StructSubInt32Field].GetMemorySize(), 1)
s.Equal(s.iDataEmpty.Data[StructSubFloatVectorField].GetMemorySize(), 0)
s.Equal(s.iDataOneRow.Data[RowIDField].GetMemorySize(), 9)
s.Equal(s.iDataOneRow.Data[TimestampField].GetMemorySize(), 9)
@@ -172,6 +174,8 @@ func (s *InsertDataSuite) TestMemorySize() {
s.Equal(s.iDataOneRow.Data[BFloat16VectorField].GetMemorySize(), 12)
s.Equal(s.iDataOneRow.Data[SparseFloatVectorField].GetMemorySize(), 28)
s.Equal(s.iDataOneRow.Data[Int8VectorField].GetMemorySize(), 8)
s.Equal(s.iDataOneRow.Data[StructSubInt32Field].GetMemorySize(), 3*4+1)
s.Equal(s.iDataOneRow.Data[StructSubFloatVectorField].GetMemorySize(), 3*4*2+4)
s.Equal(s.iDataTwoRows.Data[RowIDField].GetMemorySize(), 17)
s.Equal(s.iDataTwoRows.Data[TimestampField].GetMemorySize(), 17)
@@ -190,6 +194,8 @@ func (s *InsertDataSuite) TestMemorySize() {
s.Equal(s.iDataTwoRows.Data[BFloat16VectorField].GetMemorySize(), 20)
s.Equal(s.iDataTwoRows.Data[SparseFloatVectorField].GetMemorySize(), 54)
s.Equal(s.iDataTwoRows.Data[Int8VectorField].GetMemorySize(), 12)
s.Equal(s.iDataTwoRows.Data[StructSubInt32Field].GetMemorySize(), 3*4+2*4+1)
s.Equal(s.iDataTwoRows.Data[StructSubFloatVectorField].GetMemorySize(), 3*4*2+4+2*4*2+4)
}
func (s *InsertDataSuite) TestGetRowSize() {
@@ -211,10 +217,21 @@ func (s *InsertDataSuite) TestGetRowSize() {
s.Equal(s.iDataOneRow.Data[BFloat16VectorField].GetRowSize(0), 8)
s.Equal(s.iDataOneRow.Data[SparseFloatVectorField].GetRowSize(0), 24)
s.Equal(s.iDataOneRow.Data[Int8VectorField].GetRowSize(0), 4)
s.Equal(s.iDataOneRow.Data[StructSubInt32Field].GetRowSize(0), 3*4)
s.Equal(s.iDataOneRow.Data[StructSubFloatVectorField].GetRowSize(0), 3*4*2+4)
}
func GetFields(schema *schemapb.CollectionSchema) []*schemapb.FieldSchema {
ret := make([]*schemapb.FieldSchema, 0, 100)
ret = append(ret, schema.GetFields()...)
for _, structField := range schema.GetStructArrayFields() {
ret = append(ret, structField.GetFields()...)
}
return ret
}
func (s *InsertDataSuite) TestGetDataType() {
for _, field := range s.schema.GetFields() {
for _, field := range GetFields(s.schema) {
fieldData, ok := s.iDataOneRow.Data[field.GetFieldID()]
s.True(ok)
s.Equal(field.GetDataType(), fieldData.GetDataType())
@@ -222,7 +239,7 @@ func (s *InsertDataSuite) TestGetDataType() {
}
func (s *InsertDataSuite) TestGetNullable() {
for _, field := range s.schema.GetFields() {
for _, field := range GetFields(s.schema) {
fieldData, ok := s.iDataOneRow.Data[field.GetFieldID()]
s.True(ok)
s.Equal(field.GetNullable(), fieldData.GetNullable())
@@ -235,7 +252,7 @@ func (s *InsertDataSuite) SetupTest() {
s.Require().NoError(err)
s.True(s.iDataEmpty.IsEmpty())
s.Equal(0, s.iDataEmpty.GetRowNum())
s.Equal(32, s.iDataEmpty.GetMemorySize())
s.Equal(33, s.iDataEmpty.GetMemorySize())
row1 := map[FieldID]interface{}{
RowIDField: int64(3),
@@ -260,6 +277,17 @@ func (s *InsertDataSuite) SetupTest() {
},
},
JSONField: []byte(`{"batch":3}`),
StructSubInt32Field: &schemapb.ScalarField{
Data: &schemapb.ScalarField_IntData{
IntData: &schemapb.IntArray{Data: []int32{1, 2, 3}},
},
},
StructSubFloatVectorField: &schemapb.VectorField{
Dim: 2,
Data: &schemapb.VectorField_FloatVector{
FloatVector: &schemapb.FloatArray{Data: []float32{1, 2, 3, 4, 5, 6}},
},
},
}
s.iDataOneRow, err = NewInsertData(s.schema)
@@ -294,6 +322,17 @@ func (s *InsertDataSuite) SetupTest() {
},
},
JSONField: []byte(`{"batch":1}`),
StructSubInt32Field: &schemapb.ScalarField{
Data: &schemapb.ScalarField_IntData{
IntData: &schemapb.IntArray{Data: []int32{1, 2}},
},
},
StructSubFloatVectorField: &schemapb.VectorField{
Dim: 2,
Data: &schemapb.VectorField_FloatVector{
FloatVector: &schemapb.FloatArray{Data: []float32{1, 2, 3, 4}},
},
},
}
s.iDataTwoRows, err = NewInsertData(s.schema)
+2
View File
@@ -44,6 +44,7 @@ type PayloadWriterInterface interface {
AddBFloat16VectorToPayload([]byte, int) error
AddSparseFloatVectorToPayload(*SparseFloatVectorFieldData) error
AddInt8VectorToPayload([]int8, int) error
AddOneVectorArrayToPayload(*schemapb.VectorField) error
FinishPayloadWriter() error
GetPayloadBufferFromWriter() ([]byte, error)
GetPayloadLengthFromWriter() (int, error)
@@ -65,6 +66,7 @@ type PayloadReaderInterface interface {
GetDoubleFromPayload() ([]float64, []bool, error)
GetStringFromPayload() ([]string, []bool, error)
GetArrayFromPayload() ([]*schemapb.ScalarField, []bool, error)
GetVectorArrayFromPayload() ([]*schemapb.VectorField, error)
GetJSONFromPayload() ([][]byte, []bool, error)
GetBinaryVectorFromPayload() ([]byte, int, error)
GetFloat16VectorFromPayload() ([]byte, int, error)
+19
View File
@@ -98,6 +98,9 @@ func (r *PayloadReader) GetDataFromPayload() (interface{}, []bool, int, error) {
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
@@ -416,6 +419,22 @@ func (r *PayloadReader) GetArrayFromPayload() ([]*schemapb.ScalarField, []bool,
return value, nil, nil
}
func (r *PayloadReader) GetVectorArrayFromPayload() ([]*schemapb.VectorField, error) {
if r.colType != schemapb.DataType_ArrayOfVector {
return nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("failed to get vector from datatype %v", r.colType.String()))
}
value, err := readByteAndConvert(r, func(bytes parquet.ByteArray) *schemapb.VectorField {
v := &schemapb.VectorField{}
proto.Unmarshal(bytes, v)
return v
})
if err != nil {
return nil, err
}
return value, nil
}
func (r *PayloadReader) GetJSONFromPayload() ([][]byte, []bool, error) {
if r.colType != schemapb.DataType_JSON {
return nil, nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("failed to get json from datatype %v", r.colType.String()))
+60
View File
@@ -1706,6 +1706,66 @@ func TestPayload_ReaderAndWriter(t *testing.T) {
err = w.AddOneJSONToPayload([]byte(`{"1":"1"}`), false)
assert.ErrorIs(t, err, merr.ErrParameterInvalid)
})
t.Run("TestVectorArray", func(t *testing.T) {
w, err := NewPayloadWriter(schemapb.DataType_Array)
require.Nil(t, err)
require.NotNil(t, w)
err = w.AddOneVectorArrayToPayload(&schemapb.VectorField{
Data: &schemapb.VectorField_FloatVector{
FloatVector: &schemapb.FloatArray{
Data: []float32{1.0, 2.0, 3.0, 4.0},
},
},
})
assert.NoError(t, err)
err = w.AddOneVectorArrayToPayload(&schemapb.VectorField{
Data: &schemapb.VectorField_FloatVector{
FloatVector: &schemapb.FloatArray{
Data: []float32{11.0, 22.0, 33.0, 44.0},
},
},
})
assert.NoError(t, err)
err = w.AddOneVectorArrayToPayload(&schemapb.VectorField{
Data: &schemapb.VectorField_FloatVector{
FloatVector: &schemapb.FloatArray{
Data: []float32{111.0, 222.0, 333.0, 444.0},
},
},
})
assert.NoError(t, err)
err = w.FinishPayloadWriter()
assert.NoError(t, err)
length, err := w.GetPayloadLengthFromWriter()
assert.NoError(t, err)
assert.Equal(t, length, 3)
buffer, err := w.GetPayloadBufferFromWriter()
assert.NoError(t, err)
r, err := NewPayloadReader(schemapb.DataType_ArrayOfVector, buffer, false)
assert.NoError(t, err)
length, err = r.GetPayloadLengthFromReader()
assert.NoError(t, err)
assert.Equal(t, length, 3)
arrayList, err := r.GetVectorArrayFromPayload()
assert.NoError(t, err)
assert.EqualValues(t, []float32{1.0, 2.0, 3.0, 4.0}, arrayList[0].GetFloatVector().GetData())
assert.EqualValues(t, []float32{11.0, 22.0, 33.0, 44.0}, arrayList[1].GetFloatVector().GetData())
assert.EqualValues(t, []float32{111.0, 222.0, 333.0, 444.0}, arrayList[2].GetFloatVector().GetData())
iArrayList, _, _, err := r.GetDataFromPayload()
arrayList = iArrayList.([]*schemapb.VectorField)
assert.NoError(t, err)
assert.EqualValues(t, []float32{1.0, 2.0, 3.0, 4.0}, arrayList[0].GetFloatVector().GetData())
assert.EqualValues(t, []float32{11.0, 22.0, 33.0, 44.0}, arrayList[1].GetFloatVector().GetData())
assert.EqualValues(t, []float32{111.0, 222.0, 333.0, 444.0}, arrayList[2].GetFloatVector().GetData())
r.ReleasePayloadReader()
w.ReleasePayloadWriter()
})
}
func TestPayload_NullableReaderAndWriter(t *testing.T) {
+28
View File
@@ -243,6 +243,12 @@ func (w *NativePayloadWriter) AddDataToPayload(data interface{}, validData []boo
return merr.WrapErrParameterInvalidMsg("incorrect data type")
}
return w.AddInt8VectorToPayload(val, w.dim.GetValue())
case schemapb.DataType_ArrayOfVector:
val, ok := data.(*schemapb.VectorField)
if !ok {
return merr.WrapErrParameterInvalidMsg("incorrect data type")
}
return w.AddOneVectorArrayToPayload(val)
default:
return errors.New("unsupported datatype")
}
@@ -702,6 +708,26 @@ func (w *NativePayloadWriter) AddInt8VectorToPayload(data []int8, dim int) error
return nil
}
func (w *NativePayloadWriter) AddOneVectorArrayToPayload(data *schemapb.VectorField) error {
if w.finished {
return errors.New("can't append data to finished vector array payload")
}
bytes, err := proto.Marshal(data)
if err != nil {
return errors.New("Marshal VectorField failed")
}
builder, ok := w.builder.(*array.BinaryBuilder)
if !ok {
return errors.New("failed to cast VectorArrayBuilder")
}
builder.Append(bytes)
return nil
}
func (w *NativePayloadWriter) FinishPayloadWriter() error {
if w.finished {
return errors.New("can't reuse a finished writer")
@@ -808,6 +834,8 @@ func MilvusDataTypeToArrowType(dataType schemapb.DataType, dim int) arrow.DataTy
return &arrow.FixedSizeBinaryType{
ByteWidth: dim,
}
case schemapb.DataType_ArrayOfVector:
return &arrow.BinaryType{}
default:
panic("unsupported data type")
}
+24
View File
@@ -297,6 +297,30 @@ func TestPayloadWriter_Failed(t *testing.T) {
err = w.AddFloatToPayload(data, nil)
require.Error(t, err)
})
t.Run("Test ArrayOfFloatVector", func(t *testing.T) {
// The dim is not used for ArrayOfVector type for payload writer as each row contains this info
w, err := NewPayloadWriter(schemapb.DataType_ArrayOfVector, WithDim(1))
require.Nil(t, err)
require.NotNil(t, w)
err = w.FinishPayloadWriter()
require.NoError(t, err)
err = w.AddOneVectorArrayToPayload(&schemapb.VectorField{
Dim: 8,
})
require.Error(t, err)
w, err = NewPayloadWriter(schemapb.DataType_Int64)
require.Nil(t, err)
require.NotNil(t, w)
err = w.AddOneVectorArrayToPayload(&schemapb.VectorField{
Dim: 8,
})
require.Error(t, err)
})
}
func TestParquetEncoding(t *testing.T) {
+3 -3
View File
@@ -142,7 +142,7 @@ func (s *PackedBinlogRecordSuite) TestPackedBinlogRecordIntegration() {
for i := 1; i <= rows; i++ {
value, err := reader.NextValue()
s.NoError(err)
rec, err := ValueSerializer([]*Value{*value}, s.schema.Fields)
rec, err := ValueSerializer([]*Value{*value}, s.schema)
s.NoError(err)
err = w.Write(rec)
s.NoError(err)
@@ -225,7 +225,7 @@ func (s *PackedBinlogRecordSuite) TestGenerateBM25Stats() {
Timestamp: int64(tsoutil.ComposeTSByTime(getMilvusBirthday(), 0)),
Value: genRowWithBM25(0),
}
rec, err := ValueSerializer([]*Value{v}, s.schema.Fields)
rec, err := ValueSerializer([]*Value{v}, s.schema)
s.NoError(err)
w, err := NewBinlogRecordWriter(s.ctx, s.collectionID, s.partitionID, s.segmentID, s.schema, s.logIDAlloc, s.chunkSize, s.maxRowNum, wOption...)
@@ -338,7 +338,7 @@ func (s *PackedBinlogRecordSuite) TestAllocIDExhausedError() {
value, err := reader.NextValue()
s.NoError(err)
rec, err := ValueSerializer([]*Value{*value}, s.schema.Fields)
rec, err := ValueSerializer([]*Value{*value}, s.schema)
s.NoError(err)
err = w.Write(rec)
s.Error(err)
+23 -5
View File
@@ -10,11 +10,15 @@ import (
"github.com/milvus-io/milvus/pkg/v2/util/merr"
)
func ConvertToArrowSchema(fields []*schemapb.FieldSchema) (*arrow.Schema, error) {
arrowFields := make([]arrow.Field, 0, len(fields))
for _, field := range fields {
func ConvertToArrowSchema(schema *schemapb.CollectionSchema) (*arrow.Schema, error) {
fieldCount := len(schema.GetFields())
for _, structField := range schema.GetStructArrayFields() {
fieldCount += len(structField.GetFields())
}
arrowFields := make([]arrow.Field, 0, fieldCount)
appendArrowField := func(field *schemapb.FieldSchema) error {
if serdeMap[field.DataType].arrowType == nil {
return nil, merr.WrapErrParameterInvalidMsg("unknown field data type [%s] for field [%s]", field.DataType, field.GetName())
return merr.WrapErrParameterInvalidMsg("unknown field data type [%s] for field [%s]", field.DataType, field.GetName())
}
var dim int
switch field.DataType {
@@ -23,12 +27,26 @@ func ConvertToArrowSchema(fields []*schemapb.FieldSchema) (*arrow.Schema, error)
var err error
dim, err = GetDimFromParams(field.TypeParams)
if err != nil {
return nil, merr.WrapErrParameterInvalidMsg("dim not found in field [%s] params", field.GetName())
return merr.WrapErrParameterInvalidMsg("dim not found in field [%s] params", field.GetName())
}
default:
dim = 0
}
arrowFields = append(arrowFields, ConvertToArrowField(field, serdeMap[field.DataType].arrowType(dim)))
return nil
}
for _, field := range schema.GetFields() {
if err := appendArrowField(field); err != nil {
return nil, err
}
}
for _, structField := range schema.GetStructArrayFields() {
for _, field := range structField.GetFields() {
if err := appendArrowField(field); err != nil {
return nil, err
}
}
}
return arrow.NewSchema(arrowFields, nil), nil
+17 -3
View File
@@ -45,9 +45,20 @@ func TestConvertArrowSchema(t *testing.T) {
{FieldID: 16, Name: "field15", DataType: schemapb.DataType_Int8Vector, TypeParams: []*commonpb.KeyValuePair{{Key: "dim", Value: "128"}}},
}
schema, err := ConvertToArrowSchema(fieldSchemas)
StructArrayFieldSchemas := []*schemapb.StructArrayFieldSchema{
{FieldID: 17, Name: "struct_field0", Fields: []*schemapb.FieldSchema{
{FieldID: 18, Name: "field16", DataType: schemapb.DataType_Array, ElementType: schemapb.DataType_Int64},
{FieldID: 19, Name: "field17", DataType: schemapb.DataType_Array, ElementType: schemapb.DataType_Float},
}},
}
schema := &schemapb.CollectionSchema{
Fields: fieldSchemas,
StructArrayFields: StructArrayFieldSchemas,
}
arrowSchema, err := ConvertToArrowSchema(schema)
assert.NoError(t, err)
assert.Equal(t, len(fieldSchemas), len(schema.Fields()))
assert.Equal(t, len(fieldSchemas)+len(StructArrayFieldSchemas[0].Fields), len(arrowSchema.Fields()))
}
func TestConvertArrowSchemaWithoutDim(t *testing.T) {
@@ -70,6 +81,9 @@ func TestConvertArrowSchemaWithoutDim(t *testing.T) {
{FieldID: 16, Name: "field15", DataType: schemapb.DataType_Int8Vector, TypeParams: []*commonpb.KeyValuePair{}},
}
_, err := ConvertToArrowSchema(fieldSchemas)
schema := &schemapb.CollectionSchema{
Fields: fieldSchemas,
}
_, err := ConvertToArrowSchema(schema)
assert.Error(t, err)
}
+25 -4
View File
@@ -401,6 +401,12 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry {
return true
}
}
if vv, ok := v.(*schemapb.VectorField); ok {
if bytes, err := proto.Marshal(vv); err == nil {
builder.Append(bytes)
return true
}
}
}
return false
},
@@ -408,6 +414,7 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry {
m[schemapb.DataType_Array] = eagerArrayEntry
m[schemapb.DataType_JSON] = byteEntry
m[schemapb.DataType_ArrayOfVector] = byteEntry
fixedSizeDeserializer := func(a arrow.Array, i int, shouldCopy bool) (any, bool) {
if a.IsNull(i) {
@@ -907,13 +914,14 @@ func NewSimpleArrowRecord(r arrow.Record, field2Col map[FieldID]int) *simpleArro
}
}
func BuildRecord(b *array.RecordBuilder, data *InsertData, fields []*schemapb.FieldSchema) error {
func BuildRecord(b *array.RecordBuilder, data *InsertData, schema *schemapb.CollectionSchema) error {
if data == nil {
return nil
}
for i, field := range fields {
fBuilder := b.Field(i)
idx := 0
serializeField := func(field *schemapb.FieldSchema) error {
fBuilder := b.Field(idx)
idx++
typeEntry, ok := serdeMap[field.DataType]
if !ok {
panic("unknown type")
@@ -933,6 +941,19 @@ func BuildRecord(b *array.RecordBuilder, data *InsertData, fields []*schemapb.Fi
return merr.WrapErrServiceInternal(fmt.Sprintf("serialize error on type %s", field.DataType.String()))
}
}
return nil
}
for _, field := range schema.GetFields() {
if err := serializeField(field); err != nil {
return err
}
}
for _, structField := range schema.GetStructArrayFields() {
for _, field := range structField.GetFields() {
if err := serializeField(field); err != nil {
return err
}
}
}
return nil
}
+93 -32
View File
@@ -78,8 +78,13 @@ func (crr *CompositeBinlogRecordReader) iterateNextBatch() error {
return err
}
crr.rrs = make([]array.RecordReader, len(crr.schema.Fields))
crr.brs = make([]*BinlogReader, len(crr.schema.Fields))
fieldNum := len(crr.schema.Fields)
for _, f := range crr.schema.StructArrayFields {
fieldNum += len(f.Fields)
}
crr.rrs = make([]array.RecordReader, fieldNum)
crr.brs = make([]*BinlogReader, fieldNum)
for _, b := range blobs {
reader, err := NewBinlogReader(b.Value)
@@ -110,14 +115,19 @@ func (crr *CompositeBinlogRecordReader) Next() (Record, error) {
}
composeRecord := func() (Record, error) {
recs := make([]arrow.Array, len(crr.schema.Fields))
fieldNum := len(crr.schema.Fields)
for _, f := range crr.schema.StructArrayFields {
fieldNum += len(f.Fields)
}
recs := make([]arrow.Array, fieldNum)
for i, f := range crr.schema.Fields {
if crr.rrs[i] != nil {
if ok := crr.rrs[i].Next(); !ok {
return nil, io.EOF
idx := 0
appendFieldRecord := func(f *schemapb.FieldSchema) error {
if crr.rrs[idx] != nil {
if ok := crr.rrs[idx].Next(); !ok {
return io.EOF
}
recs[i] = crr.rrs[i].Record().Column(0)
recs[idx] = crr.rrs[idx].Record().Column(0)
} else {
// If the field is not in the current batch, fill with null array
// Note that we're intentionally not filling default value here, because the
@@ -125,9 +135,24 @@ func (crr *CompositeBinlogRecordReader) Next() (Record, error) {
numRows := int(crr.rrs[0].Record().NumRows())
arr, err := GenerateEmptyArrayFromSchema(f, numRows)
if err != nil {
return err
}
recs[idx] = arr
}
idx++
return nil
}
for _, f := range crr.schema.Fields {
if err := appendFieldRecord(f); err != nil {
return nil, err
}
}
for _, f := range crr.schema.StructArrayFields {
for _, sf := range f.Fields {
if err := appendFieldRecord(sf); err != nil {
return nil, err
}
recs[i] = arr
}
}
return &compositeRecord{
@@ -229,9 +254,17 @@ func MakeBlobsReader(blobs []*Blob) ChunkedBlobsReader {
}
func newCompositeBinlogRecordReader(schema *schemapb.CollectionSchema, blobsReader ChunkedBlobsReader) (*CompositeBinlogRecordReader, error) {
idx := 0
index := make(map[FieldID]int16)
for i, f := range schema.Fields {
index[f.FieldID] = int16(i)
for _, f := range schema.Fields {
index[f.FieldID] = int16(idx)
idx++
}
for _, f := range schema.StructArrayFields {
for _, sf := range f.Fields {
index[sf.FieldID] = int16(idx)
idx++
}
}
return &CompositeBinlogRecordReader{
schema: schema,
@@ -240,7 +273,16 @@ func newCompositeBinlogRecordReader(schema *schemapb.CollectionSchema, blobsRead
}, nil
}
func ValueDeserializer(r Record, v []*Value, fieldSchema []*schemapb.FieldSchema, shouldCopy bool) error {
func ValueDeserializerWithSelectedFields(r Record, v []*Value, fieldSchema []*schemapb.FieldSchema, shouldCopy bool) error {
return valueDeserializer(r, v, fieldSchema, shouldCopy)
}
func ValueDeserializerWithSchema(r Record, v []*Value, schema *schemapb.CollectionSchema, shouldCopy bool) error {
allFields := typeutil.GetAllFieldSchemas(schema)
return valueDeserializer(r, v, allFields, shouldCopy)
}
func valueDeserializer(r Record, v []*Value, fieldSchema []*schemapb.FieldSchema, shouldCopy bool) error {
pkField := func() *schemapb.FieldSchema {
for _, field := range fieldSchema {
if field.GetIsPrimaryKey() {
@@ -307,7 +349,7 @@ func NewBinlogDeserializeReader(schema *schemapb.CollectionSchema, blobsReader C
}
return NewDeserializeReader(reader, func(r Record, v []*Value) error {
return ValueDeserializer(r, v, schema.Fields, shouldCopy)
return ValueDeserializerWithSchema(r, v, schema, shouldCopy)
}), nil
}
@@ -415,25 +457,44 @@ func (bsw *BinlogStreamWriter) writeBinlogHeaders(w io.Writer) error {
return nil
}
func newBinlogWriter(collectionID, partitionID, segmentID UniqueID,
field *schemapb.FieldSchema,
) *BinlogStreamWriter {
return &BinlogStreamWriter{
collectionID: collectionID,
partitionID: partitionID,
segmentID: segmentID,
fieldSchema: field,
}
}
func NewBinlogStreamWriters(collectionID, partitionID, segmentID UniqueID,
schema []*schemapb.FieldSchema,
schema *schemapb.CollectionSchema,
) map[FieldID]*BinlogStreamWriter {
bws := make(map[FieldID]*BinlogStreamWriter, len(schema))
for _, f := range schema {
bws[f.FieldID] = &BinlogStreamWriter{
collectionID: collectionID,
partitionID: partitionID,
segmentID: segmentID,
fieldSchema: f,
bws := make(map[FieldID]*BinlogStreamWriter)
for _, f := range schema.Fields {
bws[f.FieldID] = newBinlogWriter(collectionID, partitionID, segmentID, f)
}
for _, structField := range schema.StructArrayFields {
for _, subField := range structField.Fields {
bws[subField.FieldID] = newBinlogWriter(collectionID, partitionID, segmentID, subField)
}
}
return bws
}
func ValueSerializer(v []*Value, fieldSchema []*schemapb.FieldSchema) (Record, error) {
builders := make(map[FieldID]array.Builder, len(fieldSchema))
types := make(map[FieldID]schemapb.DataType, len(fieldSchema))
for _, f := range fieldSchema {
func ValueSerializer(v []*Value, schema *schemapb.CollectionSchema) (Record, error) {
allFieldsSchema := schema.Fields
for _, structField := range schema.StructArrayFields {
allFieldsSchema = append(allFieldsSchema, structField.Fields...)
}
builders := make(map[FieldID]array.Builder, len(allFieldsSchema))
types := make(map[FieldID]schemapb.DataType, len(allFieldsSchema))
for _, f := range allFieldsSchema {
dim, _ := typeutil.GetDim(f)
builders[f.FieldID] = array.NewBuilder(memory.DefaultAllocator, serdeMap[f.DataType].arrowType(int(dim)))
types[f.FieldID] = f.DataType
@@ -453,10 +514,10 @@ func ValueSerializer(v []*Value, fieldSchema []*schemapb.FieldSchema) (Record, e
}
}
}
arrays := make([]arrow.Array, len(fieldSchema))
fields := make([]arrow.Field, len(fieldSchema))
field2Col := make(map[FieldID]int, len(fieldSchema))
for i, field := range fieldSchema {
arrays := make([]arrow.Array, len(allFieldsSchema))
fields := make([]arrow.Field, len(allFieldsSchema))
field2Col := make(map[FieldID]int, len(allFieldsSchema))
for i, field := range allFieldsSchema {
builder := builders[field.FieldID]
arrays[i] = builder.NewArray()
builder.Release()
@@ -569,7 +630,7 @@ func (c *CompositeBinlogRecordWriter) Write(r Record) error {
func (c *CompositeBinlogRecordWriter) initWriters() error {
if c.rw == nil {
c.fieldWriters = NewBinlogStreamWriters(c.collectionID, c.partitionID, c.segmentID, c.schema.Fields)
c.fieldWriters = NewBinlogStreamWriters(c.collectionID, c.partitionID, c.segmentID, c.schema)
rws := make(map[FieldID]RecordWriter, len(c.fieldWriters))
for fid, w := range c.fieldWriters {
rw, err := w.GetRecordWriter()
@@ -825,7 +886,7 @@ func NewBinlogValueWriter(rw BinlogRecordWriter, batchSize int,
return &BinlogValueWriter{
BinlogRecordWriter: rw,
SerializeWriter: NewSerializeRecordWriter(rw, func(v []*Value) (Record, error) {
return ValueSerializer(v, rw.Schema().Fields)
return ValueSerializer(v, rw.Schema())
}, batchSize),
}
}
@@ -856,7 +917,7 @@ func NewBinlogSerializeWriter(schema *schemapb.CollectionSchema, partitionID, se
return &BinlogSerializeWriter{
RecordWriter: compositeRecordWriter,
SerializeWriter: NewSerializeRecordWriter[*Value](compositeRecordWriter, func(v []*Value) (Record, error) {
return ValueSerializer(v, schema.Fields)
return ValueSerializer(v, schema)
}, batchSize),
}, nil
}
+5 -5
View File
@@ -204,7 +204,7 @@ func TestBinlogValueWriter(t *testing.T) {
schema := generateTestSchema()
// Copy write the generated data
writers := NewBinlogStreamWriters(0, 0, 0, schema.Fields)
writers := NewBinlogStreamWriters(0, 0, 0, schema)
writer, err := NewBinlogSerializeWriter(schema, 0, 0, writers, 7)
assert.NoError(t, err)
@@ -269,7 +269,7 @@ func TestSize(t *testing.T) {
},
}}
writers := NewBinlogStreamWriters(0, 0, 0, schema.Fields)
writers := NewBinlogStreamWriters(0, 0, 0, schema)
writer, err := NewBinlogSerializeWriter(schema, 0, 0, writers, 7)
assert.NoError(t, err)
@@ -307,7 +307,7 @@ func TestSize(t *testing.T) {
},
}}
writers := NewBinlogStreamWriters(0, 0, 0, schema.Fields)
writers := NewBinlogStreamWriters(0, 0, 0, schema)
writer, err := NewBinlogSerializeWriter(schema, 0, 0, writers, 7)
assert.NoError(t, err)
@@ -394,7 +394,7 @@ func BenchmarkSerializeWriter(b *testing.B) {
for _, s := range sizes {
b.Run(fmt.Sprintf("batch size=%d", s), func(b *testing.B) {
for i := 0; i < b.N; i++ {
writers := NewBinlogStreamWriters(0, 0, 0, schema.Fields)
writers := NewBinlogStreamWriters(0, 0, 0, schema)
writer, err := NewBinlogSerializeWriter(schema, 0, 0, writers, s)
assert.NoError(b, err)
for _, v := range values {
@@ -411,7 +411,7 @@ func TestNull(t *testing.T) {
t.Run("test null", func(t *testing.T) {
schema := generateTestSchema()
// Copy write the generated data
writers := NewBinlogStreamWriters(0, 0, 0, schema.Fields)
writers := NewBinlogStreamWriters(0, 0, 0, schema)
writer, err := NewBinlogSerializeWriter(schema, 0, 0, writers, 1024)
assert.NoError(t, err)
+5 -6
View File
@@ -109,7 +109,7 @@ func (pr *packedRecordReader) Close() error {
func newPackedRecordReader(paths [][]string, schema *schemapb.CollectionSchema, bufferSize int64, storageConfig *indexpb.StorageConfig,
) (*packedRecordReader, error) {
arrowSchema, err := ConvertToArrowSchema(schema.Fields)
arrowSchema, err := ConvertToArrowSchema(schema)
if err != nil {
return nil, merr.WrapErrParameterInvalid("convert collection schema [%s] to arrow schema error: %s", schema.Name, err.Error())
}
@@ -133,9 +133,8 @@ func NewPackedDeserializeReader(paths [][]string, schema *schemapb.CollectionSch
if err != nil {
return nil, err
}
return NewDeserializeReader(reader, func(r Record, v []*Value) error {
return ValueDeserializer(r, v, schema.Fields, shouldCopy)
return ValueDeserializerWithSchema(r, v, schema, shouldCopy)
}), nil
}
@@ -212,7 +211,7 @@ func (pw *packedRecordWriter) Close() error {
}
func NewPackedRecordWriter(bucketName string, paths []string, schema *schemapb.CollectionSchema, bufferSize int64, multiPartUploadSize int64, columnGroups []storagecommon.ColumnGroup, storageConfig *indexpb.StorageConfig) (*packedRecordWriter, error) {
arrowSchema, err := ConvertToArrowSchema(schema.Fields)
arrowSchema, err := ConvertToArrowSchema(schema)
if err != nil {
return nil, merr.WrapErrServiceInternal(
fmt.Sprintf("can not convert collection schema %s to arrow schema: %s", schema.Name, err.Error()))
@@ -267,7 +266,7 @@ func NewPackedSerializeWriter(bucketName string, paths []string, schema *schemap
fmt.Sprintf("can not new packed record writer %s", err.Error()))
}
return NewSerializeRecordWriter(packedRecordWriter, func(v []*Value) (Record, error) {
return ValueSerializer(v, schema.Fields)
return ValueSerializer(v, schema)
}, batchSize), nil
}
@@ -538,7 +537,7 @@ func newPackedBinlogRecordWriter(collectionID, partitionID, segmentID UniqueID,
blobsWriter ChunkedBlobsWriter, allocator allocator.Interface, maxRowNum int64, bufferSize, multiPartUploadSize int64, columnGroups []storagecommon.ColumnGroup,
storageConfig *indexpb.StorageConfig,
) (*PackedBinlogRecordWriter, error) {
arrowSchema, err := ConvertToArrowSchema(schema.Fields)
arrowSchema, err := ConvertToArrowSchema(schema)
if err != nil {
return nil, merr.WrapErrParameterInvalid("convert collection schema [%s] to arrow schema error: %s", schema.Name, err.Error())
}
+1
View File
@@ -101,6 +101,7 @@ func TestSerDe(t *testing.T) {
{"test bfloat16 vector null", args{dt: schemapb.DataType_BFloat16Vector, v: nil}, nil, true},
{"test bfloat16 vector negative", args{dt: schemapb.DataType_BFloat16Vector, v: -1}, nil, false},
{"test int8 vector", args{dt: schemapb.DataType_Int8Vector, v: []int8{10}}, []int8{10}, true},
{"test array of vector", args{dt: schemapb.DataType_ArrayOfVector, v: "{}"}, nil, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+73 -10
View File
@@ -382,6 +382,10 @@ func RowBasedInsertMsgToInsertData(msg *msgstream.InsertMsg, collSchema *schemap
Infos: nil,
}
if len(collSchema.StructArrayFields) > 0 {
return nil, errors.New("struct fields are not implemented in row based insert data")
}
for _, field := range collSchema.Fields {
if skipFunction && IsBM25FunctionOutputField(field, collSchema) {
continue
@@ -520,6 +524,10 @@ func RowBasedInsertMsgToInsertData(msg *msgstream.InsertMsg, collSchema *schemap
func ColumnBasedInsertMsgToInsertData(msg *msgstream.InsertMsg, collSchema *schemapb.CollectionSchema) (idata *InsertData, err error) {
srcFields := make(map[FieldID]*schemapb.FieldData)
for _, field := range msg.FieldsData {
if _, ok := field.Field.(*schemapb.FieldData_StructArrays); ok {
// unreachable
panic("struct is not flattened")
}
srcFields[field.FieldId] = field
}
@@ -527,18 +535,11 @@ func ColumnBasedInsertMsgToInsertData(msg *msgstream.InsertMsg, collSchema *sche
Data: make(map[FieldID]FieldData),
}
length := 0
for _, field := range collSchema.Fields {
if IsBM25FunctionOutputField(field, collSchema) {
continue
}
getFieldData := func(field *schemapb.FieldSchema) (FieldData, error) {
srcField, ok := srcFields[field.GetFieldID()]
if !ok && field.GetFieldID() >= common.StartOfUserFieldID {
err := fillMissingFields(collSchema, idata)
if err != nil {
return nil, err
}
continue
return nil, err
}
var fieldData FieldData
switch field.DataType {
@@ -726,18 +727,63 @@ func ColumnBasedInsertMsgToInsertData(msg *msgstream.InsertMsg, collSchema *sche
Nullable: field.GetNullable(),
}
case schemapb.DataType_ArrayOfVector:
vectorArray := srcField.GetVectors().GetVectorArray()
fieldData = &VectorArrayFieldData{
ElementType: field.GetElementType(),
Data: vectorArray.GetData(),
Dim: vectorArray.GetDim(),
}
default:
return nil, merr.WrapErrServiceInternal("data type not handled", field.GetDataType().String())
}
return fieldData, nil
}
handleFieldData := func(field *schemapb.FieldSchema) (FieldData, error) {
if IsBM25FunctionOutputField(field, collSchema) {
return nil, nil
}
fieldData, err := getFieldData(field)
if err != nil || fieldData == nil {
return nil, err
}
if length == 0 {
length = fieldData.RowNum()
}
if fieldData.RowNum() != length {
return nil, merr.WrapErrServiceInternal("row num not match", fmt.Sprintf("field %s row num not match %d, other column %d", field.GetName(), fieldData.RowNum(), length))
}
idata.Data[field.FieldID] = fieldData
return fieldData, nil
}
for _, field := range collSchema.Fields {
fieldData, err := handleFieldData(field)
if err != nil {
return nil, err
}
if fieldData != nil {
idata.Data[field.FieldID] = fieldData
}
}
for _, structField := range collSchema.GetStructArrayFields() {
for _, field := range structField.GetFields() {
fieldData, err := handleFieldData(field)
if err != nil {
return nil, err
}
if fieldData != nil {
idata.Data[field.FieldID] = fieldData
}
}
}
idata.Infos = []BlobInfo{
@@ -1339,6 +1385,23 @@ func TransferInsertDataToInsertRecord(insertData *InsertData) (*segcorepb.Insert
},
},
}
case *VectorArrayFieldData:
fieldData = &schemapb.FieldData{
Type: schemapb.DataType_ArrayOfVector,
FieldId: fieldID,
Field: &schemapb.FieldData_Vectors{
Vectors: &schemapb.VectorField{
Data: &schemapb.VectorField_VectorArray{
VectorArray: &schemapb.VectorArray{
Data: rawData.Data,
ElementType: rawData.ElementType,
Dim: rawData.Dim,
},
},
Dim: rawData.Dim,
},
},
}
default:
return insertRecord, errors.New("unsupported data type when transter storage.InsertData to internalpb.InsertRecord")
}
+1 -1
View File
@@ -126,7 +126,7 @@ func (r *reader) init(paths []string, tsStart, tsEnd uint64, storageConfig *inde
}
r.dr = storage.NewDeserializeReader(rr, func(record storage.Record, v []*storage.Value) error {
return storage.ValueDeserializer(record, v, r.schema.Fields, true)
return storage.ValueDeserializerWithSchema(record, v, r.schema, true)
})
if len(paths) < 2 {
+11 -3
View File
@@ -392,7 +392,7 @@ func CollectionLevelResourceGroups(kvs []*commonpb.KeyValuePair) ([]string, erro
// GetCollectionLoadFields returns the load field ids according to the type params.
func GetCollectionLoadFields(schema *schemapb.CollectionSchema, skipDynamicField bool) []int64 {
fields := lo.FilterMap(schema.GetFields(), func(field *schemapb.FieldSchema, _ int) (int64, bool) {
filter := func(field *schemapb.FieldSchema, _ int) (int64, bool) {
// skip system field
if IsSystemField(field.GetFieldID()) {
return field.GetFieldID(), false
@@ -409,9 +409,17 @@ func GetCollectionLoadFields(schema *schemapb.CollectionSchema, skipDynamicField
return field.GetFieldID(), true
}
return field.GetFieldID(), v
})
}
fields := lo.FilterMap(schema.GetFields(), filter)
fieldsNum := len(schema.GetFields())
for _, structField := range schema.GetStructArrayFields() {
fields = append(fields, lo.FilterMap(structField.GetFields(), filter)...)
fieldsNum += len(structField.GetFields())
}
// empty fields list means all fields will be loaded
if len(fields) == len(schema.GetFields())-int(SystemFieldsNum) {
if len(fields) == fieldsNum-int(SystemFieldsNum) {
return []int64{}
}
return fields
+3
View File
@@ -9,6 +9,9 @@ import (
type KeyValuePairs []*commonpb.KeyValuePair
func (pairs KeyValuePairs) Clone() KeyValuePairs {
if pairs == nil {
return nil
}
clone := make(KeyValuePairs, 0, len(pairs))
for _, pair := range pairs {
clone = append(clone, &commonpb.KeyValuePair{
+9
View File
@@ -307,6 +307,11 @@ func getNumRowsOfScalarField(datas interface{}) uint64 {
return uint64(realTypeDatas.Len())
}
func getNumRowsOfArrayVectorField(datas interface{}) uint64 {
realTypeDatas := reflect.ValueOf(datas)
return uint64(realTypeDatas.Len())
}
func GetNumRowsOfFloatVectorField(fDatas []float32, dim int64) (uint64, error) {
if dim <= 0 {
return 0, fmt.Errorf("dim(%d) should be greater than 0", dim)
@@ -422,6 +427,8 @@ func GetNumRowOfFieldDataWithSchema(fieldData *schemapb.FieldData, helper *typeu
if err != nil {
return 0, err
}
case schemapb.DataType_ArrayOfVector:
fieldNumRows = getNumRowsOfArrayVectorField(fieldData.GetVectors().GetVectorArray().GetData())
default:
return 0, fmt.Errorf("%s is not supported now", fieldSchema.GetDataType())
}
@@ -491,6 +498,8 @@ func GetNumRowOfFieldData(fieldData *schemapb.FieldData) (uint64, error) {
if err != nil {
return 0, err
}
case *schemapb.VectorField_VectorArray:
fieldNumRows = getNumRowsOfArrayVectorField(vectorField.GetVectorArray().Data)
default:
return 0, fmt.Errorf("%s is not supported now", vectorFieldType)
}
+23
View File
@@ -625,6 +625,7 @@ func (s *NumRowsWithSchemaSuite) SetupSuite() {
{FieldID: 113, Name: "bfloat16_vector", DataType: schemapb.DataType_BFloat16Vector, TypeParams: []*commonpb.KeyValuePair{{Key: "dim", Value: "8"}}},
{FieldID: 114, Name: "sparse_vector", DataType: schemapb.DataType_SparseFloatVector, TypeParams: []*commonpb.KeyValuePair{{Key: "dim", Value: "8"}}},
{FieldID: 115, Name: "int8_vector", DataType: schemapb.DataType_Int8Vector, TypeParams: []*commonpb.KeyValuePair{{Key: "dim", Value: "8"}}},
{FieldID: 116, Name: "array_vector_float16", DataType: schemapb.DataType_ArrayOfVector, ElementType: schemapb.DataType_Float16Vector, TypeParams: []*commonpb.KeyValuePair{{Key: "dim", Value: "4"}}},
{FieldID: 999, Name: "unknown", DataType: schemapb.DataType_None},
},
}
@@ -819,6 +820,28 @@ func (s *NumRowsWithSchemaSuite) TestNormalCases() {
},
expect: 7,
},
{
tag: "array_vector_float16",
input: &schemapb.FieldData{
FieldName: "array_vector_float16",
Field: &schemapb.FieldData_Vectors{
Vectors: &schemapb.VectorField{
Dim: 4,
Data: &schemapb.VectorField_VectorArray{VectorArray: &schemapb.VectorArray{Data: []*schemapb.VectorField{
{
Dim: 4,
Data: &schemapb.VectorField_Float16Vector{Float16Vector: make([]byte, 4*2*5)}, // 4 vectors
},
{
Dim: 4,
Data: &schemapb.VectorField_Float16Vector{Float16Vector: make([]byte, 4*2*10)}, // 10 vectors
},
}}},
},
},
},
expect: 2,
},
}
for _, tc := range cases {
s.Run(tc.tag, func() {
+22 -14
View File
@@ -439,21 +439,29 @@ type Field struct {
IsFunctionOutput bool `json:"is_function_output,omitempty"`
}
type StructArrayField struct {
FieldID string `json:"field_id,omitempty,string"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Fields []*Field `json:"fields,omitempty"`
}
type Collection struct {
CollectionID string `json:"collection_id,omitempty"`
CollectionName string `json:"collection_name,omitempty"`
CreatedTime string `json:"created_time,omitempty"`
ShardsNum int `json:"shards_num,omitempty"`
ConsistencyLevel string `json:"consistency_level,omitempty"`
Aliases []string `json:"aliases,omitempty"`
Properties map[string]string `json:"properties,omitempty"`
DBName string `json:"db_name,omitempty"`
NumPartitions int `json:"num_partitions,omitempty,string"`
VirtualChannelNames []string `json:"virtual_channel_names,omitempty"`
PhysicalChannelNames []string `json:"physical_channel_names,omitempty"`
PartitionInfos []*PartitionInfo `json:"partition_infos,omitempty"`
EnableDynamicField bool `json:"enable_dynamic_field,omitempty"`
Fields []*Field `json:"fields,omitempty"`
CollectionID string `json:"collection_id,omitempty"`
CollectionName string `json:"collection_name,omitempty"`
CreatedTime string `json:"created_time,omitempty"`
ShardsNum int `json:"shards_num,omitempty"`
ConsistencyLevel string `json:"consistency_level,omitempty"`
Aliases []string `json:"aliases,omitempty"`
Properties map[string]string `json:"properties,omitempty"`
DBName string `json:"db_name,omitempty"`
NumPartitions int `json:"num_partitions,omitempty,string"`
VirtualChannelNames []string `json:"virtual_channel_names,omitempty"`
PhysicalChannelNames []string `json:"physical_channel_names,omitempty"`
PartitionInfos []*PartitionInfo `json:"partition_infos,omitempty"`
EnableDynamicField bool `json:"enable_dynamic_field,omitempty"`
Fields []*Field `json:"fields,omitempty"`
StructArrayFields []*StructArrayField `json:"struct_array_fields,omitempty"`
}
type Database struct {
+20 -3
View File
@@ -143,9 +143,9 @@ func NewPartitionInfos(partitions *milvuspb.ShowPartitionsResponse) []*Partition
return partitionInfos
}
func NewFields(fields *schemapb.CollectionSchema) []*Field {
fieldInfos := make([]*Field, len(fields.Fields))
for i, f := range fields.Fields {
func newFields(fields []*schemapb.FieldSchema) []*Field {
fieldInfos := make([]*Field, len(fields))
for i, f := range fields {
fieldInfos[i] = &Field{
FieldID: strconv.FormatInt(f.FieldID, 10),
Name: f.Name,
@@ -167,6 +167,23 @@ func NewFields(fields *schemapb.CollectionSchema) []*Field {
return fieldInfos
}
func NewFields(fields *schemapb.CollectionSchema) []*Field {
return newFields(fields.GetFields())
}
func NewStructArrayFields(fields *schemapb.CollectionSchema) []*StructArrayField {
fieldInfos := make([]*StructArrayField, len(fields.StructArrayFields))
for i, f := range fields.StructArrayFields {
fieldInfos[i] = &StructArrayField{
FieldID: strconv.FormatInt(f.FieldID, 10),
Name: f.Name,
Description: f.Description,
Fields: newFields(f.Fields),
}
}
return fieldInfos
}
func NewDatabase(resp *milvuspb.DescribeDatabaseResponse) *Database {
return &Database{
DBName: resp.GetDbName(),
+78 -7
View File
@@ -33,7 +33,7 @@ import (
"github.com/milvus-io/milvus/pkg/v2/util/typeutil"
)
const elemCountOfArray = 10
const ElemCountOfArray = 10
// generate data
func GenerateBoolArray(numRows int) []bool {
@@ -155,7 +155,7 @@ func GenerateArrayOfBoolArray(numRows int) []*schemapb.ScalarField {
ret = append(ret, &schemapb.ScalarField{
Data: &schemapb.ScalarField_BoolData{
BoolData: &schemapb.BoolArray{
Data: GenerateBoolArray(elemCountOfArray),
Data: GenerateBoolArray(ElemCountOfArray),
},
},
})
@@ -169,7 +169,7 @@ func GenerateArrayOfIntArray(numRows int) []*schemapb.ScalarField {
ret = append(ret, &schemapb.ScalarField{
Data: &schemapb.ScalarField_IntData{
IntData: &schemapb.IntArray{
Data: GenerateInt32Array(elemCountOfArray),
Data: GenerateInt32Array(ElemCountOfArray),
},
},
})
@@ -183,7 +183,7 @@ func GenerateArrayOfLongArray(numRows int) []*schemapb.ScalarField {
ret = append(ret, &schemapb.ScalarField{
Data: &schemapb.ScalarField_LongData{
LongData: &schemapb.LongArray{
Data: GenerateInt64Array(elemCountOfArray),
Data: GenerateInt64Array(ElemCountOfArray),
},
},
})
@@ -197,7 +197,7 @@ func GenerateArrayOfFloatArray(numRows int) []*schemapb.ScalarField {
ret = append(ret, &schemapb.ScalarField{
Data: &schemapb.ScalarField_FloatData{
FloatData: &schemapb.FloatArray{
Data: GenerateFloat32Array(elemCountOfArray),
Data: GenerateFloat32Array(ElemCountOfArray),
},
},
})
@@ -211,7 +211,22 @@ func GenerateArrayOfDoubleArray(numRows int) []*schemapb.ScalarField {
ret = append(ret, &schemapb.ScalarField{
Data: &schemapb.ScalarField_DoubleData{
DoubleData: &schemapb.DoubleArray{
Data: GenerateFloat64Array(elemCountOfArray),
Data: GenerateFloat64Array(ElemCountOfArray),
},
},
})
}
return ret
}
func GenerateArrayOfFloatVectorArray(numRows int, dim int) []*schemapb.VectorField {
ret := make([]*schemapb.VectorField, 0, numRows)
for i := 0; i < numRows; i++ {
ret = append(ret, &schemapb.VectorField{
Dim: int64(dim),
Data: &schemapb.VectorField_FloatVector{
FloatVector: &schemapb.FloatArray{
Data: GenerateFloatVectors(ElemCountOfArray, dim),
},
},
})
@@ -225,7 +240,7 @@ func GenerateArrayOfStringArray(numRows int) []*schemapb.ScalarField {
ret = append(ret, &schemapb.ScalarField{
Data: &schemapb.ScalarField_StringData{
StringData: &schemapb.StringArray{
Data: GenerateStringArray(elemCountOfArray),
Data: GenerateStringArray(ElemCountOfArray),
},
},
})
@@ -708,6 +723,25 @@ func NewArrayFieldData(fieldName string, numRows int) *schemapb.FieldData {
}
}
func NewVectorArrayFieldData(fieldName string, numRows, dim int) *schemapb.FieldData {
return &schemapb.FieldData{
Type: schemapb.DataType_ArrayOfVector,
FieldName: fieldName,
Field: &schemapb.FieldData_Vectors{
Vectors: &schemapb.VectorField{
Dim: int64(dim),
Data: &schemapb.VectorField_VectorArray{
VectorArray: &schemapb.VectorArray{
Data: GenerateArrayOfFloatVectorArray(numRows, dim),
ElementType: schemapb.DataType_FloatVector,
Dim: int64(dim),
},
},
},
},
}
}
func NewArrayFieldDataWithValue(fieldName string, fieldValue interface{}) *schemapb.FieldData {
return &schemapb.FieldData{
Type: schemapb.DataType_Array,
@@ -1002,3 +1036,40 @@ func GenerateVectorFieldDataWithValue(dType schemapb.DataType, fieldName string,
fieldData.FieldId = fieldID
return fieldData
}
// Generate number of fields in StructField FieldDatas where each field is an ArrayType of something
func GenerateArrayOfStructArray(schema *schemapb.StructArrayFieldSchema, numRows int, dim int) []*schemapb.FieldData {
ret := make([]*schemapb.FieldData, 0, numRows)
for _, field := range schema.Fields {
if field.DataType != schemapb.DataType_Array && field.DataType != schemapb.DataType_ArrayOfVector {
panic("Only Array or ArrayOfVector type is supported for StructField")
}
switch field.GetElementType() {
case schemapb.DataType_Int8, schemapb.DataType_Int16, schemapb.DataType_Int32:
fieldData := NewArrayFieldData(field.Name, numRows)
fieldData.FieldId = field.FieldID
ret = append(ret, fieldData)
case schemapb.DataType_FloatVector:
fieldData := NewVectorArrayFieldData(field.Name, numRows, dim)
fieldData.FieldId = field.FieldID
ret = append(ret, fieldData)
default:
panic(fmt.Sprintf("unimplemented data type: %s", field.ElementType))
}
}
return ret
}
func GenerateStructFieldData(schema *schemapb.StructArrayFieldSchema, fieldName string, numRow int, dim int) *schemapb.FieldData {
fieldData := &schemapb.FieldData{
Type: schemapb.DataType_ArrayOfStruct,
FieldName: fieldName,
Field: &schemapb.FieldData_StructArrays{
StructArrays: &schemapb.StructArrayField{
Fields: GenerateArrayOfStructArray(schema, numRow, dim),
},
},
}
return fieldData
}
+146 -37
View File
@@ -213,6 +213,27 @@ func CalcColumnSize(column *schemapb.FieldData) int {
return res
}
func calcVectorSize(column *schemapb.VectorField, vectorType schemapb.DataType) int {
res := 0
switch vectorType {
case schemapb.DataType_BinaryVector:
res += len(column.GetBinaryVector())
case schemapb.DataType_FloatVector:
res += len(column.GetFloatVector().Data) * 4
case schemapb.DataType_Float16Vector:
res += len(column.GetFloat16Vector()) * 2
case schemapb.DataType_BFloat16Vector:
res += len(column.GetBfloat16Vector()) * 2
case schemapb.DataType_SparseFloatVector:
panic("unimplemented")
case schemapb.DataType_Int8Vector:
res += len(column.GetInt8Vector())
default:
panic("Unknown data type:" + vectorType.String())
}
return res
}
func EstimateEntitySize(fieldsData []*schemapb.FieldData, rowOffset int) (int, error) {
res := 0
for _, fs := range fieldsData {
@@ -259,6 +280,12 @@ func EstimateEntitySize(fieldsData []*schemapb.FieldData, rowOffset int) (int, e
res += len(vec.Contents[rowOffset])
case schemapb.DataType_Int8Vector:
res += int(fs.GetVectors().GetDim())
case schemapb.DataType_ArrayOfVector:
arrayVector := fs.GetVectors().GetVectorArray()
if rowOffset >= len(arrayVector.GetData()) {
return 0, errors.New("offset out range of field datas")
}
res += calcVectorSize(arrayVector.GetData()[rowOffset], arrayVector.GetElementType())
default:
panic("Unknown data type:" + fs.GetType().String())
}
@@ -275,6 +302,8 @@ type SchemaHelper struct {
partitionKeyOffset int
clusteringKeyOffset int
dynamicFieldOffset int
// include sub fields in StructArrayField
allFields []*schemapb.FieldSchema
}
// CreateSchemaHelper returns a new SchemaHelper object
@@ -282,8 +311,16 @@ func CreateSchemaHelper(schema *schemapb.CollectionSchema) (*SchemaHelper, error
if schema == nil {
return nil, errors.New("schema is nil")
}
allFields := make([]*schemapb.FieldSchema, 0, len(schema.Fields)+5)
allFields = append(allFields, schema.Fields...)
for _, structField := range schema.GetStructArrayFields() {
allFields = append(allFields, structField.GetFields()...)
}
schemaHelper := SchemaHelper{
schema: schema,
allFields: allFields,
nameOffset: make(map[string]int),
idOffset: make(map[int64]int),
primaryKeyOffset: -1,
@@ -291,7 +328,7 @@ func CreateSchemaHelper(schema *schemapb.CollectionSchema) (*SchemaHelper, error
clusteringKeyOffset: -1,
dynamicFieldOffset: -1,
}
for offset, field := range schema.Fields {
for offset, field := range allFields {
if _, ok := schemaHelper.nameOffset[field.Name]; ok {
return nil, fmt.Errorf("duplicated fieldName: %s", field.Name)
}
@@ -336,7 +373,7 @@ func (helper *SchemaHelper) GetPrimaryKeyField() (*schemapb.FieldSchema, error)
if helper.primaryKeyOffset == -1 {
return nil, errors.New("failed to get primary key field: no primary in schema")
}
return helper.schema.Fields[helper.primaryKeyOffset], nil
return helper.allFields[helper.primaryKeyOffset], nil
}
// GetPartitionKeyField returns the schema of the partition key
@@ -344,7 +381,7 @@ func (helper *SchemaHelper) GetPartitionKeyField() (*schemapb.FieldSchema, error
if helper.partitionKeyOffset == -1 {
return nil, errors.New("failed to get partition key field: no partition key in schema")
}
return helper.schema.Fields[helper.partitionKeyOffset], nil
return helper.allFields[helper.partitionKeyOffset], nil
}
// GetClusteringKeyField returns the schema of the clustering key.
@@ -353,7 +390,7 @@ func (helper *SchemaHelper) GetClusteringKeyField() (*schemapb.FieldSchema, erro
if helper.clusteringKeyOffset == -1 {
return nil, errors.New("failed to get clustering key field: not clustering key in schema")
}
return helper.schema.Fields[helper.clusteringKeyOffset], nil
return helper.allFields[helper.clusteringKeyOffset], nil
}
// GetDynamicField returns the field schema of dynamic field if exists.
@@ -362,7 +399,7 @@ func (helper *SchemaHelper) GetDynamicField() (*schemapb.FieldSchema, error) {
if helper.dynamicFieldOffset == -1 {
return nil, errors.New("failed to get dynamic field: no dynamic field in schema")
}
return helper.schema.Fields[helper.dynamicFieldOffset], nil
return helper.allFields[helper.dynamicFieldOffset], nil
}
// GetFieldFromName is used to find the schema by field name
@@ -371,7 +408,7 @@ func (helper *SchemaHelper) GetFieldFromName(fieldName string) (*schemapb.FieldS
if !ok {
return nil, fmt.Errorf("failed to get field schema by name: fieldName(%s) not found", fieldName)
}
return helper.schema.Fields[offset], nil
return helper.allFields[offset], nil
}
// GetFieldFromNameDefaultJSON is used to find the schema by field name, if not exist, use json field
@@ -380,10 +417,19 @@ func (helper *SchemaHelper) GetFieldFromNameDefaultJSON(fieldName string) (*sche
if !ok {
return helper.getDefaultJSONField(fieldName)
}
fieldSchema := helper.schema.Fields[offset]
fieldSchema := helper.allFields[offset]
return fieldSchema, nil
}
func (helper *SchemaHelper) GetStructArrayFieldFromName(fieldName string) *schemapb.StructArrayFieldSchema {
for _, field := range helper.schema.StructArrayFields {
if field.Name == fieldName {
return field
}
}
return nil
}
func (helper *SchemaHelper) IsFieldTextMatchEnabled(fieldId int64) bool {
sche, err := helper.GetFieldFromID(fieldId)
if err != nil {
@@ -409,7 +455,7 @@ func (helper *SchemaHelper) GetFieldFromID(fieldID int64) (*schemapb.FieldSchema
if !ok {
return nil, fmt.Errorf("fieldID(%d) not found", fieldID)
}
return helper.schema.Fields[offset], nil
return helper.allFields[offset], nil
}
// GetVectorDimFromID returns the dimension of specified field
@@ -509,7 +555,11 @@ func IsFixDimVectorType(dataType schemapb.DataType) bool {
// IsVectorType returns true if input is a vector type, otherwise false
func IsVectorType(dataType schemapb.DataType) bool {
return IsBinaryVectorType(dataType) || IsFloatVectorType(dataType) || IsIntVectorType(dataType)
return IsBinaryVectorType(dataType) || IsFloatVectorType(dataType) || IsIntVectorType(dataType) || IsVectorArrayType(dataType)
}
func IsVectorArrayType(dataType schemapb.DataType) bool {
return dataType == schemapb.DataType_ArrayOfVector
}
// IsIntegerType returns true if input is an integer type, otherwise false
@@ -576,7 +626,7 @@ func IsArrayContainStringElementType(dataType schemapb.DataType, elementType sch
}
func IsVariableDataType(dataType schemapb.DataType) bool {
return IsStringType(dataType) || IsArrayType(dataType) || IsJSONType(dataType)
return IsStringType(dataType) || IsArrayType(dataType) || IsJSONType(dataType) || IsVectorArrayType(dataType)
}
func IsPrimitiveType(dataType schemapb.DataType) bool {
@@ -922,6 +972,18 @@ func AppendFieldData(dst, src []*schemapb.FieldData, idx int64) (appendSize int6
}
/* #nosec G103 */
appendSize += int64(unsafe.Sizeof(srcVector.Int8Vector[idx*dim : (idx+1)*dim]))
case *schemapb.VectorField_VectorArray:
if dstVector.GetVectorArray() == nil {
dstVector.Data = &schemapb.VectorField_VectorArray{
VectorArray: &schemapb.VectorArray{
Data: []*schemapb.VectorField{srcVector.VectorArray.Data[idx]},
Dim: srcVector.VectorArray.Dim,
ElementType: srcVector.VectorArray.ElementType,
},
}
} else {
dstVector.GetVectorArray().Data = append(dstVector.GetVectorArray().Data, srcVector.VectorArray.Data[idx])
}
default:
log.Error("Not supported field type", zap.String("field type", fieldData.Type.String()))
}
@@ -994,22 +1056,17 @@ func DeleteFieldData(dst []*schemapb.FieldData) {
func MergeFieldData(dst []*schemapb.FieldData, src []*schemapb.FieldData) error {
fieldID2Data := make(map[int64]*schemapb.FieldData)
for _, data := range dst {
if _, ok := data.Field.(*schemapb.FieldData_StructArrays); ok {
panic("struct is not flattened")
}
fieldID2Data[data.FieldId] = data
}
for _, srcFieldData := range src {
switch fieldType := srcFieldData.Field.(type) {
case *schemapb.FieldData_Scalars:
if _, ok := fieldID2Data[srcFieldData.FieldId]; !ok {
scalarFieldData := &schemapb.FieldData{
Type: srcFieldData.Type,
FieldName: srcFieldData.FieldName,
FieldId: srcFieldData.FieldId,
Field: &schemapb.FieldData_Scalars{
Scalars: &schemapb.ScalarField{},
},
}
dst = append(dst, scalarFieldData)
fieldID2Data[srcFieldData.FieldId] = scalarFieldData
return errors.New("fields in src but not in dst: " + srcFieldData.Type.String())
}
fieldData := fieldID2Data[srcFieldData.FieldId]
fieldData.ValidData = append(fieldData.ValidData, srcFieldData.GetValidData()...)
@@ -1111,20 +1168,8 @@ func MergeFieldData(dst []*schemapb.FieldData, src []*schemapb.FieldData) error
return errors.New("unsupported data type: " + srcFieldData.Type.String())
}
case *schemapb.FieldData_Vectors:
dim := fieldType.Vectors.Dim
if _, ok := fieldID2Data[srcFieldData.FieldId]; !ok {
vectorFieldData := &schemapb.FieldData{
Type: srcFieldData.Type,
FieldName: srcFieldData.FieldName,
FieldId: srcFieldData.FieldId,
Field: &schemapb.FieldData_Vectors{
Vectors: &schemapb.VectorField{
Dim: dim,
},
},
}
dst = append(dst, vectorFieldData)
fieldID2Data[srcFieldData.FieldId] = vectorFieldData
return errors.New("fields in src but not in dst: " + srcFieldData.Type.String())
}
dstVector := fieldID2Data[srcFieldData.FieldId].GetVectors()
switch srcVector := fieldType.Vectors.Data.(type) {
@@ -1182,6 +1227,18 @@ func MergeFieldData(dst []*schemapb.FieldData, src []*schemapb.FieldData) error
dstInt8Vector := dstVector.Data.(*schemapb.VectorField_Int8Vector)
dstInt8Vector.Int8Vector = append(dstInt8Vector.Int8Vector, srcVector.Int8Vector...)
}
case *schemapb.VectorField_VectorArray:
if dstVector.GetVectorArray() == nil {
dstVector.Data = &schemapb.VectorField_VectorArray{
VectorArray: &schemapb.VectorArray{
Dim: srcVector.VectorArray.Dim,
ElementType: srcVector.VectorArray.ElementType,
Data: srcVector.VectorArray.Data,
},
}
} else {
dstVector.GetVectorArray().Data = append(dstVector.GetVectorArray().Data, srcVector.VectorArray.Data...)
}
default:
log.Error("Not supported data type", zap.String("data type", srcFieldData.Type.String()))
return errors.New("unsupported data type: " + srcFieldData.Type.String())
@@ -1192,6 +1249,27 @@ func MergeFieldData(dst []*schemapb.FieldData, src []*schemapb.FieldData) error
return nil
}
// GetTotalFieldsNum get total fields number
// We exclude StructArrayField itself as it does not contain data directly.
func GetTotalFieldsNum(schema *schemapb.CollectionSchema) int {
num := len(schema.GetFields())
for _, structArrayField := range schema.GetStructArrayFields() {
num += len(structArrayField.GetFields())
}
return num
}
// GetAllFieldSchemas returns every FieldSchema contained
// in CollectionSchema, including those under StructArrayField.
func GetAllFieldSchemas(schema *schemapb.CollectionSchema) []*schemapb.FieldSchema {
all := make([]*schemapb.FieldSchema, 0, len(schema.Fields)+5)
all = append(all, schema.Fields...)
for _, structField := range schema.GetStructArrayFields() {
all = append(all, structField.Fields...)
}
return all
}
// GetVectorFieldSchemas get vector fields schema from collection schema.
func GetVectorFieldSchemas(schema *schemapb.CollectionSchema) []*schemapb.FieldSchema {
ret := make([]*schemapb.FieldSchema, 0)
@@ -1200,6 +1278,13 @@ func GetVectorFieldSchemas(schema *schemapb.CollectionSchema) []*schemapb.FieldS
ret = append(ret, fieldSchema)
}
}
for _, structArrayField := range schema.GetStructArrayFields() {
for _, fieldSchema := range structArrayField.GetFields() {
if IsVectorType(fieldSchema.DataType) {
ret = append(ret, fieldSchema)
}
}
}
return ret
}
@@ -1296,15 +1381,39 @@ func GetPrimaryFieldData(datas []*schemapb.FieldData, primaryFieldSchema *schema
}
func GetField(schema *schemapb.CollectionSchema, fieldID int64) *schemapb.FieldSchema {
return lo.FindOrElse(schema.GetFields(), nil, func(field *schemapb.FieldSchema) bool {
preficate := func(field *schemapb.FieldSchema) bool {
return field.GetFieldID() == fieldID
})
}
if field := lo.FindOrElse(schema.GetFields(), nil, preficate); field != nil {
return field
}
for _, structField := range schema.GetStructArrayFields() {
if field := lo.FindOrElse(structField.Fields, nil, preficate); field != nil {
return field
}
}
return nil
}
func GetFieldByName(schema *schemapb.CollectionSchema, fieldName string) *schemapb.FieldSchema {
return lo.FindOrElse(schema.GetFields(), nil, func(field *schemapb.FieldSchema) bool {
preficate := func(field *schemapb.FieldSchema) bool {
return field.GetName() == fieldName
})
}
if field := lo.FindOrElse(schema.GetFields(), nil, preficate); field != nil {
return field
}
for _, structField := range schema.GetStructArrayFields() {
if field := lo.FindOrElse(structField.Fields, nil, preficate); field != nil {
return field
}
}
return nil
}
func IsPrimaryFieldDataExist(datas []*schemapb.FieldData, primaryFieldSchema *schemapb.FieldSchema) bool {
+44 -1
View File
@@ -247,6 +247,7 @@ func TestSchema(t *testing.T) {
assert.True(t, IsVectorType(schemapb.DataType_BFloat16Vector))
assert.True(t, IsVectorType(schemapb.DataType_SparseFloatVector))
assert.True(t, IsVectorType(schemapb.DataType_Int8Vector))
assert.True(t, IsVectorType(schemapb.DataType_ArrayOfVector))
assert.False(t, IsIntegerType(schemapb.DataType_Bool))
assert.True(t, IsIntegerType(schemapb.DataType_Int8))
@@ -292,6 +293,8 @@ func TestSchema(t *testing.T) {
assert.False(t, IsSparseFloatVectorType(schemapb.DataType_BFloat16Vector))
assert.True(t, IsSparseFloatVectorType(schemapb.DataType_SparseFloatVector))
assert.False(t, IsSparseFloatVectorType(schemapb.DataType_Int8Vector))
assert.True(t, IsVectorArrayType(schemapb.DataType_ArrayOfVector))
})
}
@@ -1028,6 +1031,23 @@ func genFieldData(fieldName string, fieldID int64, fieldType schemapb.DataType,
},
FieldId: fieldID,
}
case schemapb.DataType_ArrayOfVector:
fieldData = &schemapb.FieldData{
Type: schemapb.DataType_ArrayOfVector,
FieldName: fieldName,
Field: &schemapb.FieldData_Vectors{
Vectors: &schemapb.VectorField{
Dim: dim,
Data: &schemapb.VectorField_VectorArray{
VectorArray: &schemapb.VectorArray{
Data: fieldValue.([]*schemapb.VectorField),
ElementType: schemapb.DataType_FloatVector,
},
},
},
},
FieldId: fieldID,
}
default:
log.Error("not supported field type", zap.String("field type", fieldType.String()))
}
@@ -1050,6 +1070,7 @@ func TestAppendFieldData(t *testing.T) {
ArrayFieldName = "ArrayField"
SparseFloatVectorFieldName = "SparseFloatVectorField"
Int8VectorFieldName = "Int8VectorField"
VectorArrayFieldName = "VectorArrayField"
BoolFieldID = common.StartOfUserFieldID + 1
Int32FieldID = common.StartOfUserFieldID + 2
Int64FieldID = common.StartOfUserFieldID + 3
@@ -1062,6 +1083,7 @@ func TestAppendFieldData(t *testing.T) {
ArrayFieldID = common.StartOfUserFieldID + 10
SparseFloatVectorFieldID = common.StartOfUserFieldID + 11
Int8VectorFieldID = common.StartOfUserFieldID + 12
VectorArrayFieldID = common.StartOfUserFieldID + 13
)
BoolArray := []bool{true, false}
Int32Array := []int32{1, 2}
@@ -1104,8 +1126,26 @@ func TestAppendFieldData(t *testing.T) {
CreateSparseFloatRow([]uint32{60, 80, 230}, []float32{2.1, 2.2, 2.3}),
},
}
VectorArray := []*schemapb.VectorField{
{
Dim: Dim,
Data: &schemapb.VectorField_FloatVector{
FloatVector: &schemapb.FloatArray{
Data: FloatVector,
},
},
},
{
Dim: Dim,
Data: &schemapb.VectorField_FloatVector{
FloatVector: &schemapb.FloatArray{
Data: FloatVector,
},
},
},
}
result := make([]*schemapb.FieldData, 12)
result := make([]*schemapb.FieldData, 13)
var fieldDataArray1 []*schemapb.FieldData
fieldDataArray1 = append(fieldDataArray1, genFieldData(BoolFieldName, BoolFieldID, schemapb.DataType_Bool, BoolArray[0:1], 1))
fieldDataArray1 = append(fieldDataArray1, genFieldData(Int32FieldName, Int32FieldID, schemapb.DataType_Int32, Int32Array[0:1], 1))
@@ -1119,6 +1159,7 @@ func TestAppendFieldData(t *testing.T) {
fieldDataArray1 = append(fieldDataArray1, genFieldData(ArrayFieldName, ArrayFieldID, schemapb.DataType_Array, ArrayArray[0:1], 1))
fieldDataArray1 = append(fieldDataArray1, genFieldData(SparseFloatVectorFieldName, SparseFloatVectorFieldID, schemapb.DataType_SparseFloatVector, SparseFloatVector.Contents[0], SparseFloatVector.Dim))
fieldDataArray1 = append(fieldDataArray1, genFieldData(Int8VectorFieldName, Int8VectorFieldID, schemapb.DataType_Int8Vector, Int8Vector[0:Dim], Dim))
fieldDataArray1 = append(fieldDataArray1, genFieldData(VectorArrayFieldName, VectorArrayFieldID, schemapb.DataType_ArrayOfVector, VectorArray[0:1], Dim))
var fieldDataArray2 []*schemapb.FieldData
fieldDataArray2 = append(fieldDataArray2, genFieldData(BoolFieldName, BoolFieldID, schemapb.DataType_Bool, BoolArray[1:2], 1))
@@ -1133,6 +1174,7 @@ func TestAppendFieldData(t *testing.T) {
fieldDataArray2 = append(fieldDataArray2, genFieldData(ArrayFieldName, ArrayFieldID, schemapb.DataType_Array, ArrayArray[1:2], 1))
fieldDataArray2 = append(fieldDataArray2, genFieldData(SparseFloatVectorFieldName, SparseFloatVectorFieldID, schemapb.DataType_SparseFloatVector, SparseFloatVector.Contents[1], SparseFloatVector.Dim))
fieldDataArray2 = append(fieldDataArray2, genFieldData(Int8VectorFieldName, Int8VectorFieldID, schemapb.DataType_Int8Vector, Int8Vector[Dim:2*Dim], Dim))
fieldDataArray2 = append(fieldDataArray2, genFieldData(VectorArrayFieldName, VectorArrayFieldID, schemapb.DataType_ArrayOfVector, VectorArray[1:2], Dim))
AppendFieldData(result, fieldDataArray1, 0)
AppendFieldData(result, fieldDataArray2, 0)
@@ -1149,6 +1191,7 @@ func TestAppendFieldData(t *testing.T) {
assert.Equal(t, ArrayArray, result[9].GetScalars().GetArrayData().Data)
assert.Equal(t, SparseFloatVector, result[10].GetVectors().GetSparseFloatVector())
assert.Equal(t, Int8Vector, result[11].GetVectors().Data.(*schemapb.VectorField_Int8Vector).Int8Vector)
assert.Equal(t, VectorArray, result[12].GetVectors().GetVectorArray().Data)
}
func TestDeleteFieldData(t *testing.T) {
@@ -38,7 +38,7 @@ import (
"github.com/milvus-io/milvus/tests/integration"
)
func (s *CompactionSuite) assertMixCompaction(ctx context.Context, collectionName string) {
func (s *CompactionSuite) assertMixCompaction(ctx context.Context, collectionName string, storageV2 bool) {
c := s.Cluster
const (
@@ -49,10 +49,15 @@ func (s *CompactionSuite) assertMixCompaction(ctx context.Context, collectionNam
indexType = integration.IndexFaissIvfFlat
metricType = metric.L2
vecType = schemapb.DataType_FloatVector
)
schema := integration.ConstructSchemaOfVecDataType(collectionName, dim, true, vecType)
var schema *schemapb.CollectionSchema
// todo(SpadeA): fix this when v2 is supported
if storageV2 {
schema = integration.ConstructSchemaOfVecDataType(collectionName, dim, true, schemapb.DataType_FloatVector)
} else {
schema = integration.ConstructSchemaOfVecDataTypeWithStruct(collectionName, dim, true)
}
marshaledSchema, err := proto.Marshal(schema)
s.NoError(err)
@@ -90,11 +95,16 @@ func (s *CompactionSuite) assertMixCompaction(ctx context.Context, collectionNam
for i := 0; i < rowNum/batch; i++ {
// insert
fVecColumn := integration.NewFloatVectorFieldData(integration.FloatVecField, batch, dim)
fieldsData := []*schemapb.FieldData{fVecColumn}
if !storageV2 {
structArrayField := integration.NewStructArrayFieldData(schema.StructArrayFields[0], integration.StructArrayField, batch, dim)
fieldsData = append(fieldsData, structArrayField)
}
hashKeys := integration.GenerateHashKeys(batch)
insertResult, err := c.MilvusClient.Insert(ctx, &milvuspb.InsertRequest{
DbName: dbName,
CollectionName: collectionName,
FieldsData: []*schemapb.FieldData{fVecColumn},
FieldsData: fieldsData,
HashKeys: hashKeys,
NumRows: uint32(batch),
})
@@ -228,7 +238,7 @@ func (s *CompactionSuite) TestMixCompaction() {
defer cancel()
collectionName := "TestCompaction_" + funcutil.GenRandomStr()
s.assertMixCompaction(ctx, collectionName)
s.assertMixCompaction(ctx, collectionName, false)
s.assertQuery(ctx, collectionName)
// drop collection
@@ -252,5 +262,5 @@ func (s *CompactionSuite) TestMixCompactionV2() {
defer cancel()
collectionName := "TestCompaction_" + funcutil.GenRandomStr()
s.assertMixCompaction(ctx, collectionName)
s.assertMixCompaction(ctx, collectionName, true)
}
@@ -0,0 +1,328 @@
// 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 datanode
import (
"context"
"fmt"
"strconv"
"testing"
"github.com/stretchr/testify/suite"
"google.golang.org/protobuf/proto"
"github.com/milvus-io/milvus-proto/go-api/v2/commonpb"
"github.com/milvus-io/milvus-proto/go-api/v2/milvuspb"
"github.com/milvus-io/milvus-proto/go-api/v2/schemapb"
"github.com/milvus-io/milvus/pkg/v2/common"
"github.com/milvus-io/milvus/pkg/v2/log"
"github.com/milvus-io/milvus/pkg/v2/util/merr"
"github.com/milvus-io/milvus/pkg/v2/util/metric"
"github.com/milvus-io/milvus/tests/integration"
)
type ArrayStructDataNodeSuite struct {
integration.MiniClusterSuite
dim int
rowsPerCollection int
generatedFieldData map[int64]*schemapb.FieldData
}
func (s *ArrayStructDataNodeSuite) setupParam() {
s.dim = 32
s.rowsPerCollection = 10
s.generatedFieldData = make(map[int64]*schemapb.FieldData)
}
func (s *ArrayStructDataNodeSuite) loadCollection(collectionName string) {
c := s.Cluster
dbName := ""
schema := integration.ConstructSchema(collectionName, s.dim, true)
sId := &schemapb.FieldSchema{
FieldID: 103,
Name: integration.StructSubInt32Field,
IsPrimaryKey: false,
Description: "",
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_Int32,
TypeParams: []*commonpb.KeyValuePair{
{
Key: common.MaxCapacityKey,
Value: "100",
},
},
IndexParams: nil,
AutoID: false,
}
sVec := &schemapb.FieldSchema{
FieldID: 104,
Name: integration.StructSubFloatVecField,
IsPrimaryKey: false,
Description: "",
DataType: schemapb.DataType_ArrayOfVector,
ElementType: schemapb.DataType_FloatVector,
TypeParams: []*commonpb.KeyValuePair{
{
Key: common.DimKey,
Value: strconv.Itoa(s.dim),
},
{
Key: common.MaxCapacityKey,
Value: "100",
},
},
IndexParams: nil,
AutoID: false,
}
structF := &schemapb.StructArrayFieldSchema{
FieldID: 105,
Name: integration.StructArrayField,
Fields: []*schemapb.FieldSchema{sId, sVec},
}
schema.StructArrayFields = []*schemapb.StructArrayFieldSchema{structF}
marshaledSchema, err := proto.Marshal(schema)
s.NoError(err)
createCollectionStatus, err := c.MilvusClient.CreateCollection(context.TODO(), &milvuspb.CreateCollectionRequest{
DbName: dbName,
CollectionName: collectionName,
Schema: marshaledSchema,
ShardsNum: common.DefaultShardsNum,
})
s.NoError(err)
err = merr.Error(createCollectionStatus)
s.NoError(err)
showCollectionsResp, err := c.MilvusClient.ShowCollections(context.TODO(), &milvuspb.ShowCollectionsRequest{})
s.NoError(err)
s.True(merr.Ok(showCollectionsResp.GetStatus()))
rowNum := s.rowsPerCollection
fVecColumn := integration.NewFloatVectorFieldData(integration.FloatVecField, rowNum, s.dim)
hashKeys := integration.GenerateHashKeys(rowNum)
structColumn := integration.NewStructArrayFieldData(schema.StructArrayFields[0], integration.StructArrayField, rowNum, s.dim)
s.generatedFieldData[101] = fVecColumn
s.generatedFieldData[structColumn.FieldId] = structColumn
s.generatedFieldData[structColumn.GetStructArrays().Fields[0].FieldId] = structColumn.GetStructArrays().Fields[0]
s.generatedFieldData[structColumn.GetStructArrays().Fields[1].FieldId] = structColumn.GetStructArrays().Fields[1]
insertResult, err := c.MilvusClient.Insert(context.TODO(), &milvuspb.InsertRequest{
DbName: dbName,
CollectionName: collectionName,
FieldsData: []*schemapb.FieldData{fVecColumn, structColumn},
HashKeys: hashKeys,
NumRows: uint32(rowNum),
})
s.NoError(err)
s.True(merr.Ok(insertResult.GetStatus()))
log.Info("=========================Data insertion finished=========================")
// flush
flushResp, err := c.MilvusClient.Flush(context.TODO(), &milvuspb.FlushRequest{
DbName: dbName,
CollectionNames: []string{collectionName},
})
s.NoError(err)
segmentIDs, has := flushResp.GetCollSegIDs()[collectionName]
ids := segmentIDs.GetData()
s.Require().NotEmpty(segmentIDs)
s.Require().True(has)
flushTs, has := flushResp.GetCollFlushTs()[collectionName]
s.True(has)
s.WaitForFlush(context.TODO(), ids, flushTs, dbName, collectionName)
segments, err := c.ShowSegments(collectionName)
s.NoError(err)
s.NotEmpty(segments)
log.Info("=========================Data flush finished=========================")
// create index
createIndexStatus, err := c.MilvusClient.CreateIndex(context.TODO(), &milvuspb.CreateIndexRequest{
CollectionName: collectionName,
FieldName: integration.FloatVecField,
IndexName: "_default",
ExtraParams: integration.ConstructIndexParam(s.dim, integration.IndexFaissIvfFlat, metric.IP),
})
s.NoError(err)
err = merr.Error(createIndexStatus)
s.NoError(err)
s.WaitForIndexBuilt(context.TODO(), collectionName, integration.FloatVecField)
log.Info("=========================Index created=========================")
// load
loadStatus, err := c.MilvusClient.LoadCollection(context.TODO(), &milvuspb.LoadCollectionRequest{
DbName: dbName,
CollectionName: collectionName,
})
s.NoError(err)
err = merr.Error(loadStatus)
s.NoError(err)
s.WaitForLoad(context.TODO(), collectionName)
log.Info("=========================Collection loaded=========================")
}
func (s *ArrayStructDataNodeSuite) checkCollections() bool {
req := &milvuspb.ShowCollectionsRequest{
DbName: "",
TimeStamp: 0, // means now
}
resp, err := s.Cluster.MilvusClient.ShowCollections(context.TODO(), req)
s.NoError(err)
s.Equal(len(resp.CollectionIds), 1)
notLoaded := 0
loaded := 0
for _, name := range resp.CollectionNames {
loadProgress, err := s.Cluster.MilvusClient.GetLoadingProgress(context.TODO(), &milvuspb.GetLoadingProgressRequest{
DbName: "",
CollectionName: name,
})
s.NoError(err)
if loadProgress.GetProgress() != int64(100) {
notLoaded++
} else {
loaded++
}
}
log.Info(fmt.Sprintf("loading status: %d/%d", loaded, len(resp.GetCollectionNames())))
return notLoaded == 0
}
func (s *ArrayStructDataNodeSuite) checkFieldsData(fieldsData []*schemapb.FieldData) {
for _, fieldData := range fieldsData {
for i := 0; i < s.rowsPerCollection; i++ {
switch fieldData.FieldName {
case integration.Int64Field:
break
case integration.FloatVecField:
for j := 0; j < s.dim; j++ {
s.Equal(fieldData.GetVectors().GetFloatVector().Data[j],
s.generatedFieldData[fieldData.FieldId].GetVectors().GetFloatVector().Data[j])
}
case integration.StructArrayField:
for _, field := range fieldData.GetStructArrays().Fields {
if field.FieldName == integration.StructSubInt32Field {
getData := field.GetScalars().GetArrayData().Data[i]
generatedData := s.generatedFieldData[field.FieldId].GetScalars().GetArrayData().Data[i]
arrayLen := len(getData.GetIntData().Data)
s.Equal(arrayLen, len(generatedData.GetIntData().Data))
for j := 0; j < arrayLen; j++ {
s.Equal(getData.GetIntData().Data[j], generatedData.GetIntData().Data[j])
}
} else if field.FieldName == integration.StructSubFloatVecField {
getData := field.GetVectors().GetVectorArray().Data[i]
generatedData := s.generatedFieldData[field.FieldId].GetVectors().GetVectorArray().Data[i]
length := len(getData.GetFloatVector().Data)
s.Equal(length, len(generatedData.GetFloatVector().Data))
for j := 0; j < length; j++ {
s.Equal(getData.GetFloatVector().Data[j], generatedData.GetFloatVector().Data[j])
}
}
}
default:
s.Fail(fmt.Sprintf("unsupported field type: %s", fieldData.FieldName))
}
}
}
}
func (s *ArrayStructDataNodeSuite) query(collectionName string) {
c := s.Cluster
var err error
// Query
queryReq := &milvuspb.QueryRequest{
Base: nil,
CollectionName: collectionName,
PartitionNames: nil,
Expr: "",
OutputFields: []string{"*"},
TravelTimestamp: 0,
GuaranteeTimestamp: 0,
QueryParams: []*commonpb.KeyValuePair{
{
Key: "limit",
Value: strconv.Itoa(s.rowsPerCollection),
},
},
}
queryResult, err := c.MilvusClient.Query(context.TODO(), queryReq)
s.NoError(err)
s.Equal(len(queryResult.FieldsData), 3)
s.checkFieldsData(queryResult.FieldsData)
queryReq = &milvuspb.QueryRequest{
Base: nil,
CollectionName: collectionName,
PartitionNames: nil,
Expr: "",
OutputFields: []string{integration.StructArrayField},
TravelTimestamp: 0,
GuaranteeTimestamp: 0,
QueryParams: []*commonpb.KeyValuePair{
{
Key: "limit",
Value: strconv.Itoa(s.rowsPerCollection),
},
},
}
queryResult, err = c.MilvusClient.Query(context.TODO(), queryReq)
s.NoError(err)
// struct array field + pk
s.Equal(len(queryResult.FieldsData), 2)
s.checkFieldsData(queryResult.FieldsData)
// Search
expr := fmt.Sprintf("%s > 0", integration.Int64Field)
nq := 10
topk := 10
roundDecimal := -1
radius := 10
params := integration.GetSearchParams(integration.IndexFaissIvfFlat, metric.IP)
params["radius"] = radius
searchReq := integration.ConstructSearchRequest("", collectionName, expr,
integration.FloatVecField, schemapb.DataType_FloatVector, nil, metric.IP, params, nq, s.dim, topk, roundDecimal)
searchResult, _ := c.MilvusClient.Search(context.TODO(), searchReq)
err = merr.Error(searchResult.GetStatus())
s.NoError(err)
}
func (s *ArrayStructDataNodeSuite) TestSwapQN() {
s.setupParam()
s.Cluster.AddDataNode()
cn := "new_collection_a"
s.loadCollection(cn)
s.query(cn)
s.checkCollections()
}
func TestArrayStructDataNodeUtil(t *testing.T) {
suite.Run(t, new(ArrayStructDataNodeSuite))
}
@@ -0,0 +1,208 @@
// 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 getvector
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/suite"
"google.golang.org/protobuf/proto"
"github.com/milvus-io/milvus-proto/go-api/v2/commonpb"
"github.com/milvus-io/milvus-proto/go-api/v2/milvuspb"
"github.com/milvus-io/milvus-proto/go-api/v2/schemapb"
"github.com/milvus-io/milvus/pkg/v2/common"
"github.com/milvus-io/milvus/pkg/v2/util/funcutil"
"github.com/milvus-io/milvus/pkg/v2/util/metric"
"github.com/milvus-io/milvus/tests/integration"
)
type TestArrayStructSuite struct {
integration.MiniClusterSuite
dbName string
// test params
nq int
topK int
indexType string
metricType string
vecType schemapb.DataType
}
func (s *TestArrayStructSuite) run() {
ctx, cancel := context.WithCancel(s.Cluster.GetContext())
defer cancel()
collection := fmt.Sprintf("TestGetVector_%d_%d_%s_%s_%s",
s.nq, s.topK, s.indexType, s.metricType, funcutil.GenRandomStr())
const (
NB = 10000
dim = 16
)
if len(s.dbName) > 0 {
createDataBaseStatus, err := s.Cluster.MilvusClient.CreateDatabase(ctx, &milvuspb.CreateDatabaseRequest{
DbName: s.dbName,
})
s.Require().NoError(err)
s.Require().Equal(createDataBaseStatus.GetErrorCode(), commonpb.ErrorCode_Success)
}
pkFieldName := "pkField"
vecFieldName := "vecField"
structFieldName := "structField"
structSubVecFieldName := "structSubVecField"
pk := &schemapb.FieldSchema{
FieldID: 100,
Name: pkFieldName,
IsPrimaryKey: true,
Description: "",
DataType: schemapb.DataType_Int64,
TypeParams: []*commonpb.KeyValuePair{
{
Key: common.MaxLengthKey,
Value: "100",
},
},
IndexParams: nil,
AutoID: false,
}
fVec := &schemapb.FieldSchema{
FieldID: 101,
Name: vecFieldName,
IsPrimaryKey: false,
Description: "",
DataType: schemapb.DataType_FloatVector,
TypeParams: []*commonpb.KeyValuePair{
{
Key: common.DimKey,
Value: fmt.Sprintf("%d", dim),
},
},
IndexParams: nil,
}
structSubVec := &schemapb.FieldSchema{
FieldID: 102,
Name: structSubVecFieldName,
IsPrimaryKey: false,
Description: "",
DataType: schemapb.DataType_ArrayOfVector,
ElementType: s.vecType,
TypeParams: []*commonpb.KeyValuePair{
{
Key: common.DimKey,
Value: fmt.Sprintf("%d", dim),
},
},
IndexParams: nil,
}
structField := &schemapb.StructArrayFieldSchema{
FieldID: 103,
Name: structFieldName,
Fields: []*schemapb.FieldSchema{structSubVec},
}
schema := &schemapb.CollectionSchema{
Name: collection,
Description: "",
AutoID: false,
Fields: []*schemapb.FieldSchema{pk, fVec},
StructArrayFields: []*schemapb.StructArrayFieldSchema{structField},
}
marshaledSchema, err := proto.Marshal(schema)
s.Require().NoError(err)
createCollectionStatus, err := s.Cluster.MilvusClient.CreateCollection(ctx, &milvuspb.CreateCollectionRequest{
DbName: s.dbName,
CollectionName: collection,
Schema: marshaledSchema,
ShardsNum: 2,
})
s.Require().NoError(err)
s.Require().Equal(createCollectionStatus.GetErrorCode(), commonpb.ErrorCode_Success)
fieldsData := make([]*schemapb.FieldData, 0, 5)
// pk
fieldsData = append(fieldsData, integration.NewInt64FieldData(pkFieldName, NB))
// vec
fieldsData = append(fieldsData, integration.NewFloatVectorFieldData(vecFieldName, NB, dim))
// struct
fieldsData = append(fieldsData, integration.NewStructArrayFieldData(structField, structFieldName, NB, dim))
hashKeys := integration.GenerateHashKeys(NB)
insertResult, err := s.Cluster.MilvusClient.Insert(ctx, &milvuspb.InsertRequest{
DbName: s.dbName,
CollectionName: collection,
FieldsData: fieldsData,
HashKeys: hashKeys,
NumRows: uint32(NB),
})
s.Require().NoError(err)
s.Require().Equal(insertResult.GetStatus().GetErrorCode(), commonpb.ErrorCode_Success)
// flush
flushResp, err := s.Cluster.MilvusClient.Flush(ctx, &milvuspb.FlushRequest{
DbName: s.dbName,
CollectionNames: []string{collection},
})
s.Require().NoError(err)
segmentIDs, has := flushResp.GetCollSegIDs()[collection]
ids := segmentIDs.GetData()
s.Require().NotEmpty(segmentIDs)
s.Require().True(has)
flushTs, has := flushResp.GetCollFlushTs()[collection]
s.Require().True(has)
s.WaitForFlush(ctx, ids, flushTs, s.dbName, collection)
segments, err := s.Cluster.ShowSegments(collection)
s.Require().NoError(err)
s.Require().NotEmpty(segments)
// // create index
// createIndexResult, err := s.Cluster.Proxy.CreateIndex(ctx, &milvuspb.CreateIndexRequest{
// DbName: s.dbName,
// CollectionName: collection,
// FieldName: structSubVecFieldName,
// IndexName: "_default",
// ExtraParams: integration.ConstructIndexParam(dim, s.indexType, s.metricType),
// })
// s.Require().NoError(err)
// s.Require().Equal(createIndexResult.GetErrorCode(), commonpb.ErrorCode_Success)
// s.WaitForIndexBuiltWithDB(ctx, s.dbName, collection, structSubVecFieldName)
}
func (s *TestArrayStructSuite) TestGetVector_ArrayStruct_FloatVector() {
s.nq = 10
s.topK = 10
s.indexType = integration.IndexHNSW
s.metricType = metric.L2
s.vecType = schemapb.DataType_FloatVector
s.run()
}
func TestGetVectorArrayStruct(t *testing.T) {
// t.Skip("Skip integration test, need to refactor integration test framework.")
suite.Run(t, new(TestArrayStructSuite))
}
+13
View File
@@ -181,6 +181,19 @@ func NewInt8VectorFieldData(fieldName string, numRows, dim int) *schemapb.FieldD
return testutils.NewInt8VectorFieldData(fieldName, numRows, dim)
}
func NewStructArrayFieldData(schema *schemapb.StructArrayFieldSchema, fieldName string, numRow int, dim int) *schemapb.FieldData {
fieldData := &schemapb.FieldData{
Type: schemapb.DataType_ArrayOfStruct,
FieldName: fieldName,
Field: &schemapb.FieldData_StructArrays{
StructArrays: &schemapb.StructArrayField{
Fields: testutils.GenerateArrayOfStructArray(schema, numRow, dim),
},
},
}
return fieldData
}
func GenerateInt64Array(numRows int, start int64) []int64 {
ret := make([]int64, numRows)
for i := 0; i < numRows; i++ {
+87 -14
View File
@@ -18,6 +18,7 @@ package integration
import (
"fmt"
"strconv"
"github.com/milvus-io/milvus-proto/go-api/v2/commonpb"
"github.com/milvus-io/milvus-proto/go-api/v2/schemapb"
@@ -25,20 +26,23 @@ import (
)
const (
BoolField = "boolField"
Int8Field = "int8Field"
Int16Field = "int16Field"
Int32Field = "int32Field"
Int64Field = "int64Field"
FloatField = "floatField"
DoubleField = "doubleField"
VarCharField = "varCharField"
JSONField = "jsonField"
FloatVecField = "floatVecField"
BinVecField = "binVecField"
Float16VecField = "float16VecField"
BFloat16VecField = "bfloat16VecField"
SparseFloatVecField = "sparseFloatVecField"
BoolField = "boolField"
Int8Field = "int8Field"
Int16Field = "int16Field"
Int32Field = "int32Field"
Int64Field = "int64Field"
FloatField = "floatField"
DoubleField = "doubleField"
VarCharField = "varCharField"
JSONField = "jsonField"
FloatVecField = "floatVecField"
BinVecField = "binVecField"
Float16VecField = "float16VecField"
BFloat16VecField = "bfloat16VecField"
SparseFloatVecField = "sparseFloatVecField"
StructArrayField = "structArrayField"
StructSubInt32Field = "structSubInt32Field"
StructSubFloatVecField = "structSubFloatVecField"
)
func ConstructSchema(collection string, dim int, autoID bool, fields ...*schemapb.FieldSchema) *schemapb.CollectionSchema {
@@ -126,3 +130,72 @@ func ConstructSchemaOfVecDataType(collection string, dim int, autoID bool, dataT
Fields: []*schemapb.FieldSchema{pk, fVec},
}
}
func ConstructSchemaOfVecDataTypeWithStruct(collection string, dim int, autoID bool) *schemapb.CollectionSchema {
pk := &schemapb.FieldSchema{
FieldID: 100,
Name: Int64Field,
IsPrimaryKey: true,
Description: "",
DataType: schemapb.DataType_Int64,
TypeParams: nil,
IndexParams: nil,
AutoID: autoID,
}
fVec := &schemapb.FieldSchema{
FieldID: 101,
Name: FloatVecField,
IsPrimaryKey: false,
Description: "",
DataType: schemapb.DataType_FloatVector,
TypeParams: []*commonpb.KeyValuePair{
{
Key: common.DimKey,
Value: fmt.Sprintf("%d", dim),
},
},
IndexParams: nil,
}
structArrayField := &schemapb.StructArrayFieldSchema{
FieldID: 102,
Name: StructArrayField,
Description: "",
Fields: []*schemapb.FieldSchema{
{
FieldID: 103,
Name: StructSubInt32Field,
DataType: schemapb.DataType_Array,
ElementType: schemapb.DataType_Int32,
TypeParams: []*commonpb.KeyValuePair{
{
Key: common.MaxCapacityKey,
Value: "100",
},
},
},
{
FieldID: 104,
Name: StructSubFloatVecField,
DataType: schemapb.DataType_ArrayOfVector,
ElementType: schemapb.DataType_FloatVector,
TypeParams: []*commonpb.KeyValuePair{
{
Key: common.DimKey,
Value: strconv.Itoa(dim),
},
{
Key: common.MaxCapacityKey,
Value: "100",
},
},
},
},
}
return &schemapb.CollectionSchema{
Name: collection,
AutoID: autoID,
Fields: []*schemapb.FieldSchema{pk, fVec},
StructArrayFields: []*schemapb.StructArrayFieldSchema{structArrayField},
}
}