diff --git a/internal/proxy/task_index.go b/internal/proxy/task_index.go index 3993fe0c32..4ee0fdce3c 100644 --- a/internal/proxy/task_index.go +++ b/internal/proxy/task_index.go @@ -23,6 +23,7 @@ import ( "github.com/cockroachdb/errors" "go.uber.org/zap" + "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" @@ -661,8 +662,20 @@ func checkTrain(ctx context.Context, field *schemapb.FieldSchema, indexParams ma return fmt.Errorf("invalid index type: %s", indexType) } + // For ArrayOfVector with non-EmbList metrics (e.g., COSINE, L2, IP), each embedding + // in the array is indexed independently as a regular vector. The index only needs to + // support the element vector type, not the EmbeddingList capability. + // Resolve the effective data type used for index compatibility checks. + effectiveDataType := field.DataType + effectiveElementType := field.ElementType + if typeutil.IsArrayOfVectorType(field.DataType) && + !funcutil.SliceContain(indexparamcheck.EmbListMetrics, indexParams[common.MetricTypeKey]) { + effectiveDataType = field.ElementType + effectiveElementType = schemapb.DataType_None + } + if typeutil.IsVectorType(field.DataType) && indexType != indexparamcheck.AutoIndex { - exist := CheckVecIndexWithDataTypeExist(indexType, field.DataType, field.ElementType) + exist := CheckVecIndexWithDataTypeExist(indexType, effectiveDataType, effectiveElementType) if !exist { return fmt.Errorf("data type %s can't build with this index %s", schemapb.DataType_name[int32(field.GetDataType())], indexType) } @@ -676,12 +689,19 @@ func checkTrain(ctx context.Context, field *schemapb.FieldSchema, indexParams ma } } - if err := checker.CheckValidDataType(indexType, field); err != nil { + effectiveField := field + if effectiveDataType != field.DataType { + effectiveField = proto.Clone(field).(*schemapb.FieldSchema) + effectiveField.DataType = effectiveDataType + effectiveField.ElementType = effectiveElementType + } + + if err := checker.CheckValidDataType(indexType, effectiveField); err != nil { log.Ctx(ctx).Info("create index with invalid data type", zap.Error(err), zap.String("data_type", field.GetDataType().String())) return err } - if err := checker.CheckTrain(field.DataType, field.ElementType, indexParams); err != nil { + if err := checker.CheckTrain(effectiveDataType, effectiveElementType, indexParams); err != nil { log.Ctx(ctx).Info("create index with invalid parameters", zap.Error(err)) return err } diff --git a/internal/proxy/task_index_test.go b/internal/proxy/task_index_test.go index 790d32346a..7969e05412 100644 --- a/internal/proxy/task_index_test.go +++ b/internal/proxy/task_index_test.go @@ -1276,6 +1276,58 @@ func Test_checkEmbeddingListIndex(t *testing.T) { }) } +func Test_arrayOfVector_nonEmbListMetric_indexCompat(t *testing.T) { + // When an ArrayOfVector field uses a non-EmbList metric (e.g., COSINE), + // each embedding is indexed independently. The index should only need to + // support the element vector type, not the EmbeddingList capability. + // This means indexes like IVF_PQ that don't have the EMB_LIST flag + // should still work with ArrayOfVector + COSINE. + t.Run("ArrayOfVector with COSINE should accept IVF_FLAT", func(t *testing.T) { + cit := &createIndexTask{ + req: &milvuspb.CreateIndexRequest{ + ExtraParams: []*commonpb.KeyValuePair{ + {Key: common.IndexTypeKey, Value: "IVF_FLAT"}, + {Key: common.MetricTypeKey, Value: metric.COSINE}, + {Key: "nlist", Value: "128"}, + }, + }, + fieldSchema: &schemapb.FieldSchema{ + FieldID: 101, + Name: "vec_field", + DataType: schemapb.DataType_ArrayOfVector, + ElementType: schemapb.DataType_FloatVector, + TypeParams: []*commonpb.KeyValuePair{ + {Key: common.DimKey, Value: "128"}, + }, + }, + } + err := cit.parseIndexParams(context.TODO()) + assert.NoError(t, err) + }) + + t.Run("ArrayOfVector with MaxSimCosine should accept HNSW", func(t *testing.T) { + cit := &createIndexTask{ + req: &milvuspb.CreateIndexRequest{ + ExtraParams: []*commonpb.KeyValuePair{ + {Key: common.IndexTypeKey, Value: "HNSW"}, + {Key: common.MetricTypeKey, Value: metric.MaxSimCosine}, + }, + }, + fieldSchema: &schemapb.FieldSchema{ + FieldID: 101, + Name: "vec_field", + DataType: schemapb.DataType_ArrayOfVector, + ElementType: schemapb.DataType_FloatVector, + TypeParams: []*commonpb.KeyValuePair{ + {Key: common.DimKey, Value: "128"}, + }, + }, + } + err := cit.parseIndexParams(context.TODO()) + assert.NoError(t, err) + }) +} + func Test_ngram_parseIndexParams(t *testing.T) { t.Run("valid ngram index params", func(t *testing.T) { cit := &createIndexTask{