mirror of
https://github.com/milvus-io/milvus.git
synced 2026-07-21 02:05:41 +00:00
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:
@@ -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 {
|
||||
|
||||
@@ -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-- {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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")
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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])
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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++ {
|
||||
|
||||
@@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user