enhance: [2.5] avoid re-query if hybrid search requested only pk as output field (#40906)

pr: https://github.com/milvus-io/milvus/pull/40842
issue: https://github.com/milvus-io/milvus/issues/40833

---------

Signed-off-by: Buqian Zheng <zhengbuqian@gmail.com>
This commit is contained in:
Buqian Zheng 2025-03-28 14:50:26 +08:00 committed by GitHub
parent 27ea5d14dc
commit 8504a7b98d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 143 additions and 81 deletions

View File

@ -85,11 +85,12 @@ type queryParams struct {
} }
// translateToOutputFieldIDs translates output fields name to output fields id. // translateToOutputFieldIDs translates output fields name to output fields id.
// If no output fields specified, return only pk field
func translateToOutputFieldIDs(outputFields []string, schema *schemapb.CollectionSchema) ([]UniqueID, error) { func translateToOutputFieldIDs(outputFields []string, schema *schemapb.CollectionSchema) ([]UniqueID, error) {
outputFieldIDs := make([]UniqueID, 0, len(outputFields)+1) outputFieldIDs := make([]UniqueID, 0, len(outputFields)+1)
if len(outputFields) == 0 { if len(outputFields) == 0 {
for _, field := range schema.Fields { for _, field := range schema.Fields {
if field.FieldID >= common.StartOfUserFieldID && !typeutil.IsVectorType(field.DataType) { if field.IsPrimaryKey {
outputFieldIDs = append(outputFieldIDs, field.FieldID) outputFieldIDs = append(outputFieldIDs, field.FieldID)
} }
} }
@ -270,7 +271,7 @@ func (t *queryTask) createPlan(ctx context.Context) error {
metrics.ProxyParseExpressionLatency.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), "query", metrics.SuccessLabel).Observe(float64(time.Since(start).Milliseconds())) metrics.ProxyParseExpressionLatency.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), "query", metrics.SuccessLabel).Observe(float64(time.Since(start).Milliseconds()))
} }
t.request.OutputFields, t.userOutputFields, t.userDynamicFields, err = translateOutputFields(t.request.OutputFields, t.schema, true) t.request.OutputFields, t.userOutputFields, t.userDynamicFields, _, err = translateOutputFields(t.request.OutputFields, t.schema, false)
if err != nil { if err != nil {
return err return err
} }

View File

