소스 검색

Support doc in doc out (#1181)

Signed-off-by: yhmo <yihua.mo@zilliz.com>
groot 5 달 전
부모
커밋
dd91cf28cb

+ 0 - 0
.attach_pid4051998


+ 1 - 1
src/main/java/io/milvus/param/Constant.java

@@ -35,7 +35,7 @@ public class Constant {
     public static final String ITERATOR_FIELD = "iterator";
     public static final String GROUP_BY_FIELD = "group_by_field";
     public static final String GROUP_SIZE = "group_size";
-    public static final String GROUP_STRICT_SIZE = "group_strict_size";
+    public static final String STRICT_GROUP_SIZE = "strict_group_size";
 
     public static final String INDEX_TYPE = "index_type";
     public static final String METRIC_TYPE = "metric_type";

+ 2 - 2
src/main/java/io/milvus/param/ParamUtils.java

@@ -829,7 +829,7 @@ public class ParamUtils {
             if (requestParam.getStrictGroupSize() != null) {
                 builder.addSearchParams(
                         KeyValuePair.newBuilder()
-                                .setKey(Constant.GROUP_STRICT_SIZE)
+                                .setKey(Constant.STRICT_GROUP_SIZE)
                                 .setValue(requestParam.getStrictGroupSize().toString())
                                 .build());
             }
@@ -962,7 +962,7 @@ public class ParamUtils {
             if (requestParam.getStrictGroupSize() != null) {
                 builder.addRankParams(
                         KeyValuePair.newBuilder()
-                                .setKey(Constant.GROUP_STRICT_SIZE)
+                                .setKey(Constant.STRICT_GROUP_SIZE)
                                 .setValue(requestParam.getStrictGroupSize().toString())
                                 .build());
             }

+ 7 - 6
src/main/java/io/milvus/v2/service/vector/VectorService.java

@@ -23,7 +23,6 @@ import io.milvus.common.utils.GTsDict;
 import io.milvus.exception.ParamException;
 import io.milvus.grpc.*;
 import io.milvus.orm.iterator.*;
-import io.milvus.response.DescCollResponseWrapper;
 import io.milvus.v2.exception.ErrorCode;
 import io.milvus.v2.exception.MilvusClientException;
 import io.milvus.v2.service.BaseService;
@@ -47,7 +46,6 @@ import java.util.concurrent.ConcurrentHashMap;
 public class VectorService extends BaseService {
     Logger logger = LoggerFactory.getLogger(VectorService.class);
     public CollectionService collectionService = new CollectionService();
-    public IndexService indexService = new IndexService();
     private ConcurrentHashMap<String, DescribeCollectionResponse> cacheCollectionInfo = new ConcurrentHashMap<>();
 
     /**
@@ -108,7 +106,8 @@ public class VectorService extends BaseService {
         // TODO: set the database name
         DescribeCollectionResponse descResp = getCollectionInfo(blockingStub, "", request.getCollectionName());
         DataUtils.InsertBuilderWrapper requestBuilder = new DataUtils.InsertBuilderWrapper();
-        MutationResult response = blockingStub.insert(requestBuilder.convertGrpcInsertRequest(request, new DescCollResponseWrapper(descResp)));
+        DescribeCollectionResp descColl = convertUtils.convertDescCollectionResp(descResp);
+        MutationResult response = blockingStub.insert(requestBuilder.convertGrpcInsertRequest(request, descColl));
         cleanCacheIfFailed(response.getStatus(), "", request.getCollectionName());
         rpcUtils.handleResponse(title, response.getStatus());
         GTsDict.getInstance().updateCollectionTs(request.getCollectionName(), response.getTimestamp());
@@ -134,7 +133,8 @@ public class VectorService extends BaseService {
         // TODO: set the database name
         DescribeCollectionResponse descResp = getCollectionInfo(blockingStub, "", request.getCollectionName());
         DataUtils.InsertBuilderWrapper requestBuilder = new DataUtils.InsertBuilderWrapper();
-        MutationResult response = blockingStub.upsert(requestBuilder.convertGrpcUpsertRequest(request, new DescCollResponseWrapper(descResp)));
+        DescribeCollectionResp descColl = convertUtils.convertDescCollectionResp(descResp);
+        MutationResult response = blockingStub.upsert(requestBuilder.convertGrpcUpsertRequest(request, descColl));
         cleanCacheIfFailed(response.getStatus(), "", request.getCollectionName());
         rpcUtils.handleResponse(title, response.getStatus());
         GTsDict.getInstance().updateCollectionTs(request.getCollectionName(), response.getTimestamp());
@@ -151,10 +151,11 @@ public class VectorService extends BaseService {
             throw new MilvusClientException(ErrorCode.INVALID_PARAMS, "filter and ids can't be set at the same time");
         }
 
-        DescribeCollectionResp descR = collectionService.describeCollection(blockingStub, DescribeCollectionReq.builder().collectionName(request.getCollectionName()).build());
 
         if (request.getIds() != null && request.getFilter() == null) {
-            request.setFilter(vectorUtils.getExprById(descR.getPrimaryFieldName(), request.getIds()));
+            DescribeCollectionReq descReq = DescribeCollectionReq.builder().collectionName(request.getCollectionName()).build();
+            DescribeCollectionResp descResp = collectionService.describeCollection(blockingStub, descReq);
+            request.setFilter(vectorUtils.getExprById(descResp.getPrimaryFieldName(), request.getIds()));
         }
         QueryResults response = blockingStub.query(vectorUtils.ConvertToGrpcQueryRequest(request));
         rpcUtils.handleResponse(title, response.getStatus());

+ 20 - 0
src/main/java/io/milvus/v2/service/vector/request/data/EmbeddedText.java

@@ -0,0 +1,20 @@
+package io.milvus.v2.service.vector.request.data;
+
+import io.milvus.grpc.PlaceholderType;
+
+public class EmbeddedText implements BaseVector {
+    private final String data;
+
+    public EmbeddedText(String data) {
+        this.data = data;
+    }
+    @Override
+    public PlaceholderType getPlaceholderType() {
+        return PlaceholderType.VarChar;
+    }
+
+    @Override
+    public Object getData() {
+        return this.data;
+    }
+}

+ 89 - 60
src/main/java/io/milvus/v2/utils/DataUtils.java

@@ -19,17 +19,18 @@
 
 package io.milvus.v2.utils;
 
-import com.google.gson.JsonElement;
-import com.google.gson.JsonNull;
-import com.google.gson.JsonObject;
-import io.milvus.exception.ParamException;
+import com.google.gson.*;
 import io.milvus.grpc.*;
 import io.milvus.param.Constant;
 import io.milvus.param.ParamUtils;
-import io.milvus.param.collection.FieldType;
-import io.milvus.response.DescCollResponseWrapper;
+import io.milvus.v2.exception.ErrorCode;
+import io.milvus.v2.exception.MilvusClientException;
+import io.milvus.v2.service.collection.request.CreateCollectionReq;
+import io.milvus.v2.service.collection.response.DescribeCollectionResp;
 import io.milvus.v2.service.vector.request.InsertReq;
 import io.milvus.v2.service.vector.request.UpsertReq;
+import lombok.Builder;
+import lombok.Getter;
 import lombok.NonNull;
 
 import java.util.*;
@@ -41,7 +42,7 @@ public class DataUtils {
         private UpsertRequest.Builder upsertBuilder;
 
         public InsertRequest convertGrpcInsertRequest(@NonNull InsertReq requestParam,
-                                                      DescCollResponseWrapper wrapper) {
+                                                      DescribeCollectionResp descColl) {
             String collectionName = requestParam.getCollectionName();
 
             // generate insert request builder
@@ -51,18 +52,18 @@ public class DataUtils {
                     .setBase(msgBase)
                     .setNumRows(requestParam.getData().size());
             upsertBuilder = null;
-            fillFieldsData(requestParam, wrapper);
+            fillFieldsData(requestParam, descColl);
             return insertBuilder.build();
         }
 
         public UpsertRequest convertGrpcUpsertRequest(@NonNull UpsertReq requestParam,
-                                                      DescCollResponseWrapper wrapper) {
+                                                      DescribeCollectionResp descColl) {
             String collectionName = requestParam.getCollectionName();
 
             // currently, not allow to upsert for collection whose primary key is auto-generated
-            FieldType pk = wrapper.getPrimaryField();
-            if (pk.isAutoID()) {
-                throw new ParamException(String.format("Upsert don't support autoID==True, collection: %s",
+            CreateCollectionReq.FieldSchema primaryField = descColl.getCollectionSchema().getField(descColl.getPrimaryFieldName());
+            if (primaryField.getAutoID() == Boolean.TRUE) {
+                throw new MilvusClientException(ErrorCode.INVALID_PARAMS, String.format("Upsert don't support autoID==True, collection: %s",
                         requestParam.getCollectionName()));
             }
 
@@ -73,7 +74,7 @@ public class DataUtils {
                     .setBase(msgBase)
                     .setNumRows(requestParam.getData().size());
             insertBuilder = null;
-            fillFieldsData(requestParam, wrapper);
+            fillFieldsData(requestParam, descColl);
             return upsertBuilder.build();
         }
 
@@ -93,20 +94,24 @@ public class DataUtils {
             }
         }
 
-        private void fillFieldsData(UpsertReq requestParam, DescCollResponseWrapper wrapper) {
-            // set partition name only when there is no partition key field
-            String partitionName = requestParam.getPartitionName();
-            boolean isPartitionKeyEnabled = false;
-            for (FieldType fieldType : wrapper.getFields()) {
-                if (fieldType.isPartitionKey()) {
-                    isPartitionKeyEnabled = true;
-                    break;
+        private static boolean hasPartitionKey(DescribeCollectionResp descColl) {
+            CreateCollectionReq.CollectionSchema collectionSchema = descColl.getCollectionSchema();
+            List<CreateCollectionReq.FieldSchema> fieldsList = collectionSchema.getFieldSchemaList();
+            for (CreateCollectionReq.FieldSchema field : fieldsList) {
+                if (field.getIsPartitionKey() == Boolean.TRUE) {
+                    return true;
                 }
             }
-            if (isPartitionKeyEnabled) {
+            return false;
+        }
+
+        private void fillFieldsData(UpsertReq requestParam, DescribeCollectionResp descColl) {
+            // set partition name only when there is no partition key field
+            String partitionName = requestParam.getPartitionName();
+            if (hasPartitionKey(descColl)) {
                 if (partitionName != null && !partitionName.isEmpty()) {
                     String msg = "Collection " + requestParam.getCollectionName() + " has partition key, not allow to specify partition name";
-                    throw new ParamException(msg);
+                    throw new MilvusClientException(ErrorCode.INVALID_PARAMS, msg);
                 }
             } else if (partitionName != null) {
                 this.setPartitionName(partitionName);
@@ -114,23 +119,16 @@ public class DataUtils {
 
             // convert insert data
             List<JsonObject> rowFields = requestParam.getData();
-            checkAndSetRowData(wrapper, rowFields);
+            checkAndSetRowData(descColl, rowFields);
         }
 
-        private void fillFieldsData(InsertReq requestParam, DescCollResponseWrapper wrapper) {
+        private void fillFieldsData(InsertReq requestParam, DescribeCollectionResp descColl) {
             // set partition name only when there is no partition key field
             String partitionName = requestParam.getPartitionName();
-            boolean isPartitionKeyEnabled = false;
-            for (FieldType fieldType : wrapper.getFields()) {
-                if (fieldType.isPartitionKey()) {
-                    isPartitionKeyEnabled = true;
-                    break;
-                }
-            }
-            if (isPartitionKeyEnabled) {
+            if (hasPartitionKey(descColl)) {
                 if (partitionName != null && !partitionName.isEmpty()) {
                     String msg = "Collection " + requestParam.getCollectionName() + " has partition key, not allow to specify partition name";
-                    throw new ParamException(msg);
+                    throw new MilvusClientException(ErrorCode.INVALID_PARAMS, msg);
                 }
             } else if (partitionName != null) {
                 this.setPartitionName(partitionName);
@@ -138,53 +136,69 @@ public class DataUtils {
 
             // convert insert data
             List<JsonObject> rowFields = requestParam.getData();
-            checkAndSetRowData(wrapper, rowFields);
+            checkAndSetRowData(descColl, rowFields);
         }
 
-        private void checkAndSetRowData(DescCollResponseWrapper wrapper, List<JsonObject> rows) {
-            List<FieldType> fieldTypes = wrapper.getFields();
+        private void checkAndSetRowData(DescribeCollectionResp descColl, List<JsonObject> rows) {
+            CreateCollectionReq.CollectionSchema collectionSchema = descColl.getCollectionSchema();
+            List<CreateCollectionReq.Function> functionsList = collectionSchema.getFunctionList();
+            List<String> outputFieldNames = new ArrayList<>();
+            for (CreateCollectionReq.Function function : functionsList) {
+                outputFieldNames.addAll(function.getOutputFieldNames());
+            }
 
-            Map<String, ParamUtils.InsertDataInfo> nameInsertInfo = new HashMap<>();
-            ParamUtils.InsertDataInfo insertDynamicDataInfo = ParamUtils.InsertDataInfo.builder().fieldType(
-                            FieldType.newBuilder()
-                                    .withName(Constant.DYNAMIC_FIELD_NAME)
-                                    .withDataType(DataType.JSON)
-                                    .withIsDynamic(true)
+            List<CreateCollectionReq.FieldSchema> fieldsList = collectionSchema.getFieldSchemaList();
+            Map<String, InsertDataInfo> nameInsertInfo = new HashMap<>();
+            InsertDataInfo insertDynamicDataInfo = InsertDataInfo.builder().field(
+                            CreateCollectionReq.FieldSchema.builder()
+                                    .name(Constant.DYNAMIC_FIELD_NAME)
+                                    .dataType(io.milvus.v2.common.DataType.JSON)
                                     .build())
                     .data(new LinkedList<>()).build();
             for (JsonObject row : rows) {
-                for (FieldType fieldType : fieldTypes) {
-                    String fieldName = fieldType.getName();
-                    ParamUtils.InsertDataInfo insertDataInfo = nameInsertInfo.getOrDefault(fieldName, ParamUtils.InsertDataInfo.builder()
-                            .fieldType(fieldType).data(new LinkedList<>()).build());
+                for (CreateCollectionReq.FieldSchema field : fieldsList) {
+                    String fieldName = field.getName();
+                    InsertDataInfo insertDataInfo = nameInsertInfo.getOrDefault(fieldName, InsertDataInfo.builder()
+                            .field(field).data(new LinkedList<>()).build());
 
                     // check normalField
                     JsonElement rowFieldData = row.get(fieldName);
                     if (rowFieldData == null) {
-                        // check if autoId
-                        if (fieldType.isAutoID()) {
+                        // if the field is auto-id, no need to provide value
+                        if (field.getAutoID() == Boolean.TRUE) {
+                            continue;
+                        }
+
+                        // if the field is an output field of doc-in-doc-out, no need to provide value
+                        if (outputFieldNames.contains(field.getName())) {
                             continue;
                         }
+
                         // if the field doesn't have default value, require user provide the value
-                        if (!fieldType.isNullable() && fieldType.getDefaultValue() == null) {
-                            String msg = String.format("The field: %s is not provided.", fieldType.getName());
-                            throw new ParamException(msg);
+                        if (!field.getIsNullable() && field.getDefaultValue() == null) {
+                            String msg = String.format("The field: %s is not provided.", field.getName());
+                            throw new MilvusClientException(ErrorCode.INVALID_PARAMS, msg);
                         }
 
                         rowFieldData = JsonNull.INSTANCE;
                     }
 
-                    if (fieldType.isAutoID()) {
+                    if (field.getAutoID() == Boolean.TRUE) {
                         String msg = String.format("The primary key: %s is auto generated, no need to input.", fieldName);
-                        throw new ParamException(msg);
+                        throw new MilvusClientException(ErrorCode.INVALID_PARAMS, msg);
                     }
-                    Object fieldValue = ParamUtils.checkFieldValue(fieldType, rowFieldData);
+
+                    // here we convert the v2 FieldSchema to grpc.FieldSchema then to v1 FieldType
+                    // the reason is the logic in ParamUtils.checkFieldValue is complicated, we don't intend to
+                    // write duplicate code here
+                    FieldSchema grpcField = SchemaUtils.convertToGrpcFieldSchema(field);
+                    Object fieldValue = ParamUtils.checkFieldValue(ParamUtils.ConvertField(grpcField), rowFieldData);
                     insertDataInfo.getData().add(fieldValue);
                     nameInsertInfo.put(fieldName, insertDataInfo);
                 }
 
                 // deal with dynamicField
-                if (wrapper.getEnableDynamicField()) {
+                if (collectionSchema.isEnableDynamicField()) {
                     JsonObject dynamicField = new JsonObject();
                     for (String rowFieldName : row.keySet()) {
                         if (!nameInsertInfo.containsKey(rowFieldName)) {
@@ -196,12 +210,27 @@ public class DataUtils {
             }
 
             for (String fieldNameKey : nameInsertInfo.keySet()) {
-                ParamUtils.InsertDataInfo insertDataInfo = nameInsertInfo.get(fieldNameKey);
-                this.addFieldsData(ParamUtils.genFieldData(insertDataInfo.getFieldType(), insertDataInfo.getData()));
+                InsertDataInfo insertDataInfo = nameInsertInfo.get(fieldNameKey);
+                // here we convert the v2 FieldSchema to grpc.FieldSchema then to v1 FieldType
+                // the reason is the logic in ParamUtils.genFieldData is complicated, we don't intend to
+                // write duplicate code here
+                FieldSchema grpcField = SchemaUtils.convertToGrpcFieldSchema(insertDataInfo.getField());
+                this.addFieldsData(ParamUtils.genFieldData(ParamUtils.ConvertField(grpcField), insertDataInfo.getData()));
             }
-            if (wrapper.getEnableDynamicField()) {
-                this.addFieldsData(ParamUtils.genFieldData(insertDynamicDataInfo.getFieldType(), insertDynamicDataInfo.getData(), Boolean.TRUE));
+            if (collectionSchema.isEnableDynamicField()) {
+                // here we convert the v2 FieldSchema to grpc.FieldSchema then to v1 FieldType
+                // the reason is the logic in ParamUtils.genFieldData is complicated, we don't intend to
+                // write duplicate code here
+                FieldSchema grpcField = SchemaUtils.convertToGrpcFieldSchema(insertDynamicDataInfo.getField());
+                this.addFieldsData(ParamUtils.genFieldData(ParamUtils.ConvertField(grpcField), insertDynamicDataInfo.getData(), Boolean.TRUE));
             }
         }
     }
+
+    @Builder
+    @Getter
+    public static class InsertDataInfo {
+        private final CreateCollectionReq.FieldSchema field;
+        private final LinkedList<Object> data;
+    }
 }

+ 68 - 25
src/main/java/io/milvus/v2/utils/VectorUtils.java

@@ -28,6 +28,8 @@ import io.milvus.exception.ParamException;
 import io.milvus.grpc.*;
 import io.milvus.param.Constant;
 import io.milvus.param.ParamUtils;
+import io.milvus.v2.exception.ErrorCode;
+import io.milvus.v2.exception.MilvusClientException;
 import io.milvus.v2.service.vector.request.*;
 import io.milvus.v2.service.vector.request.ranker.BaseRanker;
 import io.milvus.v2.service.vector.request.data.*;
@@ -105,6 +107,31 @@ public class VectorUtils {
         }
     }
 
+    private static ByteString convertPlaceholder(List<Object> data, PlaceholderType placeType) {
+        if (placeType == PlaceholderType.VarChar) {
+            List<ByteString> byteStrings = new ArrayList<>();
+            for (Object obj : data) {
+                byteStrings.add(ByteString.copyFrom(((String)obj).getBytes()));
+            }
+            PlaceholderValue.Builder pldBuilder = PlaceholderValue.newBuilder()
+                    .setTag(Constant.VECTOR_TAG)
+                    .setType(placeType);
+            byteStrings.forEach(pldBuilder::addValues);
+
+            PlaceholderValue plv = pldBuilder.build();
+            PlaceholderGroup placeholderGroup = PlaceholderGroup.newBuilder()
+                    .addPlaceholders(plv)
+                    .build();
+            return placeholderGroup.toByteString();
+        } else {
+            try {
+                return ParamUtils.convertPlaceholder(data, placeType);
+            } catch (ParamException e) {
+                throw new MilvusClientException(ErrorCode.INVALID_PARAMS, e.getMessage());
+            }
+        }
+    }
+
     public SearchRequest ConvertToGrpcSearchRequest(SearchReq request) {
         SearchRequest.Builder builder = SearchRequest.newBuilder()
                 .setCollectionName(request.getCollectionName());
@@ -112,31 +139,37 @@ public class VectorUtils {
             request.getPartitionNames().forEach(builder::addPartitionNames);
         }
 
-
-        // prepare target vectors
+        // prepare target, the input could be vectors or string list for doc-in-doc-out
         List<BaseVector> vectors = request.getData();
         if (vectors.isEmpty()) {
-            throw new ParamException("Target vectors list of search request is empty.");
+            throw new MilvusClientException(ErrorCode.INVALID_PARAMS, "Target data list of search request is empty.");
         }
+
+        // the elements must be all-vector or all-string
         PlaceholderType plType = vectors.get(0).getPlaceholderType();
         List<Object> data = new ArrayList<>();
         for (BaseVector vector : vectors) {
             if (vector.getPlaceholderType() != plType) {
-                throw new ParamException("Different types of target vectors in a search request is not allowed.");
+                throw new MilvusClientException(ErrorCode.INVALID_PARAMS,
+                        "Different types of target vectors in a search request is not allowed.");
             }
             data.add(vector.getData());
         }
 
-        ByteString byteStr = ParamUtils.convertPlaceholder(data, plType);
+        ByteString byteStr = convertPlaceholder(data, plType);
         builder.setPlaceholderGroup(byteStr);
+        builder.setNq(vectors.size());
 
         // search parameters
+        if (StringUtils.isNotEmpty(request.getAnnsField())) {
+            builder.addSearchParams(
+                    KeyValuePair.newBuilder()
+                            .setKey(Constant.VECTOR_FIELD)
+                            .setValue(request.getAnnsField())
+                            .build());
+        }
+
         builder.addSearchParams(
-                        KeyValuePair.newBuilder()
-                                .setKey(Constant.VECTOR_FIELD)
-                                .setValue(request.getAnnsField())
-                                .build())
-                .addSearchParams(
                         KeyValuePair.newBuilder()
                                 .setKey(Constant.TOP_K)
                                 .setValue(String.valueOf(request.getTopK()))
@@ -174,7 +207,7 @@ public class VectorUtils {
                                 .setValue(searchParams)
                                 .build());
             } catch (IllegalArgumentException e) {
-                throw new ParamException(e.getMessage() + e.getCause().getMessage());
+                throw new MilvusClientException(ErrorCode.INVALID_PARAMS, e.getMessage() + e.getCause().getMessage());
             }
         }
 
@@ -195,7 +228,7 @@ public class VectorUtils {
             if (request.getStrictGroupSize() != null) {
                 builder.addSearchParams(
                         KeyValuePair.newBuilder()
-                                .setKey(Constant.GROUP_STRICT_SIZE)
+                                .setKey(Constant.STRICT_GROUP_SIZE)
                                 .setValue(request.getStrictGroupSize().toString())
                                 .build());
             }
@@ -238,7 +271,8 @@ public class VectorUtils {
             BoolArray.Builder builder = BoolArray.newBuilder();
             array.forEach(val->{
                 if (!(val instanceof Boolean)) {
-                    throw new ParamException("Filter expression template is a list, the first value is Boolean, but some elements are not Boolean");
+                    throw new MilvusClientException(ErrorCode.INVALID_PARAMS,
+                            "Filter expression template is a list, the first value is Boolean, but some elements are not Boolean");
                 }
                 builder.addData((Boolean)val);
             });
@@ -247,7 +281,8 @@ public class VectorUtils {
             LongArray.Builder builder = LongArray.newBuilder();
             array.forEach(val->{
                 if (!(val instanceof Integer) && !(val instanceof Long)) {
-                    throw new ParamException("Filter expression template is a list, the first value is Integer/Long, but some elements are not Integer/Long");
+                    throw new MilvusClientException(ErrorCode.INVALID_PARAMS,
+                            "Filter expression template is a list, the first value is Integer/Long, but some elements are not Integer/Long");
                 }
                 builder.addData((val instanceof Integer) ? (Integer)val : (Long)val);
             });
@@ -256,7 +291,8 @@ public class VectorUtils {
             DoubleArray.Builder builder = DoubleArray.newBuilder();
             array.forEach(val->{
                 if (!(val instanceof Double)) {
-                    throw new ParamException("Filter expression template is a list, the first value is Double, but some elements are not Double");
+                    throw new MilvusClientException(ErrorCode.INVALID_PARAMS,
+                            "Filter expression template is a list, the first value is Double, but some elements are not Double");
                 }
                 builder.addData((Double)val);
             });
@@ -265,7 +301,8 @@ public class VectorUtils {
             StringArray.Builder builder = StringArray.newBuilder();
             array.forEach(val->{
                 if (!(val instanceof String)) {
-                    throw new ParamException("Filter expression template is a list, the first value is String, but some elements are not String");
+                    throw new MilvusClientException(ErrorCode.INVALID_PARAMS,
+                            "Filter expression template is a list, the first value is String, but some elements are not String");
                 }
                 builder.addData((String)val);
             });
@@ -274,7 +311,8 @@ public class VectorUtils {
             JSONArray.Builder builder = JSONArray.newBuilder();
             array.forEach(val->{
                 if (!(val instanceof JsonElement)) {
-                    throw new ParamException("Filter expression template is a list, the first value is JsonElement, but some elements are not JsonElement");
+                    throw new MilvusClientException(ErrorCode.INVALID_PARAMS,
+                            "Filter expression template is a list, the first value is JsonElement, but some elements are not JsonElement");
                 }
                 String str = JsonUtils.toJson((JsonElement)val);
                 builder.addData(ByteString.copyFromUtf8(str));
@@ -284,7 +322,8 @@ public class VectorUtils {
             TemplateArrayValueArray.Builder builder = TemplateArrayValueArray.newBuilder();
             array.forEach(val->{
                 if (!(val instanceof List)) {
-                    throw new ParamException("Filter expression template is a list, the first value is List, but some elements are not List");
+                    throw new MilvusClientException(ErrorCode.INVALID_PARAMS,
+                            "Filter expression template is a list, the first value is List, but some elements are not List");
                 }
                 List<?> subArrary = (List<?>)val;
                 builder.addData(deduceTemplateArray(subArrary));
@@ -292,7 +331,8 @@ public class VectorUtils {
 
             return TemplateArrayValue.newBuilder().setArrayData(builder.build()).build();
         } else {
-            throw new ParamException("Unsupported value type for filter expression template.");
+            throw new MilvusClientException(ErrorCode.INVALID_PARAMS,
+                    "Unsupported value type for filter expression template.");
         }
     }
 
@@ -320,7 +360,8 @@ public class VectorUtils {
                     .setArrayVal(tav)
                     .build();
         } else {
-            throw new ParamException("Unsupported value type for filter expression template.");
+            throw new MilvusClientException(ErrorCode.INVALID_PARAMS,
+                    "Unsupported value type for filter expression template.");
         }
     }
 
@@ -330,13 +371,15 @@ public class VectorUtils {
         // prepare target vectors
         List<BaseVector> vectors = annSearchReq.getVectors();
         if (vectors.isEmpty()) {
-            throw new ParamException("Target vectors list of search request is empty.");
+            throw new MilvusClientException(ErrorCode.INVALID_PARAMS,
+                    "Target vectors list of search request is empty.");
         }
         PlaceholderType plType = vectors.get(0).getPlaceholderType();
         List<Object> data = new ArrayList<>();
         for (BaseVector vector : vectors) {
             if (vector.getPlaceholderType() != plType) {
-                throw new ParamException("Different types of target vectors in a search request is not allowed.");
+                throw new MilvusClientException(ErrorCode.INVALID_PARAMS,
+                        "Different types of target vectors in a search request is not allowed.");
             }
             data.add(vector.getData());
         }
@@ -400,7 +443,7 @@ public class VectorUtils {
         }
 
         if (request.getSearchRequests() == null || request.getSearchRequests().isEmpty()) {
-            throw new ParamException("Sub-request list is empty.");
+            throw new MilvusClientException(ErrorCode.INVALID_PARAMS, "Sub-request list is empty.");
         }
 
         for (AnnSearchReq req : request.getSearchRequests()) {
@@ -411,7 +454,7 @@ public class VectorUtils {
         // set ranker
         BaseRanker ranker = request.getRanker();
         if (request.getRanker() == null) {
-            throw new ParamException("Ranker is null.");
+            throw new MilvusClientException(ErrorCode.INVALID_PARAMS, "Ranker is null.");
         }
 
         Map<String, String> props = ranker.getProperties();
@@ -439,7 +482,7 @@ public class VectorUtils {
             if (request.getStrictGroupSize() != null) {
                 builder.addRankParams(
                         KeyValuePair.newBuilder()
-                                .setKey(Constant.GROUP_STRICT_SIZE)
+                                .setKey(Constant.STRICT_GROUP_SIZE)
                                 .setValue(request.getStrictGroupSize().toString())
                                 .build());
             }

+ 47 - 0
src/test/java/io/milvus/v2/client/MilvusClientV2DockerTest.java

@@ -2038,6 +2038,7 @@ class MilvusClientV2DockerTest {
         client.createCollection(requestCreate);
         System.out.println("Collection created");
 
+        // check the schema
         DescribeCollectionResp descResp = client.describeCollection(DescribeCollectionReq.builder()
                 .collectionName(randomCollectionName)
                 .build());
@@ -2060,5 +2061,51 @@ class MilvusClientV2DockerTest {
         Assertions.assertEquals("text", functions.get(0).getInputFieldNames().get(0));
         Assertions.assertEquals(1, functions.get(0).getOutputFieldNames().size());
         Assertions.assertEquals("sparse", functions.get(0).getOutputFieldNames().get(0));
+
+        // insert by row-based
+        List<String> texts = Arrays.asList(
+                "this is a AI world",
+                "milvus is a vector database for AI application",
+                "hello zilliz");
+        List<JsonObject> data = new ArrayList<>();
+        for (int i = 0; i < texts.size(); i++) {
+            JsonObject row = new JsonObject();
+            row.addProperty("id", i);
+            row.add("dense", JsonUtils.toJsonTree(generateFolatVector(dimension)));
+            row.addProperty("text", texts.get(i));
+            data.add(row);
+        }
+
+        InsertResp insertResp = client.insert(InsertReq.builder()
+                .collectionName(randomCollectionName)
+                .data(data)
+                .build());
+        Assertions.assertEquals(3, insertResp.getInsertCnt());
+
+        // get row count
+        long rowCount = getRowCount(randomCollectionName);
+        Assertions.assertEquals(texts.size(), rowCount);
+
+        // search
+        SearchResp searchResp = client.search(SearchReq.builder()
+                .collectionName(randomCollectionName)
+                .annsField("sparse")
+                .data(Collections.singletonList(new EmbeddedText("Vector and AI")))
+                .topK(10)
+                .outputFields(Lists.newArrayList("*"))
+                .metricType(IndexParam.MetricType.BM25)
+                .build());
+        List<List<SearchResp.SearchResult>> searchResults = searchResp.getSearchResults();
+        Assertions.assertEquals(1, searchResults.size());
+        List<SearchResp.SearchResult> firstResults = searchResults.get(0);
+        Assertions.assertEquals(2, firstResults.size());
+        SearchResp.SearchResult firstRes = firstResults.get(0);
+        Map<String, Object> entity = firstRes.getEntity();
+        Assertions.assertEquals(1L, entity.get("id"));
+        Assertions.assertEquals(texts.get(1), entity.get("text"));
+        System.out.println("Search results:");
+        for (SearchResp.SearchResult result : firstResults) {
+            System.out.println(result);
+        }
     }
 }