fix: reject invalid ArrayOfVector metric types (#50363)

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

Signed-off-by: SpadeA <tangchenjie1210@gmail.com>
This commit is contained in:
Spade A
2026-06-11 15:50:20 +08:00
committed by GitHub
parent 168cfd8fd5
commit 49648e8e25
6 changed files with 129 additions and 33 deletions
+2 -16
View File
@@ -570,22 +570,8 @@ func (cit *createIndexTask) parseIndexParams(ctx context.Context) error {
return merr.WrapErrParameterInvalid("valid index params", "invalid index params", "int vector index does not support metric type: "+metricType)
}
} else if typeutil.IsArrayOfVectorType(cit.fieldSchema.DataType) {
if !funcutil.SliceContain(indexparamcheck.EmbListMetrics, metricType) {
if typeutil.IsDenseFloatVectorType(cit.fieldSchema.ElementType) {
if !funcutil.SliceContain(indexparamcheck.FloatVectorMetrics, metricType) {
return merr.WrapErrParameterInvalid("valid index params", "invalid index params", "array of vector with float element type does not support metric type: "+metricType)
}
} else if typeutil.IsBinaryVectorType(cit.fieldSchema.ElementType) {
if !funcutil.SliceContain(indexparamcheck.BinaryVectorMetrics, metricType) {
return merr.WrapErrParameterInvalid("valid index params", "invalid index params", "array of vector with binary element type does not support metric type: "+metricType)
}
} else if typeutil.IsIntVectorType(cit.fieldSchema.ElementType) {
if !funcutil.SliceContain(indexparamcheck.IntVectorMetrics, metricType) {
return merr.WrapErrParameterInvalid("valid index params", "invalid index params", "array of vector with int element type does not support metric type: "+metricType)
}
} else {
return merr.WrapErrParameterInvalid("valid index params", "invalid index params", "array of vector index does not support metric type: "+metricType)
}
if err := indexparamcheck.ValidateArrayOfVectorMetricType(cit.fieldSchema.ElementType, metricType); err != nil {
return merr.WrapErrParameterInvalid("valid index params", "invalid index params", err.Error())
}
}
}
+45
View File
@@ -1489,6 +1489,51 @@ func Test_arrayOfVector_nonEmbListMetric_indexCompat(t *testing.T) {
err := cit.parseIndexParams(context.TODO())
assert.NoError(t, err)
})
t.Run("ArrayOfVector with float element should reject MaxSimHamming", func(t *testing.T) {
cit := &createIndexTask{
req: &milvuspb.CreateIndexRequest{
ExtraParams: []*commonpb.KeyValuePair{
{Key: common.IndexTypeKey, Value: "HNSW_SQ"},
{Key: common.MetricTypeKey, Value: metric.MaxSimHamming},
},
},
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.Error(t, err)
assert.Contains(t, err.Error(), "array of vector with float element type does not support metric type")
})
t.Run("ArrayOfVector with binary element should accept MaxSimHamming", func(t *testing.T) {
cit := &createIndexTask{
req: &milvuspb.CreateIndexRequest{
ExtraParams: []*commonpb.KeyValuePair{
{Key: common.IndexTypeKey, Value: "HNSW"},
{Key: common.MetricTypeKey, Value: metric.MaxSimHamming},
},
},
fieldSchema: &schemapb.FieldSchema{
FieldID: 101,
Name: "vec_field",
DataType: schemapb.DataType_ArrayOfVector,
ElementType: schemapb.DataType_BinaryVector,
TypeParams: []*commonpb.KeyValuePair{
{Key: common.DimKey, Value: "128"},
},
},
}
err := cit.parseIndexParams(context.TODO())
assert.NoError(t, err)
})
}
func Test_ngram_parseIndexParams(t *testing.T) {
@@ -59,6 +59,13 @@ var (
BinaryVectorMetrics = []string{metric.HAMMING, metric.JACCARD, metric.SUBSTRUCTURE, metric.SUPERSTRUCTURE, metric.MHJACCARD}
IntVectorMetrics = []string{metric.L2, metric.IP, metric.COSINE}
EmbListMetrics = []string{metric.MaxSim, metric.MaxSimCosine, metric.MaxSimL2, metric.MaxSimIP, metric.MaxSimHamming, metric.MaxSimJaccard}
ArrayOfVectorFloatMetrics = []string{metric.L2, metric.IP, metric.COSINE, metric.MaxSim, metric.MaxSimCosine, metric.MaxSimL2, metric.MaxSimIP}
ArrayOfVectorBinaryMetrics = []string{
metric.HAMMING, metric.JACCARD, metric.SUBSTRUCTURE, metric.SUPERSTRUCTURE, metric.MHJACCARD,
metric.MaxSimHamming, metric.MaxSimJaccard,
}
ArrayOfVectorIntMetrics = []string{metric.L2, metric.IP, metric.COSINE, metric.MaxSim, metric.MaxSimCosine, metric.MaxSimL2, metric.MaxSimIP}
)
// BinIDMapMetrics is a set of all metric types supported for binary vector.
+25
View File
@@ -24,6 +24,7 @@ import (
"github.com/milvus-io/milvus/pkg/v3/common"
"github.com/milvus-io/milvus/pkg/v3/util/funcutil"
"github.com/milvus-io/milvus/pkg/v3/util/paramtable"
"github.com/milvus-io/milvus/pkg/v3/util/typeutil"
)
// CheckIntByRange check if the data corresponding to the key is in the range of [min, max].
@@ -62,6 +63,30 @@ func CheckStrByValues(params map[string]string, key string, container []string)
return funcutil.SliceContain(container, value)
}
// ValidateArrayOfVectorMetricType validates both element-level and EmbList metrics
// against the ArrayOfVector element type.
func ValidateArrayOfVectorMetricType(elementType schemapb.DataType, metricType string) error {
if typeutil.IsDenseFloatVectorType(elementType) {
if !funcutil.SliceContain(ArrayOfVectorFloatMetrics, metricType) {
return fmt.Errorf("array of vector with float element type does not support metric type: %s, supported: %v", metricType, ArrayOfVectorFloatMetrics)
}
return nil
}
if typeutil.IsBinaryVectorType(elementType) {
if !funcutil.SliceContain(ArrayOfVectorBinaryMetrics, metricType) {
return fmt.Errorf("array of vector with binary element type does not support metric type: %s, supported: %v", metricType, ArrayOfVectorBinaryMetrics)
}
return nil
}
if typeutil.IsIntVectorType(elementType) {
if !funcutil.SliceContain(ArrayOfVectorIntMetrics, metricType) {
return fmt.Errorf("array of vector with int element type does not support metric type: %s, supported: %v", metricType, ArrayOfVectorIntMetrics)
}
return nil
}
return fmt.Errorf("array of vector index does not support element type: %s", elementType.String())
}
func errOutOfRange(x interface{}, lb interface{}, ub interface{}) error {
return fmt.Errorf("%v out of range: [%v, %v]", x, lb, ub)
}
@@ -73,22 +73,8 @@ func (c vecIndexChecker) StaticCheck(dataType schemapb.DataType, elementType sch
return fmt.Errorf("metric type %s not found or not supported, supported: %v", params[Metric], IntVectorMetrics)
}
} else if typeutil.IsArrayOfVectorType(dataType) {
if !CheckStrByValues(params, Metric, EmbListMetrics) {
if typeutil.IsDenseFloatVectorType(elementType) {
if !CheckStrByValues(params, Metric, FloatVectorMetrics) {
return fmt.Errorf("metric type %s not found or not supported for array of vector with float element type, supported: %v", params[Metric], FloatVectorMetrics)
}
} else if typeutil.IsBinaryVectorType(elementType) {
if !CheckStrByValues(params, Metric, BinaryVectorMetrics) {
return fmt.Errorf("metric type %s not found or not supported for array of vector with binary element type, supported: %v", params[Metric], BinaryVectorMetrics)
}
} else if typeutil.IsIntVectorType(elementType) {
if !CheckStrByValues(params, Metric, IntVectorMetrics) {
return fmt.Errorf("metric type %s not found or not supported for array of vector with int element type, supported: %v", params[Metric], IntVectorMetrics)
}
} else {
return fmt.Errorf("metric type %s not found or not supported for array of vector, supported: %v", params[Metric], EmbListMetrics)
}
if err := ValidateArrayOfVectorMetricType(elementType, params[Metric]); err != nil {
return err
}
}
@@ -6,6 +6,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/milvus-io/milvus-proto/go-api/v3/schemapb"
"github.com/milvus-io/milvus/pkg/v3/common"
"github.com/milvus-io/milvus/pkg/v3/util/metric"
)
func TestVecIndexChecker_StaticCheck(t *testing.T) {
@@ -14,6 +16,7 @@ func TestVecIndexChecker_StaticCheck(t *testing.T) {
tests := []struct {
name string
dataType schemapb.DataType
elemType schemapb.DataType
params map[string]string
wantErr bool
}{
@@ -70,11 +73,55 @@ func TestVecIndexChecker_StaticCheck(t *testing.T) {
},
wantErr: true,
},
{
name: "ArrayOfVector float accepts MaxSimCosine",
dataType: schemapb.DataType_ArrayOfVector,
elemType: schemapb.DataType_FloatVector,
params: map[string]string{
common.IndexTypeKey: "HNSW",
common.MetricTypeKey: metric.MaxSimCosine,
HNSWM: "16",
EFConstruction: "200",
},
wantErr: false,
},
{
name: "ArrayOfVector float rejects MaxSimHamming",
dataType: schemapb.DataType_ArrayOfVector,
elemType: schemapb.DataType_FloatVector,
params: map[string]string{
common.IndexTypeKey: "HNSW_SQ",
common.MetricTypeKey: metric.MaxSimHamming,
},
wantErr: true,
},
{
name: "ArrayOfVector binary accepts MaxSimHamming",
dataType: schemapb.DataType_ArrayOfVector,
elemType: schemapb.DataType_BinaryVector,
params: map[string]string{
common.IndexTypeKey: "HNSW",
common.MetricTypeKey: metric.MaxSimHamming,
HNSWM: "16",
EFConstruction: "200",
},
wantErr: false,
},
{
name: "ArrayOfVector binary rejects MaxSimCosine",
dataType: schemapb.DataType_ArrayOfVector,
elemType: schemapb.DataType_BinaryVector,
params: map[string]string{
common.IndexTypeKey: "HNSW",
common.MetricTypeKey: metric.MaxSimCosine,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := checker.StaticCheck(tt.dataType, schemapb.DataType_None, tt.params)
err := checker.StaticCheck(tt.dataType, tt.elemType, tt.params)
if tt.wantErr {
assert.Error(t, err)
} else {