@ -404,7 +404,7 @@ func Test_translateToOutputFieldIDs(t *testing.T) {
}, },
}, },
expectedError: false, expectedError: false,
expectedIDs: []int64{100, 101}, expectedIDs: []int64{100},
}, },
{ {
name: "nil output fields", name: "nil output fields",
@ -427,7 +427,7 @@ func Test_translateToOutputFieldIDs(t *testing.T) {
}, },
}, },
expectedError: false, expectedError: false,
expectedIDs: []int64{100, 101}, expectedIDs: []int64{100},
}, },
{ {
name: "full list", name: "full list",

View File

@ -88,6 +88,9 @@ type searchTask struct {
groupScorer func(group *Group) error groupScorer func(group *Group) error
isIterator bool isIterator bool
// we always remove pk field from output fields, as search result already contains pk field.
// if the user explicitly set pk field in output fields, we add it back to the result.
userRequestedPkFieldExplicitly bool
} }
func (t *searchTask) CanSkipAllocTimestamp() bool { func (t *searchTask) CanSkipAllocTimestamp() bool {
@ -163,7 +166,7 @@ func (t *searchTask) PreExecute(ctx context.Context) error {
} }
} }
t.request.OutputFields, t.userOutputFields, t.userDynamicFields, err = translateOutputFields(t.request.OutputFields, t.schema, false) t.request.OutputFields, t.userOutputFields, t.userDynamicFields, t.userRequestedPkFieldExplicitly, err = translateOutputFields(t.request.OutputFields, t.schema, true)
if err != nil { if err != nil {
log.Warn("translate output fields failed", zap.Error(err)) log.Warn("translate output fields failed", zap.Error(err))
return err return err
@ -786,6 +789,33 @@ func (t *searchTask) PostExecute(ctx context.Context) error {
t.result.Results.OutputFields = t.userOutputFields t.result.Results.OutputFields = t.userOutputFields
t.result.CollectionName = t.request.GetCollectionName() t.result.CollectionName = t.request.GetCollectionName()
t.result.Results.PrimaryFieldName = primaryFieldSchema.GetName() t.result.Results.PrimaryFieldName = primaryFieldSchema.GetName()
if t.userRequestedPkFieldExplicitly {
t.result.Results.OutputFields = append(t.result.Results.OutputFields, primaryFieldSchema.GetName())
var scalars *schemapb.ScalarField
if primaryFieldSchema.GetDataType() == schemapb.DataType_Int64 {
scalars = &schemapb.ScalarField{
Data: &schemapb.ScalarField_LongData{
LongData: t.result.Results.Ids.GetIntId(),
},
}
} else {
scalars = &schemapb.ScalarField{
Data: &schemapb.ScalarField_StringData{
StringData: t.result.Results.Ids.GetStrId(),
},
}
}
pkFieldData := &schemapb.FieldData{
FieldName: primaryFieldSchema.GetName(),
FieldId: primaryFieldSchema.GetFieldID(),
Type: primaryFieldSchema.GetDataType(),
IsDynamic: false,
Field: &schemapb.FieldData_Scalars{
Scalars: scalars,
},
}
t.result.Results.FieldsData = append(t.result.Results.FieldsData, pkFieldData)
}
if t.isIterator && len(t.queryInfos) == 1 && t.queryInfos[0] != nil { if t.isIterator && len(t.queryInfos) == 1 && t.queryInfos[0] != nil {
if iterInfo := t.queryInfos[0].GetSearchIteratorV2Info(); iterInfo != nil { if iterInfo := t.queryInfos[0].GetSearchIteratorV2Info(); iterInfo != nil {
t.result.Results.SearchIteratorV2Results = &schemapb.SearchIteratorV2Results{ t.result.Results.SearchIteratorV2Results = &schemapb.SearchIteratorV2Results{

View File

@ -472,6 +472,7 @@ func TestTranslateOutputFields(t *testing.T) {
var outputFields []string var outputFields []string
var userOutputFields []string var userOutputFields []string
var userDynamicFields []string var userDynamicFields []string
var requestedPK bool
var err error var err error
collSchema := &schemapb.CollectionSchema{ collSchema := &schemapb.CollectionSchema{
@ -498,107 +499,124 @@ func TestTranslateOutputFields(t *testing.T) {
} }
schema := newSchemaInfo(collSchema) schema := newSchemaInfo(collSchema)
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{}, schema, false) // Test empty output fields
assert.Equal(t, nil, err) outputFields, userOutputFields, userDynamicFields, requestedPK, err = translateOutputFields([]string{}, schema, false)
assert.NoError(t, err)
assert.ElementsMatch(t, []string{}, outputFields) assert.ElementsMatch(t, []string{}, outputFields)
assert.ElementsMatch(t, []string{}, userOutputFields) assert.ElementsMatch(t, []string{}, userOutputFields)
assert.ElementsMatch(t, []string{}, userDynamicFields) assert.ElementsMatch(t, []string{}, userDynamicFields)
assert.False(t, requestedPK)
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{idFieldName}, schema, false) // Test single field
assert.Equal(t, nil, err) outputFields, userOutputFields, userDynamicFields, requestedPK, err = translateOutputFields([]string{idFieldName}, schema, false)
assert.NoError(t, err)
assert.ElementsMatch(t, []string{idFieldName}, outputFields) assert.ElementsMatch(t, []string{idFieldName}, outputFields)
assert.ElementsMatch(t, []string{idFieldName}, userOutputFields) assert.ElementsMatch(t, []string{idFieldName}, userOutputFields)
assert.ElementsMatch(t, []string{}, userDynamicFields) assert.ElementsMatch(t, []string{}, userDynamicFields)
assert.True(t, requestedPK)
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{idFieldName, tsFieldName}, schema, false) // Test multiple fields
assert.Equal(t, nil, err) outputFields, userOutputFields, userDynamicFields, requestedPK, err = translateOutputFields([]string{idFieldName, tsFieldName}, schema, false)
assert.NoError(t, err)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName}, outputFields) assert.ElementsMatch(t, []string{idFieldName, tsFieldName}, outputFields)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName}, userOutputFields) assert.ElementsMatch(t, []string{idFieldName, tsFieldName}, userOutputFields)
assert.ElementsMatch(t, []string{}, userDynamicFields) assert.ElementsMatch(t, []string{}, userDynamicFields)
assert.True(t, requestedPK)
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{idFieldName, tsFieldName, floatVectorFieldName}, schema, false) // Test with vector field
assert.Equal(t, nil, err) outputFields, userOutputFields, userDynamicFields, requestedPK, err = translateOutputFields([]string{idFieldName, tsFieldName, floatVectorFieldName}, schema, false)
assert.NoError(t, err)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName}, outputFields) assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName}, outputFields)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName}, userOutputFields) assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName}, userOutputFields)
assert.ElementsMatch(t, []string{}, userDynamicFields) assert.ElementsMatch(t, []string{}, userDynamicFields)
assert.True(t, requestedPK)
// sparse_float_vector is a BM25 function output field, so it should not be included in the output fields // Test without id field
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{"*"}, schema, false) outputFields, userOutputFields, userDynamicFields, requestedPK, err = translateOutputFields([]string{tsFieldName, floatVectorFieldName}, schema, false)
assert.Equal(t, nil, err) assert.NoError(t, err)
assert.ElementsMatch(t, []string{tsFieldName, floatVectorFieldName}, outputFields)
assert.ElementsMatch(t, []string{tsFieldName, floatVectorFieldName}, userOutputFields)
assert.ElementsMatch(t, []string{}, userDynamicFields)
assert.False(t, requestedPK)
// Test wildcard - should not include function output fields
outputFields, userOutputFields, userDynamicFields, requestedPK, err = translateOutputFields([]string{"*"}, schema, false)
assert.NoError(t, err)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, outputFields) assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, outputFields)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, userOutputFields) assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, userOutputFields)
assert.ElementsMatch(t, []string{}, userDynamicFields) assert.ElementsMatch(t, []string{}, userDynamicFields)
assert.True(t, requestedPK)
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{" * "}, schema, false) // Test wildcard with spaces
assert.Equal(t, nil, err) outputFields, userOutputFields, userDynamicFields, requestedPK, err = translateOutputFields([]string{" * "}, schema, false)
assert.NoError(t, err)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, outputFields) assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, outputFields)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, userOutputFields) assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, userOutputFields)
assert.ElementsMatch(t, []string{}, userDynamicFields) assert.ElementsMatch(t, []string{}, userDynamicFields)
assert.True(t, requestedPK)
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{"*", tsFieldName}, schema, false) // Test function output field - should error
assert.Equal(t, nil, err) _, _, _, _, err = translateOutputFields([]string{"*", sparseFloatVectorFieldName}, schema, false)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, outputFields)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, userOutputFields)
assert.ElementsMatch(t, []string{}, userDynamicFields)
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{"*", floatVectorFieldName}, schema, false)
assert.Equal(t, nil, err)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, outputFields)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, userOutputFields)
assert.ElementsMatch(t, []string{}, userDynamicFields)
// sparse_float_vector is a BM25 function output field, so it should not be included in the output fields
_, _, _, err = translateOutputFields([]string{"*", sparseFloatVectorFieldName}, schema, false)
assert.Error(t, err) assert.Error(t, err)
_, _, _, err = translateOutputFields([]string{sparseFloatVectorFieldName}, schema, false) _, _, _, _, err = translateOutputFields([]string{sparseFloatVectorFieldName}, schema, false)
assert.Error(t, err) assert.Error(t, err)
_, _, _, err = translateOutputFields([]string{sparseFloatVectorFieldName}, schema, true) _, _, _, _, err = translateOutputFields([]string{sparseFloatVectorFieldName}, schema, true)
assert.Error(t, err) assert.Error(t, err)
//========================================================================= // Test with removePkField=true
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{}, schema, true) outputFields, userOutputFields, userDynamicFields, requestedPK, err = translateOutputFields([]string{}, schema, true)
assert.Equal(t, nil, err) assert.NoError(t, err)
assert.ElementsMatch(t, []string{idFieldName}, outputFields) assert.ElementsMatch(t, []string{}, outputFields)
assert.ElementsMatch(t, []string{idFieldName}, userOutputFields) assert.ElementsMatch(t, []string{}, userOutputFields)
assert.ElementsMatch(t, []string{}, userDynamicFields) assert.ElementsMatch(t, []string{}, userDynamicFields)
assert.False(t, requestedPK)
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{idFieldName}, schema, true) // if removePkField is true, pk field should be removed from output fields
assert.Equal(t, nil, err)
assert.ElementsMatch(t, []string{idFieldName}, outputFields) outputFields, userOutputFields, userDynamicFields, requestedPK, err = translateOutputFields([]string{"*"}, schema, true)
assert.ElementsMatch(t, []string{idFieldName}, userOutputFields) assert.NoError(t, err)
assert.ElementsMatch(t, []string{tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, outputFields)
assert.ElementsMatch(t, []string{tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, userOutputFields)
assert.ElementsMatch(t, []string{}, userDynamicFields) assert.ElementsMatch(t, []string{}, userDynamicFields)
assert.True(t, requestedPK)
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{idFieldName, tsFieldName}, schema, true) outputFields, userOutputFields, userDynamicFields, requestedPK, err = translateOutputFields([]string{idFieldName, tsFieldName}, schema, true)
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName}, outputFields) assert.ElementsMatch(t, []string{tsFieldName}, outputFields)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName}, userOutputFields) assert.ElementsMatch(t, []string{tsFieldName}, userOutputFields)
assert.ElementsMatch(t, []string{}, userDynamicFields) assert.ElementsMatch(t, []string{}, userDynamicFields)
assert.True(t, requestedPK)
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{idFieldName, tsFieldName, floatVectorFieldName}, schema, true) outputFields, userOutputFields, userDynamicFields, requestedPK, err = translateOutputFields([]string{idFieldName, tsFieldName, floatVectorFieldName}, schema, true)
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName}, outputFields) assert.ElementsMatch(t, []string{tsFieldName, floatVectorFieldName}, outputFields)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName}, userOutputFields) assert.ElementsMatch(t, []string{tsFieldName, floatVectorFieldName}, userOutputFields)
assert.ElementsMatch(t, []string{}, userDynamicFields) assert.ElementsMatch(t, []string{}, userDynamicFields)
assert.True(t, requestedPK)
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{"*"}, schema, true) outputFields, userOutputFields, userDynamicFields, requestedPK, err = translateOutputFields([]string{"*"}, schema, true)
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, outputFields) assert.ElementsMatch(t, []string{tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, outputFields)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, userOutputFields) assert.ElementsMatch(t, []string{tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, userOutputFields)
assert.ElementsMatch(t, []string{}, userDynamicFields) assert.ElementsMatch(t, []string{}, userDynamicFields)
assert.True(t, requestedPK)
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{"*", tsFieldName}, schema, true) outputFields, userOutputFields, userDynamicFields, requestedPK, err = translateOutputFields([]string{"*", tsFieldName}, schema, true)
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, outputFields) assert.ElementsMatch(t, []string{tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, outputFields)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, userOutputFields) assert.ElementsMatch(t, []string{tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, userOutputFields)
assert.ElementsMatch(t, []string{}, userDynamicFields) assert.ElementsMatch(t, []string{}, userDynamicFields)
assert.True(t, requestedPK)
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{"*", floatVectorFieldName}, schema, true) outputFields, userOutputFields, userDynamicFields, requestedPK, err = translateOutputFields([]string{"*", floatVectorFieldName}, schema, true)
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, outputFields) assert.ElementsMatch(t, []string{tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, outputFields)
assert.ElementsMatch(t, []string{idFieldName, tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, userOutputFields) assert.ElementsMatch(t, []string{tsFieldName, floatVectorFieldName, binaryVectorFieldName, float16VectorFieldName, bfloat16VectorFieldName}, userOutputFields)
assert.ElementsMatch(t, []string{}, userDynamicFields) assert.ElementsMatch(t, []string{}, userDynamicFields)
assert.True(t, requestedPK)
_, _, _, err = translateOutputFields([]string{"A"}, schema, true) // Test non-existent field, dynamic field not enabled
_, _, _, _, err = translateOutputFields([]string{"A"}, schema, true)
assert.Error(t, err) assert.Error(t, err)
t.Run("enable dynamic schema", func(t *testing.T) { t.Run("enable dynamic schema", func(t *testing.T) {
@ -617,30 +635,33 @@ func TestTranslateOutputFields(t *testing.T) {
} }
schema := newSchemaInfo(collSchema) schema := newSchemaInfo(collSchema)
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{"A", idFieldName}, schema, true) outputFields, userOutputFields, userDynamicFields, requestedPK, err = translateOutputFields([]string{"A", idFieldName}, schema, true)
assert.Equal(t, nil, err) assert.NoError(t, err)
assert.ElementsMatch(t, []string{common.MetaFieldName, idFieldName}, outputFields) assert.ElementsMatch(t, []string{common.MetaFieldName}, outputFields)
assert.ElementsMatch(t, []string{"A", idFieldName}, userOutputFields) assert.ElementsMatch(t, []string{"A"}, userOutputFields)
assert.ElementsMatch(t, []string{"A"}, userDynamicFields)
assert.True(t, requestedPK)
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{idFieldName, floatVectorFieldName, "$meta[\"A\"]"}, schema, true) // Test invalid dynamic field expressions
_, _, _, _, err = translateOutputFields([]string{idFieldName, floatVectorFieldName, "$meta[\"A\"]"}, schema, true)
assert.Error(t, err) assert.Error(t, err)
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{idFieldName, floatVectorFieldName, "$meta[]"}, schema, true) _, _, _, _, err = translateOutputFields([]string{idFieldName, floatVectorFieldName, "$meta[]"}, schema, true)
assert.Error(t, err) assert.Error(t, err)
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{idFieldName, floatVectorFieldName, "$meta[\"\"]"}, schema, true) _, _, _, _, err = translateOutputFields([]string{idFieldName, floatVectorFieldName, "$meta[\"\"]"}, schema, true)
assert.Error(t, err) assert.Error(t, err)
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{idFieldName, floatVectorFieldName, "$meta["}, schema, true) _, _, _, _, err = translateOutputFields([]string{idFieldName, floatVectorFieldName, "$meta["}, schema, true)
assert.Error(t, err) assert.Error(t, err)
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{idFieldName, floatVectorFieldName, "[]"}, schema, true) _, _, _, _, err = translateOutputFields([]string{idFieldName, floatVectorFieldName, "[]"}, schema, true)
assert.Error(t, err) assert.Error(t, err)
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{idFieldName, floatVectorFieldName, "A > 1"}, schema, true) _, _, _, _, err = translateOutputFields([]string{idFieldName, floatVectorFieldName, "A > 1"}, schema, true)
assert.Error(t, err) assert.Error(t, err)
outputFields, userOutputFields, userDynamicFields, err = translateOutputFields([]string{idFieldName, floatVectorFieldName, ""}, schema, true) _, _, _, _, err = translateOutputFields([]string{idFieldName, floatVectorFieldName, ""}, schema, true)
assert.Error(t, err) assert.Error(t, err)
}) })
} }

