github.com/milvus-io/milvus-sdk-go/v2@v2.4.1/test/testcases/groupby_search_test.go (about)

     1  //go:build L0
     2  
     3  package testcases
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"log"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/milvus-io/milvus-sdk-go/v2/client"
    13  	"github.com/milvus-io/milvus-sdk-go/v2/test/base"
    14  
    15  	"github.com/milvus-io/milvus-sdk-go/v2/entity"
    16  	"github.com/milvus-io/milvus-sdk-go/v2/test/common"
    17  
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  // Generate groupBy-supported vector indexes
    22  func genGroupByVectorIndex(metricType entity.MetricType) []entity.Index {
    23  	nlist := 128
    24  	idxFlat, _ := entity.NewIndexFlat(metricType)
    25  	idxIvfFlat, _ := entity.NewIndexIvfFlat(metricType, nlist)
    26  	idxHnsw, _ := entity.NewIndexHNSW(metricType, 8, 96)
    27  
    28  	allFloatIndex := []entity.Index{
    29  		idxFlat,
    30  		idxIvfFlat,
    31  		idxHnsw,
    32  	}
    33  	return allFloatIndex
    34  }
    35  
    36  // Generate groupBy-supported vector indexes
    37  func genGroupByBinaryIndex(metricType entity.MetricType) []entity.Index {
    38  	nlist := 128
    39  	idxBinFlat, _ := entity.NewIndexBinFlat(metricType, nlist)
    40  	idxBinIvfFlat, _ := entity.NewIndexBinIvfFlat(metricType, nlist)
    41  
    42  	allFloatIndex := []entity.Index{
    43  		idxBinFlat,
    44  		idxBinIvfFlat,
    45  	}
    46  	return allFloatIndex
    47  }
    48  
    49  func genUnsupportedFloatGroupByIndex() []entity.Index {
    50  	idxIvfSq8, _ := entity.NewIndexIvfSQ8(entity.L2, 128)
    51  	idxIvfPq, _ := entity.NewIndexIvfPQ(entity.L2, 128, 16, 8)
    52  	idxScann, _ := entity.NewIndexSCANN(entity.L2, 16, false)
    53  	idxDiskAnn, _ := entity.NewIndexDISKANN(entity.L2)
    54  	return []entity.Index{
    55  		idxIvfSq8,
    56  		idxIvfPq,
    57  		idxScann,
    58  		idxDiskAnn,
    59  	}
    60  }
    61  
    62  func prepareDataForGroupBySearch(t *testing.T, loopInsert int, insertNi int, idx entity.Index, withGrowing bool) (*base.MilvusClient, context.Context, string) {
    63  	ctx := createContext(t, time.Second*common.DefaultTimeout*5)
    64  	mc := createMilvusClient(ctx, t)
    65  
    66  	// create collection with all datatype
    67  	cp := CollectionParams{CollectionFieldsType: AllFields, AutoID: false, EnableDynamicField: true,
    68  		ShardsNum: common.DefaultShards, Dim: common.DefaultDim}
    69  	collName := createCollection(ctx, t, mc, cp)
    70  
    71  	// insert
    72  	dp := DataParams{CollectionName: collName, PartitionName: "", CollectionFieldsType: AllFields,
    73  		start: 0, nb: insertNi, dim: common.DefaultDim, EnableDynamicField: true, WithRows: false}
    74  	for i := 0; i < loopInsert; i++ {
    75  		_, _ = insertData(ctx, t, mc, dp)
    76  	}
    77  
    78  	if !withGrowing {
    79  		mc.Flush(ctx, collName, false)
    80  	}
    81  
    82  	//create scalar index
    83  	supportedGroupByFields := []string{common.DefaultIntFieldName, common.DefaultInt8FieldName, common.DefaultInt16FieldName,
    84  		common.DefaultInt32FieldName, common.DefaultVarcharFieldName, common.DefaultBoolFieldName}
    85  	for _, groupByField := range supportedGroupByFields {
    86  		err := mc.CreateIndex(ctx, collName, groupByField, entity.NewScalarIndex(), false)
    87  		common.CheckErr(t, err, true)
    88  	}
    89  
    90  	// create vector index
    91  	idxHnsw, _ := entity.NewIndexHNSW(entity.COSINE, 8, 96)
    92  	indexBinary, _ := entity.NewIndexBinIvfFlat(entity.JACCARD, 64)
    93  	for _, fieldName := range common.AllVectorsFieldsName {
    94  		if fieldName == common.DefaultFloatVecFieldName {
    95  			err := mc.CreateIndex(ctx, collName, common.DefaultFloatVecFieldName, idx, false)
    96  			common.CheckErr(t, err, true)
    97  		} else if fieldName == common.DefaultBinaryVecFieldName {
    98  			err := mc.CreateIndex(ctx, collName, fieldName, indexBinary, false)
    99  			common.CheckErr(t, err, true)
   100  		} else {
   101  			err := mc.CreateIndex(ctx, collName, fieldName, idxHnsw, false)
   102  			common.CheckErr(t, err, true)
   103  		}
   104  	}
   105  
   106  	// load collection
   107  	err := mc.LoadCollection(ctx, collName, false)
   108  	common.CheckErr(t, err, true)
   109  
   110  	return mc, ctx, collName
   111  }
   112  
   113  // create coll with all datatype -> build all supported index
   114  // -> search with WithGroupByField (int* + varchar + bool
   115  // -> verify every top passage is the top of whole group
   116  // output_fields: pk + groupBy
   117  func TestSearchGroupByFloatDefault(t *testing.T) {
   118  	t.Parallel()
   119  	for _, metricType := range common.SupportFloatMetricType {
   120  		for _, idx := range genGroupByVectorIndex(metricType) {
   121  			// prepare data
   122  			mc, ctx, collName := prepareDataForGroupBySearch(t, 100, 200, idx, false)
   123  
   124  			// search params
   125  			queryVec := common.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeFloatVector)
   126  			sp, _ := entity.NewIndexIvfFlatSearchParam(32)
   127  
   128  			// search with groupBy field
   129  			supportedGroupByFields := []string{common.DefaultIntFieldName, "int8", "int16", "int32", "varchar", "bool"}
   130  			for _, groupByField := range supportedGroupByFields {
   131  				resGroupBy, _ := mc.Search(ctx, collName, []string{}, "", []string{common.DefaultIntFieldName, groupByField}, queryVec,
   132  					common.DefaultFloatVecFieldName, metricType, common.DefaultTopK, sp, client.WithGroupByField(groupByField))
   133  
   134  				// verify each topK entity is the top1 of the whole group
   135  				hitsNum := 0
   136  				total := 0
   137  				for i := 0; i < common.DefaultNq; i++ {
   138  					for j := 0; j < resGroupBy[i].ResultCount; j++ {
   139  						groupByValue, _ := resGroupBy[i].GroupByValue.Get(j)
   140  						pkValue, _ := resGroupBy[i].IDs.GetAsInt64(j)
   141  						var expr string
   142  						if groupByField == "varchar" {
   143  							expr = fmt.Sprintf("%s == '%v' ", groupByField, groupByValue)
   144  						} else {
   145  							expr = fmt.Sprintf("%s == %v", groupByField, groupByValue)
   146  						}
   147  						// 	search filter with groupByValue is the top1
   148  						resFilter, _ := mc.Search(ctx, collName, []string{}, expr, []string{common.DefaultIntFieldName,
   149  							groupByField}, []entity.Vector{queryVec[i]}, common.DefaultFloatVecFieldName, metricType, 1, sp)
   150  						filterTop1Pk, _ := resFilter[0].IDs.GetAsInt64(0)
   151  						//log.Printf("Search top1 with %s: groupByValue: %v, pkValue: %d. The returned pk by filter search is: %d",
   152  						//	groupByField, groupByValue, pkValue, filterTop1Pk)
   153  						if filterTop1Pk == pkValue {
   154  							hitsNum += 1
   155  						}
   156  						total += 1
   157  					}
   158  				}
   159  
   160  				// verify hits rate
   161  				hitsRate := float32(hitsNum) / float32(total)
   162  				_str := fmt.Sprintf("GroupBy search with field %s, nq=%d and limit=%d , then hitsNum= %d, hitsRate=%v\n",
   163  					groupByField, common.DefaultNq, common.DefaultTopK, hitsNum, hitsRate)
   164  				log.Println(_str)
   165  				if groupByField != "bool" {
   166  					require.GreaterOrEqualf(t, hitsRate, float32(0.8), _str)
   167  				}
   168  			}
   169  		}
   170  	}
   171  }
   172  
   173  // test groupBy search sparse vector
   174  func TestGroupBySearchSparseVector(t *testing.T) {
   175  	t.Parallel()
   176  	idxInverted, _ := entity.NewIndexSparseInverted(entity.IP, 0.3)
   177  	idxWand, _ := entity.NewIndexSparseWAND(entity.IP, 0.2)
   178  	for _, idx := range []entity.Index{idxInverted, idxWand} {
   179  		ctx := createContext(t, time.Second*common.DefaultTimeout*2)
   180  		// connect
   181  		mc := createMilvusClient(ctx, t)
   182  
   183  		// create -> insert [0, 3000) -> flush -> index -> load
   184  		cp := CollectionParams{CollectionFieldsType: Int64VarcharSparseVec, AutoID: false, EnableDynamicField: true,
   185  			ShardsNum: common.DefaultShards, Dim: common.DefaultDim, MaxLength: common.TestMaxLen}
   186  		collName := createCollection(ctx, t, mc, cp, client.WithConsistencyLevel(entity.ClStrong))
   187  
   188  		// insert data
   189  		dp := DataParams{DoInsert: true, CollectionName: collName, CollectionFieldsType: Int64VarcharSparseVec, start: 0,
   190  			nb: 200, dim: common.DefaultDim, EnableDynamicField: true}
   191  		for i := 0; i < 100; i++ {
   192  			_, _ = insertData(ctx, t, mc, dp)
   193  		}
   194  		mc.Flush(ctx, collName, false)
   195  
   196  		// index and load
   197  		idxHnsw, _ := entity.NewIndexHNSW(entity.L2, 8, 96)
   198  		mc.CreateIndex(ctx, collName, common.DefaultFloatVecFieldName, idxHnsw, false)
   199  		mc.CreateIndex(ctx, collName, common.DefaultSparseVecFieldName, idx, false)
   200  		mc.LoadCollection(ctx, collName, false)
   201  
   202  		// groupBy search
   203  		queryVec := common.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeSparseVector)
   204  		sp, _ := entity.NewIndexSparseInvertedSearchParam(0.2)
   205  		resGroupBy, _ := mc.Search(ctx, collName, []string{}, "", []string{common.DefaultIntFieldName, common.DefaultVarcharFieldName}, queryVec,
   206  			common.DefaultSparseVecFieldName, entity.IP, common.DefaultTopK, sp, client.WithGroupByField(common.DefaultVarcharFieldName))
   207  
   208  		// verify each topK entity is the top1 of the whole group
   209  		hitsNum := 0
   210  		total := 0
   211  		for i := 0; i < common.DefaultNq; i++ {
   212  			if resGroupBy[i].ResultCount > 0 {
   213  				for j := 0; j < resGroupBy[i].ResultCount; j++ {
   214  					groupByValue, _ := resGroupBy[i].GroupByValue.Get(j)
   215  					pkValue, _ := resGroupBy[i].IDs.GetAsInt64(j)
   216  					expr := fmt.Sprintf("%s == '%v' ", common.DefaultVarcharFieldName, groupByValue)
   217  					// 	search filter with groupByValue is the top1
   218  					resFilter, _ := mc.Search(ctx, collName, []string{}, expr, []string{common.DefaultIntFieldName,
   219  						common.DefaultVarcharFieldName}, []entity.Vector{queryVec[i]}, common.DefaultSparseVecFieldName, entity.IP, 1, sp)
   220  					filterTop1Pk, _ := resFilter[0].IDs.GetAsInt64(0)
   221  					log.Printf("Search top1 with %s: groupByValue: %v, pkValue: %d. The returned pk by filter search is: %d",
   222  						common.DefaultVarcharFieldName, groupByValue, pkValue, filterTop1Pk)
   223  					if filterTop1Pk == pkValue {
   224  						hitsNum += 1
   225  					}
   226  					total += 1
   227  				}
   228  			}
   229  		}
   230  
   231  		// verify hits rate
   232  		hitsRate := float32(hitsNum) / float32(total)
   233  		_str := fmt.Sprintf("GroupBy search with field %s, nq=%d and limit=%d , then hitsNum= %d, hitsRate=%v\n",
   234  			common.DefaultVarcharFieldName, common.DefaultNq, common.DefaultTopK, hitsNum, hitsRate)
   235  		log.Println(_str)
   236  		require.GreaterOrEqualf(t, hitsRate, float32(0.8), _str)
   237  	}
   238  }
   239  
   240  // binary vector -> not supported
   241  func TestSearchGroupByBinaryDefault(t *testing.T) {
   242  	t.Parallel()
   243  	for _, metricType := range common.SupportBinIvfFlatMetricType {
   244  		for _, idx := range genGroupByBinaryIndex(metricType) {
   245  			ctx := createContext(t, time.Second*common.DefaultTimeout)
   246  			// connect
   247  			mc := createMilvusClient(ctx, t)
   248  
   249  			// create collection with all datatype
   250  			cp := CollectionParams{CollectionFieldsType: VarcharBinaryVec, AutoID: false, EnableDynamicField: true,
   251  				ShardsNum: common.DefaultShards, Dim: common.DefaultDim}
   252  			collName := createCollection(ctx, t, mc, cp)
   253  
   254  			// insert
   255  			dp := DataParams{CollectionName: collName, PartitionName: "", CollectionFieldsType: VarcharBinaryVec,
   256  				start: 0, nb: 1000, dim: common.DefaultDim, EnableDynamicField: true, WithRows: false}
   257  			for i := 0; i < 2; i++ {
   258  				_, _ = insertData(ctx, t, mc, dp)
   259  			}
   260  			mc.Flush(ctx, collName, false)
   261  
   262  			// create index and load
   263  			err := mc.CreateIndex(ctx, collName, common.DefaultBinaryVecFieldName, idx, false)
   264  			common.CheckErr(t, err, true)
   265  			err = mc.LoadCollection(ctx, collName, false)
   266  			common.CheckErr(t, err, true)
   267  
   268  			// search params
   269  			queryVec := common.GenSearchVectors(1, common.DefaultDim, entity.FieldTypeBinaryVector)
   270  			sp, _ := entity.NewIndexBinIvfFlatSearchParam(32)
   271  			supportedGroupByFields := []string{common.DefaultVarcharFieldName, common.DefaultBinaryVecFieldName}
   272  
   273  			// search with groupBy field
   274  			for _, groupByField := range supportedGroupByFields {
   275  				_, err := mc.Search(ctx, collName, []string{}, "", []string{common.DefaultVarcharFieldName, groupByField}, queryVec,
   276  					common.DefaultBinaryVecFieldName, metricType, common.DefaultTopK, sp, client.WithGroupByField(groupByField))
   277  				common.CheckErr(t, err, false, "not support search_group_by operation based on binary vector column")
   278  			}
   279  		}
   280  	}
   281  }
   282  
   283  // binary vector -> growing segments, maybe brute force
   284  // default Bounded ConsistencyLevel -> succ ??
   285  // strong ConsistencyLevel -> error
   286  func TestSearchGroupByBinaryGrowing(t *testing.T) {
   287  	t.Parallel()
   288  	for _, metricType := range common.SupportBinIvfFlatMetricType {
   289  		idxBinIvfFlat, _ := entity.NewIndexBinIvfFlat(metricType, 128)
   290  		ctx := createContext(t, time.Second*common.DefaultTimeout)
   291  		// connect
   292  		mc := createMilvusClient(ctx, t)
   293  
   294  		// create collection with all datatype
   295  		cp := CollectionParams{CollectionFieldsType: VarcharBinaryVec, AutoID: false, EnableDynamicField: true,
   296  			ShardsNum: common.DefaultShards, Dim: common.DefaultDim}
   297  		collName := createCollection(ctx, t, mc, cp)
   298  
   299  		// create index and load
   300  		err := mc.CreateIndex(ctx, collName, common.DefaultBinaryVecFieldName, idxBinIvfFlat, false)
   301  		common.CheckErr(t, err, true)
   302  		err = mc.LoadCollection(ctx, collName, false)
   303  		common.CheckErr(t, err, true)
   304  
   305  		// insert
   306  		dp := DataParams{CollectionName: collName, PartitionName: "", CollectionFieldsType: VarcharBinaryVec,
   307  			start: 0, nb: 1000, dim: common.DefaultDim, EnableDynamicField: true, WithRows: false}
   308  		_, _ = insertData(ctx, t, mc, dp)
   309  
   310  		// search params
   311  		queryVec := common.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeBinaryVector)
   312  		sp, _ := entity.NewIndexBinIvfFlatSearchParam(64)
   313  		supportedGroupByFields := []string{common.DefaultVarcharFieldName}
   314  
   315  		// search with groupBy field
   316  		for _, groupByField := range supportedGroupByFields {
   317  			_, err := mc.Search(ctx, collName, []string{}, "", []string{common.DefaultVarcharFieldName,
   318  				groupByField}, queryVec, common.DefaultBinaryVecFieldName, metricType, common.DefaultTopK, sp,
   319  				client.WithGroupByField(groupByField), client.WithSearchQueryConsistencyLevel(entity.ClStrong))
   320  			common.CheckErr(t, err, false, "not support search_group_by operation based on binary vector column")
   321  		}
   322  	}
   323  }
   324  
   325  // groupBy in growing segments, maybe growing index or brute force
   326  func TestSearchGroupByFloatGrowing(t *testing.T) {
   327  	for _, metricType := range common.SupportFloatMetricType {
   328  		idxHnsw, _ := entity.NewIndexHNSW(metricType, 8, 96)
   329  		mc, ctx, collName := prepareDataForGroupBySearch(t, 100, 200, idxHnsw, true)
   330  
   331  		// search params
   332  		queryVec := common.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeFloatVector)
   333  		sp, _ := entity.NewIndexIvfFlatSearchParam(32)
   334  		supportedGroupByFields := []string{common.DefaultIntFieldName, "int8", "int16", "int32", "varchar", "bool"}
   335  
   336  		// search with groupBy field
   337  		hitsNum := 0
   338  		total := 0
   339  		for _, groupByField := range supportedGroupByFields {
   340  			resGroupBy, _ := mc.Search(ctx, collName, []string{}, "", []string{common.DefaultIntFieldName, groupByField}, queryVec,
   341  				common.DefaultFloatVecFieldName, metricType, common.DefaultTopK, sp, client.WithGroupByField(groupByField),
   342  				client.WithSearchQueryConsistencyLevel(entity.ClStrong))
   343  
   344  			// verify each topK entity is the top1 in the group
   345  			for i := 0; i < common.DefaultNq; i++ {
   346  				for j := 0; j < resGroupBy[i].ResultCount; j++ {
   347  					groupByValue, _ := resGroupBy[i].GroupByValue.Get(j)
   348  					pkValue, _ := resGroupBy[i].IDs.GetAsInt64(j)
   349  					var expr string
   350  					if groupByField == "varchar" {
   351  						expr = fmt.Sprintf("%s == '%v' ", groupByField, groupByValue)
   352  					} else {
   353  						expr = fmt.Sprintf("%s == %v", groupByField, groupByValue)
   354  					}
   355  					resFilter, _ := mc.Search(ctx, collName, []string{}, expr, []string{common.DefaultIntFieldName,
   356  						groupByField}, []entity.Vector{queryVec[i]}, common.DefaultFloatVecFieldName, metricType, 1, sp, client.WithSearchQueryConsistencyLevel(entity.ClStrong))
   357  
   358  					// search filter with groupByValue is the top1
   359  					filterTop1Pk, _ := resFilter[0].IDs.GetAsInt64(0)
   360  					//log.Printf("Search top1 with %s: groupByValue: %v, pkValue: %d. The returned pk by filter search is: %d",
   361  					//	groupByField, groupByValue, pkValue, filterTop1Pk)
   362  					if filterTop1Pk == pkValue {
   363  						hitsNum += 1
   364  					}
   365  					total += 1
   366  				}
   367  			}
   368  			// verify hits rate
   369  			hitsRate := float32(hitsNum) / float32(total)
   370  			_str := fmt.Sprintf("GroupBy search with field %s, nq=%d and limit=%d , then hitsNum= %d, hitsRate=%v\n",
   371  				groupByField, common.DefaultNq, common.DefaultTopK, hitsNum, hitsRate)
   372  			log.Println(_str)
   373  			if groupByField != "bool" {
   374  				require.GreaterOrEqualf(t, hitsRate, float32(0.8), _str)
   375  			}
   376  		}
   377  	}
   378  }
   379  
   380  // groupBy + pagination
   381  func TestSearchGroupByPagination(t *testing.T) {
   382  	// create index and load
   383  	idx, _ := entity.NewIndexHNSW(entity.COSINE, 8, 96)
   384  	mc, ctx, collName := prepareDataForGroupBySearch(t, 10, 1000, idx, false)
   385  
   386  	// search params
   387  	queryVec := common.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeFloatVector)
   388  	sp, _ := entity.NewIndexIvfFlatSearchParam(32)
   389  	var offset = int64(10)
   390  
   391  	// search pagination & groupBy
   392  	resGroupByPagination, _ := mc.Search(ctx, collName, []string{}, "", []string{common.DefaultIntFieldName, common.DefaultVarcharFieldName},
   393  		queryVec, common.DefaultFloatVecFieldName, entity.COSINE, common.DefaultTopK, sp,
   394  		client.WithGroupByField(common.DefaultVarcharFieldName), client.WithOffset(offset))
   395  
   396  	common.CheckSearchResult(t, resGroupByPagination, common.DefaultNq, common.DefaultTopK)
   397  
   398  	// search limit=origin limit + offset
   399  	resGroupByDefault, _ := mc.Search(ctx, collName, []string{}, "", []string{common.DefaultIntFieldName, common.DefaultVarcharFieldName},
   400  		queryVec, common.DefaultFloatVecFieldName, entity.COSINE, common.DefaultTopK+int(offset), sp,
   401  		client.WithGroupByField(common.DefaultVarcharFieldName))
   402  	for i := 0; i < common.DefaultNq; i++ {
   403  		require.Equal(t, resGroupByDefault[i].IDs.(*entity.ColumnInt64).Data()[10:], resGroupByPagination[i].IDs.(*entity.ColumnInt64).Data())
   404  	}
   405  }
   406  
   407  // only support: "FLAT", "IVF_FLAT", "HNSW"
   408  func TestSearchGroupByUnsupportedIndex(t *testing.T) {
   409  	t.Parallel()
   410  	for _, idx := range genUnsupportedFloatGroupByIndex() {
   411  		mc, ctx, collName := prepareDataForGroupBySearch(t, 3, 1000, idx, false)
   412  		// groupBy search
   413  		queryVec := common.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeFloatVector)
   414  		sp, _ := entity.NewIndexIvfFlatSearchParam(32)
   415  		_, err := mc.Search(ctx, collName, []string{}, "", []string{common.DefaultIntFieldName, common.DefaultVarcharFieldName},
   416  			queryVec, common.DefaultFloatVecFieldName, entity.MetricType(idx.Params()["metrics_type"]),
   417  			common.DefaultTopK, sp, client.WithGroupByField(common.DefaultVarcharFieldName))
   418  		common.CheckErr(t, err, false, "doesn't support search_group_by")
   419  	}
   420  }
   421  
   422  // FLOAT, DOUBLE, JSON, ARRAY
   423  func TestSearchGroupByUnsupportedDataType(t *testing.T) {
   424  	idxHnsw, _ := entity.NewIndexHNSW(entity.L2, 8, 96)
   425  	mc, ctx, collName := prepareDataForGroupBySearch(t, 1, 1000, idxHnsw, true)
   426  
   427  	// groupBy search with unsupported field type
   428  	queryVec := common.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeFloatVector)
   429  	sp, _ := entity.NewIndexIvfFlatSearchParam(32)
   430  	for _, unsupportedField := range []string{"float", "double", "json", "floatVec", "int8Array", "floatArray"} {
   431  		_, err := mc.Search(ctx, collName, []string{}, "", []string{common.DefaultIntFieldName, common.DefaultVarcharFieldName},
   432  			queryVec, common.DefaultFloatVecFieldName, entity.L2,
   433  			common.DefaultTopK, sp, client.WithGroupByField(unsupportedField))
   434  		common.CheckErr(t, err, false, "unsupported data type")
   435  	}
   436  }
   437  
   438  // groupBy + iterator -> not supported
   439  func TestSearchGroupByIterator(t *testing.T) {
   440  	// TODO: sdk support
   441  }
   442  
   443  // groupBy + range search -> not supported
   444  func TestSearchGroupByRangeSearch(t *testing.T) {
   445  	idxHnsw, _ := entity.NewIndexHNSW(entity.COSINE, 8, 96)
   446  	mc, ctx, collName := prepareDataForGroupBySearch(t, 1, 1000, idxHnsw, true)
   447  
   448  	// groupBy search with range
   449  	queryVec := common.GenSearchVectors(common.DefaultNq, common.DefaultDim, entity.FieldTypeFloatVector)
   450  	sp, _ := entity.NewIndexHNSWSearchParam(50)
   451  	sp.AddRadius(0)
   452  	sp.AddRangeFilter(0.8)
   453  
   454  	// range search
   455  	_, err := mc.Search(ctx, collName, []string{}, "", []string{common.DefaultIntFieldName, common.DefaultVarcharFieldName},
   456  		queryVec, common.DefaultFloatVecFieldName, entity.COSINE, common.DefaultTopK, sp,
   457  		client.WithGroupByField(common.DefaultVarcharFieldName))
   458  
   459  	common.CheckErr(t, err, false, "Not allowed to do range-search when doing search-group-by")
   460  }
   461  
   462  // groupBy + advanced search
   463  func TestSearchGroupByHybridSearch(t *testing.T) {
   464  	// prepare data
   465  	indexHnsw, _ := entity.NewIndexHNSW(entity.L2, 8, 96)
   466  	mc, ctx, collName := prepareDataForGroupBySearch(t, 10, 1000, indexHnsw, false)
   467  
   468  	// hybrid search with groupBy field
   469  	sp, _ := entity.NewIndexHNSWSearchParam(20)
   470  	expr := fmt.Sprintf("%s > 4", common.DefaultIntFieldName)
   471  	queryVec1 := common.GenSearchVectors(1, common.DefaultDim, entity.FieldTypeFloatVector)
   472  	queryVec2 := common.GenSearchVectors(1, common.DefaultDim, entity.FieldTypeFloatVector)
   473  	sReqs := []*client.ANNSearchRequest{
   474  		client.NewANNSearchRequest(common.DefaultFloatVecFieldName, entity.L2, expr, queryVec1, sp, common.DefaultTopK, client.WithOffset(2)),
   475  		client.NewANNSearchRequest(common.DefaultFloatVecFieldName, entity.L2, expr, queryVec2, sp, common.DefaultTopK, client.WithGroupByField("varchar")),
   476  	}
   477  	_, errSearch := mc.HybridSearch(ctx, collName, []string{}, common.DefaultTopK, []string{}, client.NewRRFReranker(), sReqs)
   478  	common.CheckErr(t, errSearch, false, "not support search_group_by operation in the hybrid search")
   479  }