View File

@ -1308,7 +1308,11 @@ func computeRecall(results *schemapb.SearchResultData, gts *schemapb.SearchResul
// output_fields=["*"] ==> [A,B,C,D] // output_fields=["*"] ==> [A,B,C,D]
// output_fields=["*",A] ==> [A,B,C,D] // output_fields=["*",A] ==> [A,B,C,D]
// output_fields=["*",C] ==> [A,B,C,D] // output_fields=["*",C] ==> [A,B,C,D]
func translateOutputFields(outputFields []string, schema *schemaInfo, addPrimary bool) ([]string, []string, []string, error) { //
// 4th return value is true if user requested pk field explicitly or using wildcard.
// if removePkField is true, pk field will not be include in the first(resultFieldNames)/second(userOutputFields)
// return value.
func translateOutputFields(outputFields []string, schema *schemaInfo, removePkField bool) ([]string, []string, []string, bool, error) {
var primaryFieldName string var primaryFieldName string
var dynamicField *schemapb.FieldSchema var dynamicField *schemapb.FieldSchema
allFieldNameMap := make(map[string]*schemapb.FieldSchema) allFieldNameMap := make(map[string]*schemapb.FieldSchema)
@ -1329,9 +1333,15 @@ func translateOutputFields(outputFields []string, schema *schemaInfo, addPrimary
allFieldNameMap[field.Name] = field allFieldNameMap[field.Name] = field
} }
userRequestedPkFieldExplicitly := false
for _, outputFieldName := range outputFields { for _, outputFieldName := range outputFields {
outputFieldName = strings.TrimSpace(outputFieldName) outputFieldName = strings.TrimSpace(outputFieldName)
if outputFieldName == primaryFieldName {
userRequestedPkFieldExplicitly = true
}
if outputFieldName == "*" { if outputFieldName == "*" {
userRequestedPkFieldExplicitly = true
for fieldName, field := range allFieldNameMap { for fieldName, field := range allFieldNameMap {
// skip Cold field and fields that can't be output // skip Cold field and fields that can't be output
if schema.IsFieldLoaded(field.GetFieldID()) && schema.CanRetrieveRawFieldData(field) { if schema.IsFieldLoaded(field.GetFieldID()) && schema.CanRetrieveRawFieldData(field) {
@ -1343,20 +1353,20 @@ func translateOutputFields(outputFields []string, schema *schemaInfo, addPrimary
} else { } else {
if field, ok := allFieldNameMap[outputFieldName]; ok { if field, ok := allFieldNameMap[outputFieldName]; ok {
if !schema.CanRetrieveRawFieldData(field) { if !schema.CanRetrieveRawFieldData(field) {
return nil, nil, nil, fmt.Errorf("not allowed to retrieve raw data of field %s", outputFieldName) return nil, nil, nil, false, fmt.Errorf("not allowed to retrieve raw data of field %s", outputFieldName)
} }
if schema.IsFieldLoaded(field.GetFieldID()) { if schema.IsFieldLoaded(field.GetFieldID()) {
resultFieldNameMap[outputFieldName] = true resultFieldNameMap[outputFieldName] = true
userOutputFieldsMap[outputFieldName] = true userOutputFieldsMap[outputFieldName] = true
} else { } else {
return nil, nil, nil, fmt.Errorf("field %s is not loaded", outputFieldName) return nil, nil, nil, false, fmt.Errorf("field %s is not loaded", outputFieldName)
} }
} else { } else {
if schema.EnableDynamicField { if schema.EnableDynamicField {
if schema.IsFieldLoaded(dynamicField.GetFieldID()) { if schema.IsFieldLoaded(dynamicField.GetFieldID()) {
schemaH, err := typeutil.CreateSchemaHelper(schema.CollectionSchema) schemaH, err := typeutil.CreateSchemaHelper(schema.CollectionSchema)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, false, err
} }
err = planparserv2.ParseIdentifier(schemaH, outputFieldName, func(expr *planpb.Expr) error { err = planparserv2.ParseIdentifier(schemaH, outputFieldName, func(expr *planpb.Expr) error {
if len(expr.GetColumnExpr().GetInfo().GetNestedPath()) == 1 && if len(expr.GetColumnExpr().GetInfo().GetNestedPath()) == 1 &&
@ -1367,25 +1377,25 @@ func translateOutputFields(outputFields []string, schema *schemaInfo, addPrimary
}) })
if err != nil { if err != nil {
log.Info("parse output field name failed", zap.String("field name", outputFieldName)) log.Info("parse output field name failed", zap.String("field name", outputFieldName))
return nil, nil, nil, fmt.Errorf("parse output field name failed: %s", outputFieldName) return nil, nil, nil, false, fmt.Errorf("parse output field name failed: %s", outputFieldName)
} }
resultFieldNameMap[common.MetaFieldName] = true resultFieldNameMap[common.MetaFieldName] = true
userOutputFieldsMap[outputFieldName] = true userOutputFieldsMap[outputFieldName] = true
userDynamicFieldsMap[outputFieldName] = true userDynamicFieldsMap[outputFieldName] = true
} else { } else {
// TODO after cold field be able to fetched with chunk cache, this check shall be removed // TODO after cold field be able to fetched with chunk cache, this check shall be removed
return nil, nil, nil, fmt.Errorf("field %s cannot be returned since dynamic field not loaded", outputFieldName) return nil, nil, nil, false, fmt.Errorf("field %s cannot be returned since dynamic field not loaded", outputFieldName)
} }
} else { } else {
return nil, nil, nil, fmt.Errorf("field %s not exist", outputFieldName) return nil, nil, nil, false, fmt.Errorf("field %s not exist", outputFieldName)
} }
} }
} }
} }
if addPrimary { if removePkField {
resultFieldNameMap[primaryFieldName] = true delete(resultFieldNameMap, primaryFieldName)
userOutputFieldsMap[primaryFieldName] = true delete(userOutputFieldsMap, primaryFieldName)
} }
for fieldName := range resultFieldNameMap { for fieldName := range resultFieldNameMap {
@ -1400,7 +1410,7 @@ func translateOutputFields(outputFields []string, schema *schemaInfo, addPrimary
} }
} }
return resultFieldNames, userOutputFields, userDynamicFields, nil return resultFieldNames, userOutputFields, userDynamicFields, userRequestedPkFieldExplicitly, nil
} }
func validateIndexName(indexName string) error { func validateIndexName(indexName string) error {