Browse Source

Iterator for V2 (#949)

Signed-off-by: yhmo <yihua.mo@zilliz.com>
groot 1 year ago
parent
commit
a7eb64880d
36 changed files with 1438 additions and 444 deletions
  1. 2 2
      examples/main/java/io/milvus/v1/BinaryVectorExample.java
  2. 35 45
      examples/main/java/io/milvus/v1/BulkWriterExample.java
  3. 4 2
      examples/main/java/io/milvus/v1/CommonUtils.java
  4. 8 13
      examples/main/java/io/milvus/v1/GeneralExample.java
  5. 2 9
      examples/main/java/io/milvus/v1/HighLevelExample.java
  6. 2 2
      examples/main/java/io/milvus/v1/IteratorExample.java
  7. 2 2
      examples/main/java/io/milvus/v1/RBACExample.java
  8. 3 3
      examples/main/java/io/milvus/v1/SimpleExample.java
  9. 17 15
      examples/main/java/io/milvus/v1/TLSExample.java
  10. 161 0
      examples/main/java/io/milvus/v2/IteratorExample.java
  11. 88 0
      examples/main/java/io/milvus/v2/SimpleExample.java
  12. 0 0
      examples/main/resources/tls/gen.sh
  13. 0 0
      examples/main/resources/tls/openssl.cnf
  14. 71 44
      src/main/java/io/milvus/client/AbstractMilvusGrpcClient.java
  15. 111 0
      src/main/java/io/milvus/orm/iterator/IteratorAdapterV2.java
  16. 21 0
      src/main/java/io/milvus/orm/iterator/QueryIterator.java
  17. 27 8
      src/main/java/io/milvus/orm/iterator/SearchIterator.java
  18. 10 0
      src/main/java/io/milvus/response/FieldDataWrapper.java
  19. 3 3
      src/main/java/io/milvus/response/QueryResultsWrapper.java
  20. 4 4
      src/main/java/io/milvus/response/SearchResultsWrapper.java
  21. 34 3
      src/main/java/io/milvus/response/basic/RowRecordWrapper.java
  22. 23 0
      src/main/java/io/milvus/v2/client/MilvusClientV2.java
  23. 0 115
      src/main/java/io/milvus/v2/examples/Simple.java
  24. 0 136
      src/main/java/io/milvus/v2/examples/Simple_Schema.java
  25. 12 2
      src/main/java/io/milvus/v2/service/collection/CollectionService.java
  26. 1 1
      src/main/java/io/milvus/v2/service/collection/response/DescribeCollectionResp.java
  27. 3 0
      src/main/java/io/milvus/v2/service/index/response/DescribeIndexResp.java
  28. 85 11
      src/main/java/io/milvus/v2/service/vector/VectorService.java
  29. 32 0
      src/main/java/io/milvus/v2/service/vector/request/QueryIteratorReq.java
  30. 41 0
      src/main/java/io/milvus/v2/service/vector/request/SearchIteratorReq.java
  31. 4 0
      src/main/java/io/milvus/v2/service/vector/request/data/BinaryVec.java
  32. 9 0
      src/main/java/io/milvus/v2/service/vector/request/data/FloatVec.java
  33. 5 1
      src/main/java/io/milvus/v2/utils/ConvertUtils.java
  34. 5 0
      src/main/java/io/milvus/v2/utils/SchemaUtils.java
  35. 69 0
      src/test/java/io/milvus/client/MilvusClientDockerTest.java
  36. 544 23
      src/test/java/io/milvus/v2/client/MilvusClientV2DockerTest.java

+ 2 - 2
examples/main/java/io/milvus/BinaryVectorExample.java → examples/main/java/io/milvus/v1/BinaryVectorExample.java

@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package io.milvus;
+package io.milvus.v1;
 
 import com.google.gson.Gson;
 import com.google.gson.JsonObject;
@@ -37,7 +37,7 @@ import java.nio.ByteBuffer;
 import java.util.*;
 
 public class BinaryVectorExample {
-    private static final String COLLECTION_NAME = "java_sdk_example_binary";
+    private static final String COLLECTION_NAME = "java_sdk_example_binary_vector_v1";
     private static final String ID_FIELD = "id";
     private static final String VECTOR_FIELD = "vector";
 

+ 35 - 45
examples/main/java/io/milvus/BulkWriterExample.java → examples/main/java/io/milvus/v1/BulkWriterExample.java

@@ -1,4 +1,22 @@
-package io.milvus;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package io.milvus.v1;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.dataformat.csv.CsvMapper;
@@ -7,12 +25,12 @@ import com.google.common.collect.Lists;
 import com.google.gson.Gson;
 import com.google.gson.JsonObject;
 import com.google.gson.reflect.TypeToken;
-import io.milvus.bulkwriter.BulkWriter;
-import io.milvus.bulkwriter.CloudImport;
-import io.milvus.bulkwriter.LocalBulkWriter;
-import io.milvus.bulkwriter.LocalBulkWriterParam;
-import io.milvus.bulkwriter.RemoteBulkWriter;
-import io.milvus.bulkwriter.RemoteBulkWriterParam;
+import io.milvus.bulkwriter.*;
+import io.milvus.bulkwriter.common.clientenum.BulkFileType;
+import io.milvus.bulkwriter.common.clientenum.CloudStorage;
+import io.milvus.bulkwriter.common.utils.GeneratorUtils;
+import io.milvus.bulkwriter.common.utils.ImportUtils;
+import io.milvus.bulkwriter.common.utils.ParquetReaderUtils;
 import io.milvus.bulkwriter.connect.AzureConnectParam;
 import io.milvus.bulkwriter.connect.S3ConnectParam;
 import io.milvus.bulkwriter.connect.StorageConnectParam;
@@ -21,34 +39,12 @@ import io.milvus.bulkwriter.response.GetImportProgressResponse;
 import io.milvus.bulkwriter.response.ListImportJobsResponse;
 import io.milvus.client.MilvusClient;
 import io.milvus.client.MilvusServiceClient;
-import io.milvus.bulkwriter.common.clientenum.BulkFileType;
-import io.milvus.bulkwriter.common.clientenum.CloudStorage;
 import io.milvus.common.utils.ExceptionUtils;
-import io.milvus.bulkwriter.common.utils.GeneratorUtils;
-import io.milvus.bulkwriter.common.utils.ImportUtils;
-import io.milvus.bulkwriter.common.utils.ParquetReaderUtils;
-import io.milvus.grpc.DataType;
-import io.milvus.grpc.GetCollectionStatisticsResponse;
-import io.milvus.grpc.GetImportStateResponse;
-import io.milvus.grpc.ImportResponse;
-import io.milvus.grpc.ImportState;
-import io.milvus.grpc.KeyValuePair;
-import io.milvus.grpc.QueryResults;
-import io.milvus.param.ConnectParam;
-import io.milvus.param.IndexType;
-import io.milvus.param.MetricType;
-import io.milvus.param.R;
-import io.milvus.param.RpcStatus;
+import io.milvus.grpc.*;
+import io.milvus.param.*;
 import io.milvus.param.bulkinsert.BulkInsertParam;
 import io.milvus.param.bulkinsert.GetBulkInsertStateParam;
-import io.milvus.param.collection.CollectionSchemaParam;
-import io.milvus.param.collection.CreateCollectionParam;
-import io.milvus.param.collection.DropCollectionParam;
-import io.milvus.param.collection.FieldType;
-import io.milvus.param.collection.FlushParam;
-import io.milvus.param.collection.GetCollectionStatisticsParam;
-import io.milvus.param.collection.HasCollectionParam;
-import io.milvus.param.collection.LoadCollectionParam;
+import io.milvus.param.collection.*;
 import io.milvus.param.dml.QueryParam;
 import io.milvus.param.index.CreateIndexParam;
 import io.milvus.response.GetCollStatResponseWrapper;
@@ -68,19 +64,13 @@ import java.util.List;
 import java.util.Optional;
 import java.util.concurrent.TimeUnit;
 
-import static io.milvus.BulkWriterExample.MilvusConsts.HOST;
-import static io.milvus.BulkWriterExample.MilvusConsts.PORT;
-import static io.milvus.BulkWriterExample.MilvusConsts.USER_NAME;
-
 
 public class BulkWriterExample {
     // milvus
-    public static class MilvusConsts {
-        public static final String HOST = "127.0.0.1";
-        public static final Integer PORT = 19530;
-        public static final String USER_NAME = "user.name";
-        public static final String PASSWORD = "password";
-    }
+    public static final String HOST = "127.0.0.1";
+    public static final Integer PORT = 19530;
+    public static final String USER_NAME = "user.name";
+    public static final String PASSWORD = "password";
 
     private static final Gson GSON_INSTANCE = new Gson();
 
@@ -145,8 +135,8 @@ public class BulkWriterExample {
         public static final String OBJECT_SECRET_KEY = "_your_storage_secret_key_";
     }
 
-    private static final String SIMPLE_COLLECTION_NAME = "for_bulkwriter";
-    private static final String ALL_TYPES_COLLECTION_NAME = "all_types_for_bulkwriter";
+    private static final String SIMPLE_COLLECTION_NAME = "java_sdk_bulkwriter_simple_v1";
+    private static final String ALL_TYPES_COLLECTION_NAME = "java_sdk_bulkwriter_all_v1";
     private static final Integer DIM = 512;
     private MilvusClient milvusClient;
 
@@ -170,7 +160,7 @@ public class BulkWriterExample {
         ConnectParam connectParam = ConnectParam.newBuilder()
                 .withHost(HOST)
                 .withPort(PORT)
-                .withAuthorization(USER_NAME, MilvusConsts.PASSWORD)
+                .withAuthorization(USER_NAME, PASSWORD)
                 .build();
         milvusClient = new MilvusServiceClient(connectParam);
         System.out.println("\nConnected");

+ 4 - 2
examples/main/java/io/milvus/CommonUtils.java → examples/main/java/io/milvus/v1/CommonUtils.java

@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package io.milvus;
+package io.milvus.v1;
 
 import io.milvus.param.R;
 
@@ -67,9 +67,11 @@ public class CommonUtils {
         return vectors;
     }
 
+    /////////////////////////////////////////////////////////////////////////////////////////////////////
     public static ByteBuffer generateBinaryVector(int dimension) {
         Random ran = new Random();
         int byteCount = dimension / 8;
+        // binary vector doesn't care endian since each byte is independent
         ByteBuffer vector = ByteBuffer.allocate(byteCount);
         for (int i = 0; i < byteCount; ++i) {
             vector.put((byte) ran.nextInt(Byte.MAX_VALUE));
@@ -85,4 +87,4 @@ public class CommonUtils {
         }
         return vectors;
     }
-}
+}

+ 8 - 13
examples/main/java/io/milvus/GeneralExample.java → examples/main/java/io/milvus/v1/GeneralExample.java

@@ -16,21 +16,21 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
-package io.milvus;
-
+package io.milvus.v1;
 
 import com.google.gson.Gson;
 import com.google.gson.JsonObject;
 import io.milvus.client.MilvusClient;
 import io.milvus.client.MilvusServiceClient;
 import io.milvus.common.clientenum.ConsistencyLevelEnum;
-import io.milvus.common.utils.JacksonUtils;
 import io.milvus.grpc.*;
 import io.milvus.param.*;
 import io.milvus.param.collection.*;
 import io.milvus.param.control.ManualCompactParam;
-import io.milvus.param.dml.*;
+import io.milvus.param.dml.DeleteParam;
+import io.milvus.param.dml.InsertParam;
+import io.milvus.param.dml.QueryParam;
+import io.milvus.param.dml.SearchParam;
 import io.milvus.param.index.*;
 import io.milvus.param.partition.*;
 import io.milvus.response.*;
@@ -38,12 +38,6 @@ import io.milvus.response.*;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 
-///////////////////////////////////////////////////////////////////////////////////////////////////////////
-// Note:
-// Due do a technical limitation, the Milvus 2.0 not allow to create multi-vector-fields within a collection.
-// So this example only create a single vector field in the collection, but we suppose the next version
-// should support this function.
-///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
 public class GeneralExample {
     private static final MilvusClient milvusClient;
@@ -60,7 +54,7 @@ public class GeneralExample {
         milvusClient = new MilvusServiceClient(connectParam).withRetry(retryParam);
     }
 
-    private static final String COLLECTION_NAME = "java_sdk_example_general";
+    private static final String COLLECTION_NAME = "java_sdk_example_general_v1";
     private static final String ID_FIELD = "userID";
     private static final String VECTOR_FIELD = "userFace";
     private static final Integer VECTOR_DIM = 64;
@@ -72,7 +66,7 @@ public class GeneralExample {
 
     private static final Integer SEARCH_K = 5;
     private static final String SEARCH_PARAM = "{\"nprobe\":10}";
-
+    
 
     private R<RpcStatus> createCollection(long timeoutMilliseconds) {
         System.out.println("========== createCollection() ==========");
@@ -370,6 +364,7 @@ public class GeneralExample {
                 .withCollectionName(COLLECTION_NAME)
                 .withExpr(expr)
                 .withOutFields(fields)
+                .withLimit(10L)
                 .build();
         R<QueryResults> response = milvusClient.query(test);
         CommonUtils.handleResponseStatus(response);

+ 2 - 9
examples/main/java/io/milvus/HighLevelExample.java → examples/main/java/io/milvus/v1/HighLevelExample.java

@@ -16,8 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
-package io.milvus;
+package io.milvus.v1;
 
 import com.google.gson.Gson;
 import com.google.gson.JsonObject;
@@ -40,12 +39,6 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
 
-///////////////////////////////////////////////////////////////////////////////////////////////////////////
-// Note:
-// Due do a technical limitation, the Milvus 2.0 not allow to create multi-vector-fields within a collection.
-// So this example only create a single vector field in the collection, but we suppose the next version
-// should support this function.
-///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
 public class HighLevelExample {
     private static final MilvusServiceClient milvusClient;
@@ -59,7 +52,7 @@ public class HighLevelExample {
         milvusClient = new MilvusServiceClient(connectParam);
     }
 
-    private static final String COLLECTION_NAME = "java_sdk_example_hl";
+    private static final String COLLECTION_NAME = "java_sdk_example_highlevel_v1";
     private static final String ID_FIELD = "userID";
     private static final String VECTOR_FIELD = "userFace";
     private static final String USER_JSON_FIELD = "userJson";

+ 2 - 2
examples/main/java/io/milvus/IteratorExample.java → examples/main/java/io/milvus/v1/IteratorExample.java

@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package io.milvus;
+package io.milvus.v1;
 
 import com.google.common.collect.Lists;
 import io.milvus.client.MilvusClient;
@@ -61,7 +61,7 @@ public class IteratorExample {
         milvusClient = new MilvusServiceClient(connectParam).withRetry(retryParam);
     }
 
-    private static final String COLLECTION_NAME = "test_iterator";
+    private static final String COLLECTION_NAME = "java_sdk_example_iterator_v1";
     private static final String ID_FIELD = "userID";
     private static final String VECTOR_FIELD = "userFace";
     private static final Integer VECTOR_DIM = 8;

+ 2 - 2
examples/main/java/io/milvus/RBACExample.java → examples/main/java/io/milvus/v1/RBACExample.java

@@ -16,8 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
-package io.milvus;
+package io.milvus.v1;
 
 import io.milvus.client.MilvusServiceClient;
 import io.milvus.grpc.ListCredUsersResponse;
@@ -30,6 +29,7 @@ import io.milvus.param.credential.ListCredUsersParam;
 import io.milvus.param.role.*;
 import org.apache.commons.lang3.Validate;
 
+
 public class RBACExample {
     private static final MilvusServiceClient milvusClient;
 

+ 3 - 3
examples/main/java/io/milvus/SimpleExample.java → examples/main/java/io/milvus/v1/SimpleExample.java

@@ -16,8 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
-package io.milvus;
+package io.milvus.v1;
 
 import com.google.gson.Gson;
 import com.google.gson.JsonObject;
@@ -30,8 +29,9 @@ import io.milvus.param.index.*;
 import io.milvus.response.*;
 import java.util.*;
 
+
 public class SimpleExample {
-    private static final String COLLECTION_NAME = "java_sdk_example_simple";
+    private static final String COLLECTION_NAME = "java_sdk_example_simple_v1";
     private static final String ID_FIELD = "book_id";
     private static final String VECTOR_FIELD = "book_intro";
     private static final String TITLE_FIELD = "book_title";

+ 17 - 15
examples/main/java/io/milvus/TLSExample.java → examples/main/java/io/milvus/v1/TLSExample.java

@@ -16,17 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
-package io.milvus;
+package io.milvus.v1;
 
 import io.milvus.client.MilvusServiceClient;
-import io.milvus.grpc.*;
-import io.milvus.param.*;
-import io.milvus.param.collection.*;
-import io.milvus.param.dml.*;
-import io.milvus.param.index.*;
-import io.milvus.response.*;
-import java.util.*;
+import io.milvus.grpc.CheckHealthResponse;
+import io.milvus.param.ConnectParam;
+import io.milvus.param.R;
+
+import java.io.File;
+import java.net.URL;
 
 
 // Note: read the following description before running this example
@@ -54,12 +52,14 @@ import java.util.*;
 public class TLSExample {
 
     private static void oneWayAuth() {
-        String path = ClassLoader.getSystemResource("").getPath();
+        ClassLoader classLoader = BulkWriterExample.class.getClassLoader();
+        URL resourceUrl = classLoader.getResource("tls");
+        String path = new File(resourceUrl.getFile()).getAbsolutePath();
         ConnectParam connectParam = ConnectParam.newBuilder()
                 .withHost("localhost")
                 .withPort(19530)
                 .withServerName("localhost")
-                .withServerPemPath(path + "/tls/server.pem")
+                .withServerPemPath(path + "/server.pem")
                 .build();
         MilvusServiceClient milvusClient = new MilvusServiceClient(connectParam);
 
@@ -72,14 +72,16 @@ public class TLSExample {
     }
 
     private static void twoWayAuth() {
-        String path = ClassLoader.getSystemResource("").getPath();
+        ClassLoader classLoader = BulkWriterExample.class.getClassLoader();
+        URL resourceUrl = classLoader.getResource("tls");
+        String path = new File(resourceUrl.getFile()).getAbsolutePath();
         ConnectParam connectParam = ConnectParam.newBuilder()
                 .withHost("localhost")
                 .withPort(19530)
                 .withServerName("localhost")
-                .withCaPemPath(path + "/tls/ca.pem")
-                .withClientKeyPath(path + "/tls/client.key")
-                .withClientPemPath(path + "/tls/client.pem")
+                .withCaPemPath(path + "/ca.pem")
+                .withClientKeyPath(path + "/client.key")
+                .withClientPemPath(path + "/client.pem")
                 .build();
         MilvusServiceClient milvusClient = new MilvusServiceClient(connectParam);
 

+ 161 - 0
examples/main/java/io/milvus/v2/IteratorExample.java

@@ -0,0 +1,161 @@
+package io.milvus.v2;
+
+import com.google.common.collect.Lists;
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import io.milvus.orm.iterator.QueryIterator;
+import io.milvus.orm.iterator.SearchIterator;
+import io.milvus.response.QueryResultsWrapper;
+import io.milvus.v1.CommonUtils;
+import io.milvus.v2.client.ConnectConfig;
+import io.milvus.v2.client.MilvusClientV2;
+import io.milvus.v2.common.ConsistencyLevel;
+import io.milvus.v2.common.DataType;
+import io.milvus.v2.common.IndexParam;
+import io.milvus.v2.service.collection.request.AddFieldReq;
+import io.milvus.v2.service.collection.request.CreateCollectionReq;
+import io.milvus.v2.service.collection.request.DropCollectionReq;
+import io.milvus.v2.service.vector.request.InsertReq;
+import io.milvus.v2.service.vector.request.QueryIteratorReq;
+import io.milvus.v2.service.vector.request.QueryReq;
+import io.milvus.v2.service.vector.request.SearchIteratorReq;
+import io.milvus.v2.service.vector.request.data.FloatVec;
+import io.milvus.v2.service.vector.response.InsertResp;
+import io.milvus.v2.service.vector.response.QueryResp;
+
+import java.util.*;
+
+public class IteratorExample {
+    private static final String COLLECTION_NAME = "java_sdk_example_iterator_v2";
+    private static final String ID_FIELD = "userID";
+    private static final String AGE_FIELD = "userAge";
+    private static final String VECTOR_FIELD = "userFace";
+    private static final Integer VECTOR_DIM = 128;
+
+    public static void main(String[] args) {
+        ConnectConfig config = ConnectConfig.builder()
+                .uri("http://localhost:19530")
+                .build();
+        MilvusClientV2 client = new MilvusClientV2(config);
+
+        // create collection
+        CreateCollectionReq.CollectionSchema collectionSchema = CreateCollectionReq.CollectionSchema.builder()
+                .build();
+        collectionSchema.addField(AddFieldReq.builder()
+                .fieldName(ID_FIELD)
+                .dataType(DataType.Int64)
+                .isPrimaryKey(Boolean.TRUE)
+                .autoID(Boolean.FALSE)
+                .build());
+        collectionSchema.addField(AddFieldReq.builder()
+                .fieldName(AGE_FIELD)
+                .dataType(DataType.Int32)
+                .build());
+        collectionSchema.addField(AddFieldReq.builder()
+                .fieldName(VECTOR_FIELD)
+                .dataType(DataType.FloatVector)
+                .dimension(VECTOR_DIM)
+                .build());
+
+        List<IndexParam> indexParams = new ArrayList<>();
+        indexParams.add(IndexParam.builder()
+                .fieldName(VECTOR_FIELD)
+                .indexType(IndexParam.IndexType.FLAT)
+                .metricType(IndexParam.MetricType.L2)
+                .build());
+
+        CreateCollectionReq requestCreate = CreateCollectionReq.builder()
+                .collectionName(COLLECTION_NAME)
+                .collectionSchema(collectionSchema)
+                .indexParams(indexParams)
+                .build();
+        client.createCollection(requestCreate);
+
+        // insert rows
+        long count = 10000;
+        List<JsonObject> rowsData = new ArrayList<>();
+        Random ran = new Random();
+        Gson gson = new Gson();
+        for (long i = 0L; i < count; ++i) {
+            JsonObject row = new JsonObject();
+            row.addProperty(ID_FIELD, i);
+            row.addProperty(AGE_FIELD, ran.nextInt(99));
+            row.add(VECTOR_FIELD, gson.toJsonTree(CommonUtils.generateFloatVector(VECTOR_DIM)));
+
+            rowsData.add(row);
+        }
+        InsertResp insertResp = client.insert(InsertReq.builder()
+                .collectionName(COLLECTION_NAME)
+                .data(rowsData)
+                .build());
+
+        // check row count
+        QueryResp queryResp = client.query(QueryReq.builder()
+                .collectionName(COLLECTION_NAME)
+                .filter("")
+                .outputFields(Collections.singletonList("count(*)"))
+                .consistencyLevel(ConsistencyLevel.STRONG)
+                .build());
+        List<QueryResp.QueryResult> queryResults = queryResp.getQueryResults();
+        System.out.printf("Inserted row count: %d\n", queryResults.size());
+
+        // search iterator
+        SearchIterator searchIterator = client.searchIterator(SearchIteratorReq.builder()
+                .collectionName(COLLECTION_NAME)
+                .outputFields(Lists.newArrayList(AGE_FIELD))
+                .batchSize(50L)
+                .vectorFieldName(VECTOR_FIELD)
+                .vectors(Collections.singletonList(new FloatVec(CommonUtils.generateFloatVector(VECTOR_DIM))))
+                .expr(String.format("%s > 50 && %s < 100", AGE_FIELD, AGE_FIELD))
+                .params("{\"range_filter\": 15.0, \"radius\": 20.0}")
+                .topK(300)
+                .metricType(IndexParam.MetricType.L2)
+                .consistencyLevel(ConsistencyLevel.BOUNDED)
+                .build());
+
+        int counter = 0;
+        while (true) {
+            List<QueryResultsWrapper.RowRecord> res = searchIterator.next();
+            if (res.isEmpty()) {
+                System.out.println("Search iteration finished, close");
+                searchIterator.close();
+                break;
+            }
+
+            for (QueryResultsWrapper.RowRecord record : res) {
+                System.out.println(record);
+                counter++;
+            }
+        }
+        System.out.println(String.format("%d search results returned\n", counter));
+
+        // query iterator
+        QueryIterator queryIterator = client.queryIterator(QueryIteratorReq.builder()
+                .collectionName(COLLECTION_NAME)
+                .expr(String.format("%s < 300", ID_FIELD))
+                .outputFields(Lists.newArrayList(ID_FIELD, AGE_FIELD))
+                .batchSize(50L)
+                .offset(5)
+                .limit(400)
+                .consistencyLevel(ConsistencyLevel.BOUNDED)
+                .build());
+
+        counter = 0;
+        while (true) {
+            List<QueryResultsWrapper.RowRecord> res = queryIterator.next();
+            if (res.isEmpty()) {
+                System.out.println("query iteration finished, close");
+                queryIterator.close();
+                break;
+            }
+
+            for (QueryResultsWrapper.RowRecord record : res) {
+                System.out.println(record);
+                counter++;
+            }
+        }
+        System.out.println(String.format("%d query results returned", counter));
+
+        client.dropCollection(DropCollectionReq.builder().collectionName(COLLECTION_NAME).build());
+    }
+}

+ 88 - 0
examples/main/java/io/milvus/v2/SimpleExample.java

@@ -0,0 +1,88 @@
+package io.milvus.v2;
+
+import com.google.gson.*;
+import io.milvus.v2.client.*;
+import io.milvus.v2.common.ConsistencyLevel;
+import io.milvus.v2.service.collection.request.CreateCollectionReq;
+import io.milvus.v2.service.collection.request.DropCollectionReq;
+import io.milvus.v2.service.vector.request.*;
+import io.milvus.v2.service.vector.request.data.FloatVec;
+import io.milvus.v2.service.vector.response.*;
+
+import java.util.*;
+
+public class SimpleExample {
+    public static void main(String[] args) {
+
+        ConnectConfig config = ConnectConfig.builder()
+                .uri("http://localhost:19530")
+                .build();
+        MilvusClientV2 client = new MilvusClientV2(config);
+
+        String collectionName = "java_sdk_example_simple_v2";
+        // drop collection if exists
+        client.dropCollection(DropCollectionReq.builder()
+                .collectionName(collectionName)
+                .build());
+
+        // quickly create a collection with "id" field and "vector" field
+        client.createCollection(CreateCollectionReq.builder()
+                .collectionName(collectionName)
+                .dimension(4)
+                .build());
+        System.out.printf("Collection '%s' created\n", collectionName);
+
+        // insert some data
+        List<JsonObject> rows = new ArrayList<>();
+        Gson gson = new Gson();
+        for (int i = 0; i < 100; i++) {
+            JsonObject row = new JsonObject();
+            row.addProperty("id", i);
+            row.add("vector", gson.toJsonTree(new float[]{i, (float) i /2, (float) i /3, (float) i /4}));
+            row.addProperty(String.format("dynamic_%d", i), "this is dynamic value"); // this value is stored in dynamic field
+            rows.add(row);
+        }
+        InsertResp insertR = client.insert(InsertReq.builder()
+                .collectionName(collectionName)
+                .data(rows)
+                .build());
+        System.out.printf("%d rows inserted\n", insertR.getInsertCnt());
+
+        // get row count
+        QueryResp countR = client.query(QueryReq.builder()
+                .collectionName(collectionName)
+                .filter("")
+                .outputFields(Collections.singletonList("count(*)"))
+                .consistencyLevel(ConsistencyLevel.STRONG)
+                .build());
+        System.out.printf("%d rows persisted\n", (long)countR.getQueryResults().get(0).getEntity().get("count(*)"));
+
+        // retrieve
+        List<Object> ids = Arrays.asList(1L, 50L);
+        GetResp getR = client.get(GetReq.builder()
+                .collectionName(collectionName)
+                .ids(ids)
+                .outputFields(Collections.singletonList("*"))
+                .build());
+        System.out.println("\nRetrieve results:");
+        for (QueryResp.QueryResult result : getR.getGetResults()) {
+            System.out.println(result.getEntity());
+        }
+
+        // search
+        SearchResp searchR = client.search(SearchReq.builder()
+                .collectionName(collectionName)
+                .data(Collections.singletonList(new FloatVec(new float[]{1.0f, 1.0f, 1.0f, 1.0f})))
+                .filter("id < 100")
+                .topK(10)
+                .outputFields(Collections.singletonList("*"))
+                .build());
+        List<List<SearchResp.SearchResult>> searchResults = searchR.getSearchResults();
+        System.out.println("\nSearch results:");
+        for (List<SearchResp.SearchResult> results : searchResults) {
+            for (SearchResp.SearchResult result : results) {
+                System.out.printf("ID: %d, Score: %f, %s\n", (long)result.getId(), result.getScore(), result.getEntity().toString());
+            }
+        }
+    }
+}

+ 0 - 0
examples/main/java/io/milvus/tls/gen.sh → examples/main/resources/tls/gen.sh


+ 0 - 0
examples/main/java/io/milvus/tls/openssl.cnf → examples/main/resources/tls/openssl.cnf


+ 71 - 44
src/main/java/io/milvus/client/AbstractMilvusGrpcClient.java

@@ -54,6 +54,7 @@ import org.slf4j.LoggerFactory;
 import javax.annotation.Nonnull;
 import java.nio.charset.StandardCharsets;
 import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 
@@ -61,6 +62,7 @@ public abstract class AbstractMilvusGrpcClient implements MilvusClient {
 
     protected static final Logger logger = LoggerFactory.getLogger(AbstractMilvusGrpcClient.class);
     protected LogLevel logLevel = LogLevel.Info;
+    private ConcurrentHashMap<String, DescribeCollectionResponse> cacheCollectionInfo = new ConcurrentHashMap<>();
 
     protected abstract MilvusServiceGrpc.MilvusServiceBlockingStub blockingStub();
 
@@ -68,6 +70,56 @@ public abstract class AbstractMilvusGrpcClient implements MilvusClient {
 
     protected abstract boolean clientIsReady();
 
+    /**
+     * This method is for insert/upsert requests to reduce the rpc call of describeCollection()
+     * Always try to get the collection info from cache.
+     * If the cache doesn't have the collection info, call describeCollection() and cache it.
+     * If insert/upsert get server error, remove the cached collection info.
+     */
+    private DescribeCollectionResponse getCollectionInfo(String databaseName, String collectionName) {
+        String key = combineCacheKey(databaseName, collectionName);
+        DescribeCollectionResponse info = cacheCollectionInfo.get(key);
+        if (info == null) {
+            String msg = String.format("Fail to describe collection '%s'", collectionName);
+            DescribeCollectionRequest.Builder builder = DescribeCollectionRequest.newBuilder()
+                    .setCollectionName(collectionName);
+            if (StringUtils.isNotEmpty(databaseName)) {
+                builder.setDbName(databaseName);
+                msg = String.format("Fail to describe collection '%s' in database '%s'",
+                        collectionName, databaseName);
+            }
+            DescribeCollectionRequest describeCollectionRequest = builder.build();
+            DescribeCollectionResponse response = blockingStub().describeCollection(describeCollectionRequest);
+            handleResponse(msg, response.getStatus());
+            info = response;
+            cacheCollectionInfo.put(key, info);
+        }
+
+        return info;
+    }
+
+    private String combineCacheKey(String databaseName, String collectionName) {
+        if (collectionName == null || StringUtils.isBlank(collectionName)) {
+            throw new ParamException("Collection name is empty, not able to get collection info.");
+        }
+        String key = collectionName;
+        if (StringUtils.isNotEmpty(databaseName)) {
+            key = String.format("%s|%s", databaseName, collectionName);
+        }
+        return key;
+    }
+
+    /**
+     * insert/upsert return an error, but is not a RateLimit error,
+     * clean the cache so that the next insert will call describeCollection() to get the latest info.
+     */
+    private void cleanCacheIfFailed(Status status, String databaseName, String collectionName) {
+        if ((status.getCode() != 0 && status.getCode() != 8) ||
+                (!status.getErrorCode().equals(ErrorCode.Success) && status.getErrorCode() != ErrorCode.RateLimit)) {
+            cacheCollectionInfo.remove(combineCacheKey(databaseName, collectionName));
+        }
+    }
+
     private void waitForLoadingCollection(String databaseName, String collectionName, List<String> partitionNames,
                                           long waitingInterval, long timeout) throws IllegalResponseException {
         long tsBegin = System.currentTimeMillis();
@@ -528,6 +580,7 @@ public abstract class AbstractMilvusGrpcClient implements MilvusClient {
 
             Status response = blockingStub().dropCollection(dropCollectionRequest);
             handleResponse(title, response);
+            cacheCollectionInfo.remove(combineCacheKey(requestParam.getDatabaseName(), requestParam.getCollectionName()));
             return R.success(new RpcStatus(RpcStatus.SUCCESS_MSG));
         } catch (StatusRuntimeException e) {
             logError("{} RPC failed! Exception:{}", title, e);
@@ -1417,17 +1470,12 @@ public abstract class AbstractMilvusGrpcClient implements MilvusClient {
         String title = String.format("InsertRequest collectionName:%s", requestParam.getCollectionName());
 
         try {
-            DescribeCollectionParam.Builder builder = DescribeCollectionParam.newBuilder()
-                    .withDatabaseName(requestParam.getDatabaseName())
-                    .withCollectionName(requestParam.getCollectionName());
-            R<DescribeCollectionResponse> descResp = describeCollection(builder.build());
-            if (descResp.getStatus() != R.Status.Success.getCode()) {
-                return R.failed(descResp.getException());
-            }
-
-            DescCollResponseWrapper wrapper = new DescCollResponseWrapper(descResp.getData());
+            DescribeCollectionResponse descResp = getCollectionInfo(requestParam.getDatabaseName(),
+                    requestParam.getCollectionName());
+            DescCollResponseWrapper wrapper = new DescCollResponseWrapper(descResp);
             ParamUtils.InsertBuilderWrapper builderWraper = new ParamUtils.InsertBuilderWrapper(requestParam, wrapper);
             MutationResult response = blockingStub().insert(builderWraper.buildInsertRequest());
+            cleanCacheIfFailed(response.getStatus(), requestParam.getDatabaseName(), requestParam.getCollectionName());
             handleResponse(title, response.getStatus());
             return R.success(response);
         } catch (StatusRuntimeException e) {
@@ -1450,15 +1498,9 @@ public abstract class AbstractMilvusGrpcClient implements MilvusClient {
         logDebug(requestParam.toString());
         String title = String.format("InsertAsyncRequest collectionName:%s", requestParam.getCollectionName());
 
-        DescribeCollectionParam.Builder builder = DescribeCollectionParam.newBuilder()
-                .withDatabaseName(requestParam.getDatabaseName())
-                .withCollectionName(requestParam.getCollectionName());
-        R<DescribeCollectionResponse> descResp = describeCollection(builder.build());
-        if (descResp.getStatus() != R.Status.Success.getCode()) {
-            return Futures.immediateFuture(R.failed(descResp.getException()));
-        }
-
-        DescCollResponseWrapper wrapper = new DescCollResponseWrapper(descResp.getData());
+        DescribeCollectionResponse descResp = getCollectionInfo(requestParam.getDatabaseName(),
+                requestParam.getCollectionName());
+        DescCollResponseWrapper wrapper = new DescCollResponseWrapper(descResp);
         ParamUtils.InsertBuilderWrapper builderWraper = new ParamUtils.InsertBuilderWrapper(requestParam, wrapper);
         ListenableFuture<MutationResult> response = futureStub().insert(builderWraper.buildInsertRequest());
 
@@ -1467,6 +1509,7 @@ public abstract class AbstractMilvusGrpcClient implements MilvusClient {
                 new FutureCallback<MutationResult>() {
                     @Override
                     public void onSuccess(MutationResult result) {
+                        cleanCacheIfFailed(result.getStatus(), requestParam.getDatabaseName(), requestParam.getCollectionName());
                         if (result.getStatus().getErrorCode() == ErrorCode.Success) {
                             logDebug("{} successfully!", title);
                         } else {
@@ -1504,17 +1547,12 @@ public abstract class AbstractMilvusGrpcClient implements MilvusClient {
         String title = String.format("UpsertRequest collectionName:%s", requestParam.getCollectionName());
 
         try {
-            DescribeCollectionParam.Builder builder = DescribeCollectionParam.newBuilder()
-                    .withDatabaseName(requestParam.getDatabaseName())
-                    .withCollectionName(requestParam.getCollectionName());
-            R<DescribeCollectionResponse> descResp = describeCollection(builder.build());
-            if (descResp.getStatus() != R.Status.Success.getCode()) {
-                return R.failed(descResp.getException());
-            }
-
-            DescCollResponseWrapper wrapper = new DescCollResponseWrapper(descResp.getData());
+            DescribeCollectionResponse descResp = getCollectionInfo(requestParam.getDatabaseName(),
+                    requestParam.getCollectionName());
+            DescCollResponseWrapper wrapper = new DescCollResponseWrapper(descResp);
             ParamUtils.InsertBuilderWrapper builderWraper = new ParamUtils.InsertBuilderWrapper(requestParam, wrapper);
             MutationResult response = blockingStub().upsert(builderWraper.buildUpsertRequest());
+            cleanCacheIfFailed(response.getStatus(), requestParam.getDatabaseName(), requestParam.getCollectionName());
             handleResponse(title, response.getStatus());
             return R.success(response);
         } catch (StatusRuntimeException e) {
@@ -1536,15 +1574,9 @@ public abstract class AbstractMilvusGrpcClient implements MilvusClient {
         logDebug(requestParam.toString());
         String title = String.format("UpsertAsyncRequest collectionName:%s", requestParam.getCollectionName());
 
-        DescribeCollectionParam.Builder builder = DescribeCollectionParam.newBuilder()
-                .withDatabaseName(requestParam.getDatabaseName())
-                .withCollectionName(requestParam.getCollectionName());
-        R<DescribeCollectionResponse> descResp = describeCollection(builder.build());
-        if (descResp.getStatus() != R.Status.Success.getCode()) {
-            return Futures.immediateFuture(R.failed(descResp.getException()));
-        }
-
-        DescCollResponseWrapper wrapper = new DescCollResponseWrapper(descResp.getData());
+        DescribeCollectionResponse descResp = getCollectionInfo(requestParam.getDatabaseName(),
+                requestParam.getCollectionName());
+        DescCollResponseWrapper wrapper = new DescCollResponseWrapper(descResp);
         ParamUtils.InsertBuilderWrapper builderWraper = new ParamUtils.InsertBuilderWrapper(requestParam, wrapper);
         ListenableFuture<MutationResult> response = futureStub().upsert(builderWraper.buildUpsertRequest());
 
@@ -1553,6 +1585,7 @@ public abstract class AbstractMilvusGrpcClient implements MilvusClient {
                 new FutureCallback<MutationResult>() {
                     @Override
                     public void onSuccess(MutationResult result) {
+                        cleanCacheIfFailed(result.getStatus(), requestParam.getDatabaseName(), requestParam.getCollectionName());
                         if (result.getStatus().getErrorCode() == ErrorCode.Success) {
                             logDebug("{} successfully!", title);
                         } else {
@@ -2912,14 +2945,8 @@ public abstract class AbstractMilvusGrpcClient implements MilvusClient {
         String title = String.format("DeleteIdsRequest collectionName:%s", requestParam.getCollectionName());
 
         try {
-            DescribeCollectionParam.Builder builder = DescribeCollectionParam.newBuilder()
-                    .withCollectionName(requestParam.getCollectionName());
-            R<DescribeCollectionResponse> descResp = describeCollection(builder.build());
-            if (descResp.getStatus() != R.Status.Success.getCode()) {
-                logError("Failed to describe collection: {}", requestParam.getCollectionName());
-                return R.failed(descResp.getException());
-            }
-            DescCollResponseWrapper wrapper = new DescCollResponseWrapper(descResp.getData());
+            DescribeCollectionResponse descResp = getCollectionInfo("", requestParam.getCollectionName());
+            DescCollResponseWrapper wrapper = new DescCollResponseWrapper(descResp);
 
             String expr = VectorUtils.convertPksExpr(requestParam.getPrimaryIds(), wrapper);
             DeleteParam deleteParam = DeleteParam.newBuilder()

+ 111 - 0
src/main/java/io/milvus/orm/iterator/IteratorAdapterV2.java

@@ -0,0 +1,111 @@
+package io.milvus.orm.iterator;
+
+import io.milvus.common.clientenum.ConsistencyLevelEnum;
+import io.milvus.exception.ParamException;
+import io.milvus.grpc.DataType;
+import io.milvus.grpc.PlaceholderType;
+import io.milvus.param.MetricType;
+import io.milvus.param.collection.FieldType;
+import io.milvus.param.dml.SearchIteratorParam;
+import io.milvus.param.dml.QueryIteratorParam;
+import io.milvus.v2.common.IndexParam;
+import io.milvus.v2.service.collection.request.CreateCollectionReq;
+import io.milvus.v2.service.vector.request.QueryIteratorReq;
+import io.milvus.v2.service.vector.request.SearchIteratorReq;
+import io.milvus.v2.service.vector.request.data.BaseVector;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.SortedMap;
+
+public class IteratorAdapterV2 {
+    public static QueryIteratorParam convertV2Req(QueryIteratorReq queryIteratorReq) {
+        return QueryIteratorParam.newBuilder()
+                .withDatabaseName(queryIteratorReq.getDatabaseName())
+                .withCollectionName(queryIteratorReq.getCollectionName())
+                .withPartitionNames(queryIteratorReq.getPartitionNames())
+                .withExpr(queryIteratorReq.getExpr())
+                .withOutFields(queryIteratorReq.getOutputFields())
+                .withConsistencyLevel(ConsistencyLevelEnum.valueOf(queryIteratorReq.getConsistencyLevel().name()))
+                .withOffset(queryIteratorReq.getOffset())
+                .withLimit(queryIteratorReq.getLimit())
+                .withIgnoreGrowing(queryIteratorReq.isIgnoreGrowing())
+                .withBatchSize(queryIteratorReq.getBatchSize())
+                .build();
+    }
+    public static SearchIteratorParam convertV2Req(SearchIteratorReq searchIteratorReq) {
+        MetricType metricType = MetricType.None;
+        if (searchIteratorReq.getMetricType() != IndexParam.MetricType.INVALID) {
+            metricType = MetricType.valueOf(searchIteratorReq.getMetricType().name());
+        }
+
+        SearchIteratorParam.Builder builder = SearchIteratorParam.newBuilder()
+                .withDatabaseName(searchIteratorReq.getDatabaseName())
+                .withCollectionName(searchIteratorReq.getCollectionName())
+                .withPartitionNames(searchIteratorReq.getPartitionNames())
+                .withVectorFieldName(searchIteratorReq.getVectorFieldName())
+                .withMetricType(metricType)
+                .withTopK(searchIteratorReq.getTopK())
+                .withExpr(searchIteratorReq.getExpr())
+                .withOutFields(searchIteratorReq.getOutputFields())
+                .withRoundDecimal(searchIteratorReq.getRoundDecimal())
+                .withParams(searchIteratorReq.getParams())
+                .withIgnoreGrowing(searchIteratorReq.isIgnoreGrowing())
+                .withBatchSize(searchIteratorReq.getBatchSize());
+
+        if (searchIteratorReq.getConsistencyLevel() != null) {
+            builder.withConsistencyLevel(ConsistencyLevelEnum.valueOf(searchIteratorReq.getConsistencyLevel().name()));
+        }
+
+        List<BaseVector> vectors = searchIteratorReq.getVectors();
+        PlaceholderType plType = vectors.get(0).getPlaceholderType();
+        for (BaseVector vector : vectors) {
+            if (vector.getPlaceholderType() != plType) {
+                throw new ParamException("Different types of target vectors in a search request is not allowed.");
+            }
+        }
+
+        switch (plType) {
+            case FloatVector: {
+                List<List<Float>> data = new ArrayList<>();
+                vectors.forEach(vector->data.add((List<Float>)vector.getData()));
+                builder.withVectors(data);
+                break;
+            }
+            case BinaryVector: {
+                List<ByteBuffer> data = new ArrayList<>();
+                vectors.forEach(vector->data.add((ByteBuffer)vector.getData()));
+                builder.withVectors(data);
+                break;
+            }
+            default:
+                throw new ParamException("Unsupported vector type.");
+        }
+
+        return builder.build();
+    }
+
+    public static FieldType convertV2Field(CreateCollectionReq.FieldSchema schema) {
+        FieldType.Builder builder = FieldType.newBuilder()
+                .withName(schema.getName())
+                .withDataType(DataType.valueOf(schema.getDataType().name()))
+                .withPrimaryKey(schema.getIsPrimaryKey())
+                .withAutoID(schema.getAutoID())
+                .withPartitionKey(schema.getIsPartitionKey());
+
+        if (schema.getDimension() != null) {
+            builder.withDimension(schema.getDimension());
+        }
+        if (schema.getMaxLength() != null) {
+            builder.withMaxLength(schema.getMaxLength());
+        }
+        if (schema.getMaxCapacity() != null) {
+            builder.withMaxCapacity(schema.getMaxLength());
+        }
+        if (schema.getElementType() != null) {
+            builder.withElementType(DataType.valueOf(schema.getElementType().name()));
+        }
+        return builder.build();
+    }
+}

+ 21 - 0
src/main/java/io/milvus/orm/iterator/QueryIterator.java

@@ -28,6 +28,8 @@ import io.milvus.param.collection.FieldType;
 import io.milvus.param.dml.QueryIteratorParam;
 import io.milvus.param.dml.QueryParam;
 import io.milvus.response.QueryResultsWrapper;
+import io.milvus.v2.service.collection.request.CreateCollectionReq;
+import io.milvus.v2.service.vector.request.QueryIteratorReq;
 import io.milvus.v2.utils.RpcUtils;
 import org.apache.commons.lang3.StringUtils;
 
@@ -68,6 +70,25 @@ public class QueryIterator {
         seek();
     }
 
+    public QueryIterator(QueryIteratorReq queryIteratorReq,
+                         MilvusServiceGrpc.MilvusServiceBlockingStub blockingStub,
+                         CreateCollectionReq.FieldSchema primaryField) {
+        this.iteratorCache = new IteratorCache();
+        this.blockingStub = blockingStub;
+        IteratorAdapterV2 adapter = new IteratorAdapterV2();
+        this.queryIteratorParam = adapter.convertV2Req(queryIteratorReq);
+        this.primaryField = adapter.convertV2Field(primaryField);
+
+
+        this.batchSize = (int) queryIteratorParam.getBatchSize();
+        this.expr = queryIteratorParam.getExpr();
+        this.limit = queryIteratorParam.getLimit();
+        this.offset = queryIteratorParam.getOffset();
+        this.rpcUtils = new RpcUtils();
+
+        seek();
+    }
+
     private void seek() {
         this.cacheIdInUse = NO_CACHE_ID;
         if (offset == 0) {

+ 27 - 8
src/main/java/io/milvus/orm/iterator/SearchIterator.java

@@ -6,10 +6,7 @@ import com.google.common.collect.Lists;
 import io.milvus.common.utils.ExceptionUtils;
 import io.milvus.common.utils.JacksonUtils;
 import io.milvus.exception.ParamException;
-import io.milvus.grpc.DataType;
-import io.milvus.grpc.MilvusServiceGrpc;
-import io.milvus.grpc.SearchRequest;
-import io.milvus.grpc.SearchResults;
+import io.milvus.grpc.*;
 import io.milvus.param.MetricType;
 import io.milvus.param.ParamUtils;
 import io.milvus.param.collection.FieldType;
@@ -17,14 +14,14 @@ import io.milvus.param.dml.SearchIteratorParam;
 import io.milvus.param.dml.SearchParam;
 import io.milvus.response.QueryResultsWrapper;
 import io.milvus.response.SearchResultsWrapper;
+import io.milvus.v2.service.collection.request.CreateCollectionReq;
+import io.milvus.v2.service.vector.request.SearchIteratorReq;
 import io.milvus.v2.utils.RpcUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.text.DecimalFormat;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 import static io.milvus.param.Constant.DEFAULT_SEARCH_EXTENSION_RATE;
 import static io.milvus.param.Constant.EF;
@@ -79,6 +76,28 @@ public class SearchIterator {
         initSearchIterator();
     }
 
+    // to support V2
+    public SearchIterator(SearchIteratorReq searchIteratorReq,
+                          MilvusServiceGrpc.MilvusServiceBlockingStub blockingStub,
+                          CreateCollectionReq.FieldSchema primaryField) {
+        this.iteratorCache = new IteratorCache();
+        this.blockingStub = blockingStub;
+        IteratorAdapterV2 adapter = new IteratorAdapterV2();
+        this.searchIteratorParam = adapter.convertV2Req(searchIteratorReq);
+        this.primaryField = adapter.convertV2Field(primaryField);
+        this.metricType = this.searchIteratorParam.getMetricType();
+
+        this.batchSize = (int) this.searchIteratorParam.getBatchSize();
+        this.expr = this.searchIteratorParam.getExpr();
+        this.topK = this.searchIteratorParam.getTopK();
+        this.rpcUtils = new RpcUtils();
+
+        initParams();
+        checkForSpecialIndexParam();
+        checkRmRangeSearchParameters();
+        initSearchIterator();
+    }
+
     public List<QueryResultsWrapper.RowRecord> next() {
         // 0. check reached limit
         if (!initSuccess || checkReachedLimit()) {
@@ -175,7 +194,7 @@ public class SearchIterator {
         tailBand = getDistance(lastHit);
         String msg = String.format("set up init parameter for searchIterator width:%s tail_band:%s", width, tailBand);
         logger.debug(msg);
-        System.out.println(msg);
+//        System.out.println(msg);
     }
 
     private void updateFilteredIds(SearchResultsWrapper searchResultsWrapper) {

+ 10 - 0
src/main/java/io/milvus/response/FieldDataWrapper.java

@@ -25,6 +25,7 @@ import static io.milvus.grpc.DataType.JSON;
  */
 public class FieldDataWrapper {
     private final FieldData fieldData;
+    private List<?> cacheData = null;
 
     public FieldDataWrapper(@NonNull FieldData fieldData) {
         this.fieldData = fieldData;
@@ -124,6 +125,15 @@ public class FieldDataWrapper {
      * @return <code>List</code>
      */
     public List<?> getFieldData() throws IllegalResponseException {
+        if (cacheData != null) {
+            return cacheData;
+        }
+
+        cacheData = getFieldDataInternal();
+        return cacheData;
+    }
+
+    private List<?> getFieldDataInternal() throws IllegalResponseException {
         DataType dt = fieldData.getType();
         switch (dt) {
             case FloatVector: {

+ 3 - 3
src/main/java/io/milvus/response/QueryResultsWrapper.java

@@ -51,7 +51,7 @@ public class QueryResultsWrapper extends RowRecordWrapper {
         List<FieldData> fields = results.getFieldsDataList();
         for (FieldData field : fields) {
             if (fieldName.compareTo(field.getFieldName()) == 0) {
-                return new FieldDataWrapper(field);
+                return getFieldWrapperInternal(field);
             }
         }
 
@@ -95,7 +95,7 @@ public class QueryResultsWrapper extends RowRecordWrapper {
     public long getRowCount() {
         List<FieldData> fields = results.getFieldsDataList();
         for (FieldData field : fields) {
-            FieldDataWrapper wrapper = new FieldDataWrapper(field);
+            FieldDataWrapper wrapper = getFieldWrapperInternal(field);
             return wrapper.getRowCount();
         }
 
@@ -135,7 +135,7 @@ public class QueryResultsWrapper extends RowRecordWrapper {
          * If the key name is in dynamic field, return the value from the dynamic field.
          * Throws {@link ParamException} if the key name doesn't exist.
          *
-         * @return {@link FieldDataWrapper}
+         * @return {@link Object}
          */
         public Object get(String keyName) throws ParamException {
             if (fieldValues.isEmpty()) {

+ 4 - 4
src/main/java/io/milvus/response/SearchResultsWrapper.java

@@ -54,7 +54,7 @@ public class SearchResultsWrapper extends RowRecordWrapper {
         List<FieldData> fields = results.getFieldsDataList();
         for (FieldData field : fields) {
             if (fieldName.compareTo(field.getFieldName()) == 0) {
-                return new FieldDataWrapper(field);
+                return getFieldWrapperInternal(field);
             }
         }
 
@@ -117,7 +117,7 @@ public class SearchResultsWrapper extends RowRecordWrapper {
         for (int i = 0; i < results.getFieldsDataCount(); ++i) {
             FieldData data = results.getFieldsData(i);
             if (fieldName.compareTo(data.getFieldName()) == 0) {
-                wrapper = new FieldDataWrapper(data);
+                wrapper = getFieldWrapperInternal(data);
             }
         }
 
@@ -195,10 +195,10 @@ public class SearchResultsWrapper extends RowRecordWrapper {
             FieldDataWrapper dynamicField = null;
             for (FieldData field : fields) {
                 if (field.getIsDynamic()) {
-                    dynamicField = new FieldDataWrapper(field);
+                    dynamicField = getFieldWrapperInternal(field);
                 }
                 if (outputKey.equals(field.getFieldName())) {
-                    FieldDataWrapper wrapper = new FieldDataWrapper(field);
+                    FieldDataWrapper wrapper = getFieldWrapperInternal(field);
                     for (int n = 0; n < k; ++n) {
                         if ((offset + n) >= wrapper.getRowCount()) {
                             throw new ParamException("Illegal values length of output fields");

+ 34 - 3
src/main/java/io/milvus/response/basic/RowRecordWrapper.java

@@ -1,3 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
 package io.milvus.response.basic;
 
 import com.google.gson.*;
@@ -6,10 +25,22 @@ import io.milvus.grpc.FieldData;
 import io.milvus.response.FieldDataWrapper;
 import io.milvus.response.QueryResultsWrapper;
 
-import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
 
 public abstract class RowRecordWrapper {
+    // a cache for output fields
+    private ConcurrentHashMap<String, FieldDataWrapper> outputFieldsData = new ConcurrentHashMap<>();
+
+    protected FieldDataWrapper getFieldWrapperInternal(FieldData field) {
+        if (outputFieldsData.containsKey(field.getFieldName())) {
+            return outputFieldsData.get(field.getFieldName());
+        }
+
+        FieldDataWrapper wrapper = new FieldDataWrapper(field);
+        outputFieldsData.put(field.getFieldName(), wrapper);
+        return wrapper;
+    }
 
     public abstract List<QueryResultsWrapper.RowRecord> getRowRecords();
 
@@ -23,7 +54,7 @@ public abstract class RowRecordWrapper {
         List<FieldData> fields = getFieldDataList();
         for (FieldData field : fields) {
             if (field.getIsDynamic()) {
-                return new FieldDataWrapper(field);
+                return getFieldWrapperInternal(field);
             }
         }
 
@@ -41,7 +72,7 @@ public abstract class RowRecordWrapper {
             boolean isField = false;
             for (FieldData field : getFieldDataList()) {
                 if (outputKey.equals(field.getFieldName())) {
-                    FieldDataWrapper wrapper = new FieldDataWrapper(field);
+                    FieldDataWrapper wrapper = getFieldWrapperInternal(field);
                     if (index < 0 || index >= wrapper.getRowCount()) {
                         throw new ParamException("Index out of range");
                     }

+ 23 - 0
src/main/java/io/milvus/v2/client/MilvusClientV2.java

@@ -21,6 +21,8 @@ package io.milvus.v2.client;
 
 import io.grpc.ManagedChannel;
 import io.milvus.grpc.MilvusServiceGrpc;
+import io.milvus.orm.iterator.QueryIterator;
+import io.milvus.orm.iterator.SearchIterator;
 import io.milvus.v2.service.collection.CollectionService;
 import io.milvus.v2.service.collection.request.*;
 import io.milvus.v2.service.collection.response.DescribeCollectionResp;
@@ -311,6 +313,27 @@ public class MilvusClientV2 {
         return vectorService.search(this.blockingStub, request);
     }
 
+    /**
+     * Get queryIterator based on scalar field(s) filtered by boolean expression.
+     * Note that the order of the returned entities cannot be guaranteed.
+     *
+     * @param request {@link QueryIteratorReq}
+     * @return {status:result code,data: QueryIterator}
+     */
+    public QueryIterator queryIterator(QueryIteratorReq request) {
+        return vectorService.queryIterator(this.blockingStub, request);
+    }
+
+    /**
+     * Get searchIterator based on a vector field. Use expression to do filtering before search.
+     *
+     * @param request {@link SearchIteratorReq}
+     * @return {status:result code, data: SearchIterator}
+     */
+    public SearchIterator searchIterator(SearchIteratorReq request) {
+        return vectorService.searchIterator(this.blockingStub, request);
+    }
+
     // Partition Operations
     /**
      * Creates a partition in a collection in Milvus.

+ 0 - 115
src/main/java/io/milvus/v2/examples/Simple.java

@@ -1,115 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package io.milvus.v2.examples;
-
-import com.google.gson.*;
-import com.google.gson.reflect.TypeToken;
-import io.milvus.v2.client.ConnectConfig;
-import io.milvus.v2.client.MilvusClientV2;
-import io.milvus.v2.exception.MilvusClientException;
-import io.milvus.v2.service.collection.request.CreateCollectionReq;
-import io.milvus.v2.service.collection.request.DescribeCollectionReq;
-import io.milvus.v2.service.collection.request.DropCollectionReq;
-import io.milvus.v2.service.collection.request.HasCollectionReq;
-import io.milvus.v2.service.vector.request.InsertReq;
-import io.milvus.v2.service.vector.request.QueryReq;
-import io.milvus.v2.service.vector.request.SearchReq;
-import io.milvus.v2.service.vector.request.data.FloatVec;
-import io.milvus.v2.service.vector.response.QueryResp;
-import io.milvus.v2.service.vector.response.SearchResp;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Random;
-
-public class Simple {
-    Integer dim = 2;
-    String collectionName = "book";
-    static Logger logger = LoggerFactory.getLogger(Simple.class);
-    public static void main(String[] args) {
-        try {
-            new Simple().run();
-        } catch (MilvusClientException | InterruptedException e) {
-            logger.info(e.toString());
-        }
-    }
-
-    public void run() throws InterruptedException {
-        ConnectConfig connectConfig = ConnectConfig.builder()
-                .uri("https://in01-***.aws-us-west-2.vectordb.zillizcloud.com:19531")
-                .token("***")
-                .build();
-        MilvusClientV2 client = new MilvusClientV2(connectConfig);
-        // check collection exists
-        if (client.hasCollection(HasCollectionReq.builder().collectionName(collectionName).build())) {
-            logger.info("collection exists");
-            client.dropCollection(DropCollectionReq.builder().collectionName(collectionName).build());
-            logger.info("collection dropped");
-        }
-        // create collection
-        CreateCollectionReq createCollectionReq = CreateCollectionReq.builder()
-                .collectionName(collectionName)
-                .description("simple collection")
-                .dimension(dim)
-                .build();
-        client.createCollection(createCollectionReq);
-
-        logger.info(String.valueOf(client.listCollections()));
-        logger.info(String.valueOf(client.describeCollection(DescribeCollectionReq.builder().collectionName(collectionName).build())));
-        //insert data
-        List<JsonObject> insertData = new ArrayList<>();
-        Gson gson = new Gson();
-        for(int i = 0; i < 6; i++){
-            JsonObject jsonObject = new JsonObject();
-            List<Float> vectorList = new ArrayList<>();
-            for(int j = 0; j < dim; j++){
-                // generate random float vector
-                vectorList.add(new Random().nextFloat());
-            }
-            jsonObject.addProperty("id", (long) i);
-            jsonObject.add("vector", gson.toJsonTree(vectorList).getAsJsonArray());
-            insertData.add(jsonObject);
-        }
-        InsertReq insertReq = InsertReq.builder()
-                .collectionName(collectionName)
-                .data(insertData)
-                .build();
-        client.insert(insertReq);
-        //query data
-        QueryReq queryReq = QueryReq.builder()
-                .collectionName(collectionName)
-                .filter("id in [0]")
-                .build();
-        QueryResp queryResp = client.query(queryReq);
-        System.out.println(queryResp);
-        //search data
-        List<Float> vector = new Gson().fromJson(insertData.get(0).get("vector"), new TypeToken<List<Float>>() {}.getType());
-        SearchReq searchReq = SearchReq.builder()
-                .collectionName(collectionName)
-                .data(Collections.singletonList(new FloatVec(vector)))
-                .topK(10)
-                .build();
-        SearchResp searchResp = client.search(searchReq);
-        System.out.println(searchResp);
-    }
-}

+ 0 - 136
src/main/java/io/milvus/v2/examples/Simple_Schema.java

@@ -1,136 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package io.milvus.v2.examples;
-
-import com.google.gson.*;
-import com.google.gson.reflect.TypeToken;
-import io.milvus.v2.client.ConnectConfig;
-import io.milvus.v2.client.MilvusClientV2;
-import io.milvus.v2.common.DataType;
-import io.milvus.v2.common.IndexParam;
-import io.milvus.v2.service.collection.request.*;
-import io.milvus.v2.service.index.request.CreateIndexReq;
-import io.milvus.v2.service.vector.request.InsertReq;
-import io.milvus.v2.service.vector.request.QueryReq;
-import io.milvus.v2.service.vector.request.SearchReq;
-import io.milvus.v2.service.vector.request.data.FloatVec;
-import io.milvus.v2.service.vector.response.QueryResp;
-import io.milvus.v2.service.vector.response.SearchResp;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Random;
-import java.util.concurrent.TimeUnit;
-
-public class Simple_Schema {
-    Integer dim = 2;
-    String collectionName = "book";
-    static Logger logger = LoggerFactory.getLogger(Simple_Schema.class);
-    public void run() throws InterruptedException {
-        ConnectConfig connectConfig = ConnectConfig.builder()
-                .uri("https://in01-***.aws-us-west-2.vectordb.zillizcloud.com:19531")
-                .token("***")
-                .build();
-        MilvusClientV2 client = new MilvusClientV2(connectConfig);
-        // check collection exists
-        if (client.hasCollection(HasCollectionReq.builder().collectionName(collectionName).build())) {
-            logger.info("collection exists");
-            client.dropCollection(DropCollectionReq.builder().collectionName(collectionName).build());
-        }
-        // create collection
-        CreateCollectionReq.CollectionSchema collectionSchema = client.createSchema();
-        collectionSchema.addField(AddFieldReq.builder().fieldName("id").dataType(DataType.Int64).isPrimaryKey(Boolean.TRUE).autoID(Boolean.FALSE).description("id").build());
-        collectionSchema.addField(AddFieldReq.builder().fieldName("vector").dataType(DataType.FloatVector).dimension(dim).build());
-        collectionSchema.addField(AddFieldReq.builder().fieldName("num").dataType(DataType.Int64).isPartitionKey(Boolean.TRUE).build());
-        collectionSchema.addField(AddFieldReq.builder().fieldName("array").dataType(DataType.Array).elementType(DataType.Int32).maxCapacity(10).description("array").build());
-
-        CreateCollectionReq createCollectionReq = CreateCollectionReq.builder()
-                .collectionName(collectionName)
-                .description("simple collection")
-                .collectionSchema(collectionSchema)
-                .enableDynamicField(Boolean.FALSE)
-                .build();
-        client.createCollection(createCollectionReq);
-        //create index
-        IndexParam indexParam = IndexParam.builder()
-                .fieldName("vector")
-                .metricType(IndexParam.MetricType.COSINE)
-                .build();
-        CreateIndexReq createIndexReq = CreateIndexReq.builder()
-                .collectionName(collectionName)
-                .indexParams(Collections.singletonList(indexParam))
-                .build();
-        client.createIndex(createIndexReq);
-        TimeUnit.SECONDS.sleep(1);
-        client.loadCollection(LoadCollectionReq.builder().collectionName(collectionName).build());
-        //insert data
-        List<JsonObject> insertData = new ArrayList<>();
-        Gson gson = new Gson();
-        for(int i = 0; i < 6; i++){
-            JsonObject jsonObject = new JsonObject();
-            List<Float> vectorList = new ArrayList<>();
-            for(int j = 0; j < dim; j++){
-                // generate random float vector
-                vectorList.add(new Random().nextFloat());
-            }
-            List<Integer> array = new ArrayList<>();
-            array.add(i);
-            jsonObject.addProperty("id", (long) i);
-            jsonObject.add("vector", gson.toJsonTree(vectorList).getAsJsonArray());
-            jsonObject.addProperty("num", (long) i);
-            jsonObject.add("array", gson.toJsonTree(array).getAsJsonArray());
-            insertData.add(jsonObject);
-        }
-
-        InsertReq insertReq = InsertReq.builder()
-                .collectionName(collectionName)
-                .data(insertData)
-                .build();
-        client.insert(insertReq);
-        //query data
-        QueryReq queryReq = QueryReq.builder()
-                .collectionName(collectionName)
-                .filter("id in [0]")
-                .build();
-        QueryResp queryResp = client.query(queryReq);
-        queryResp.getQueryResults().get(0).getEntity().get("vector");
-        System.out.println(queryResp);
-        //search data
-        List<Float> vector = new Gson().fromJson(insertData.get(0).get("vector"), new TypeToken<List<Float>>() {}.getType());
-        SearchReq searchReq = SearchReq.builder()
-                .collectionName(collectionName)
-                .data(Collections.singletonList(new FloatVec(vector)))
-                .outputFields(Collections.singletonList("vector"))
-                .topK(10)
-                .build();
-        SearchResp searchResp = client.search(searchReq);
-        System.out.println(searchResp);
-    }
-    public static void main(String[] args) {
-        try {
-            new Simple_Schema().run();
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
-    }
-}

+ 12 - 2
src/main/java/io/milvus/v2/service/collection/CollectionService.java

@@ -20,6 +20,7 @@
 package io.milvus.v2.service.collection;
 
 import io.milvus.grpc.*;
+import io.milvus.param.ParamUtils;
 import io.milvus.v2.common.IndexParam;
 import io.milvus.v2.exception.ErrorCode;
 import io.milvus.v2.exception.MilvusClientException;
@@ -43,6 +44,11 @@ public class CollectionService extends BaseService {
             createCollectionWithSchema(blockingStub, request);
             return;
         }
+
+        if (request.getDimension() == null) {
+            throw new MilvusClientException(ErrorCode.INVALID_PARAMS, "Dimension is undefined.");
+        }
+
         String title = String.format("CreateCollectionRequest collectionName:%s", request.getCollectionName());
         FieldSchema vectorSchema = FieldSchema.newBuilder()
                 .setName(request.getVectorFieldName())
@@ -172,12 +178,16 @@ public class CollectionService extends BaseService {
     }
 
     public DescribeCollectionResp describeCollection(MilvusServiceGrpc.MilvusServiceBlockingStub milvusServiceBlockingStub, DescribeCollectionReq request) {
-
+        String title = String.format("DescribeCollectionRequest collectionName:%s", request.getCollectionName());
         DescribeCollectionRequest describeCollectionRequest = DescribeCollectionRequest.newBuilder()
                 .setCollectionName(request.getCollectionName())
                 .build();
         DescribeCollectionResponse response = milvusServiceBlockingStub.describeCollection(describeCollectionRequest);
+        rpcUtils.handleResponse(title, response.getStatus());
+        return convertDescCollectionResp(response);
+    }
 
+    public static DescribeCollectionResp convertDescCollectionResp(DescribeCollectionResponse response) {
         DescribeCollectionResp describeCollectionResp = DescribeCollectionResp.builder()
                 .collectionName(response.getCollectionName())
                 .description(response.getSchema().getDescription())
@@ -186,7 +196,7 @@ public class CollectionService extends BaseService {
                 .autoID(response.getSchema().getFieldsList().stream().anyMatch(FieldSchema::getAutoID))
                 .enableDynamicField(response.getSchema().getEnableDynamicField())
                 .fieldNames(response.getSchema().getFieldsList().stream().map(FieldSchema::getName).collect(java.util.stream.Collectors.toList()))
-                .vectorFieldName(response.getSchema().getFieldsList().stream().filter(fieldSchema -> fieldSchema.getDataType() == DataType.FloatVector || fieldSchema.getDataType() == DataType.BinaryVector).map(FieldSchema::getName).collect(java.util.stream.Collectors.toList()))
+                .vectorFieldNames(response.getSchema().getFieldsList().stream().filter(fieldSchema -> ParamUtils.isVectorDataType(fieldSchema.getDataType())).map(FieldSchema::getName).collect(java.util.stream.Collectors.toList()))
                 .primaryFieldName(response.getSchema().getFieldsList().stream().filter(FieldSchema::getIsPrimaryKey).map(FieldSchema::getName).collect(java.util.stream.Collectors.toList()).get(0))
                 .createTime(response.getCreatedTimestamp())
                 .build();

+ 1 - 1
src/main/java/io/milvus/v2/service/collection/response/DescribeCollectionResp.java

@@ -33,7 +33,7 @@ public class DescribeCollectionResp {
     private Long numOfPartitions;
 
     private List<String> fieldNames;
-    private List<String> vectorFieldName;
+    private List<String> vectorFieldNames;
     private String primaryFieldName;
     private Boolean enableDynamicField;
     private Boolean autoID;

+ 3 - 0
src/main/java/io/milvus/v2/service/index/response/DescribeIndexResp.java

@@ -22,6 +22,8 @@ package io.milvus.v2.service.index.response;
 import lombok.Data;
 import lombok.experimental.SuperBuilder;
 
+import java.util.Map;
+
 @Data
 @SuperBuilder
 public class DescribeIndexResp {
@@ -29,4 +31,5 @@ public class DescribeIndexResp {
     private String indexType;
     private String metricType;
     private String fieldName;
+    private Map<String, Object> extraParams;
 }

+ 85 - 11
src/main/java/io/milvus/v2/service/vector/VectorService.java

@@ -19,49 +19,106 @@
 
 package io.milvus.v2.service.vector;
 
+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;
 import io.milvus.v2.service.collection.CollectionService;
+import io.milvus.v2.service.collection.request.CreateCollectionReq;
 import io.milvus.v2.service.collection.request.DescribeCollectionReq;
 import io.milvus.v2.service.collection.response.DescribeCollectionResp;
 import io.milvus.v2.service.index.IndexService;
 import io.milvus.v2.service.vector.request.*;
 import io.milvus.v2.service.vector.response.*;
+import io.milvus.v2.utils.RpcUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.concurrent.TimeUnit;
+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<>();
+
+    /**
+     * This method is for insert/upsert requests to reduce the rpc call of describeCollection()
+     * Always try to get the collection info from cache.
+     * If the cache doesn't have the collection info, call describeCollection() and cache it.
+     * If insert/upsert get server error, remove the cached collection info.
+     */
+    private DescribeCollectionResponse getCollectionInfo(MilvusServiceGrpc.MilvusServiceBlockingStub blockingStub,
+                                                         String databaseName, String collectionName) {
+        String key = combineCacheKey(databaseName, collectionName);
+        DescribeCollectionResponse info = cacheCollectionInfo.get(key);
+        if (info == null) {
+            String msg = String.format("Fail to describe collection '%s'", collectionName);
+            DescribeCollectionRequest.Builder builder = DescribeCollectionRequest.newBuilder()
+                    .setCollectionName(collectionName);
+            if (StringUtils.isNotEmpty(databaseName)) {
+                builder.setDbName(databaseName);
+                msg = String.format("Fail to describe collection '%s' in database '%s'",
+                        collectionName, databaseName);
+            }
+            DescribeCollectionRequest describeCollectionRequest = builder.build();
+            DescribeCollectionResponse response = blockingStub.describeCollection(describeCollectionRequest);
+            new RpcUtils().handleResponse(msg, response.getStatus());
+            info = response;
+            cacheCollectionInfo.put(key, info);
+        }
+
+        return info;
+    }
+
+    private String combineCacheKey(String databaseName, String collectionName) {
+        if (collectionName == null || StringUtils.isBlank(collectionName)) {
+            throw new ParamException("Collection name is empty, not able to get collection info.");
+        }
+        String key = collectionName;
+        if (StringUtils.isNotEmpty(databaseName)) {
+            key = String.format("%s|%s", databaseName, collectionName);
+        }
+        return key;
+    }
+
+    /**
+     * insert/upsert return an error, but is not a RateLimit error,
+     * clean the cache so that the next insert will call describeCollection() to get the latest info.
+     */
+    private void cleanCacheIfFailed(Status status, String databaseName, String collectionName) {
+        if ((status.getCode() != 0 && status.getCode() != 8) ||
+                (!status.getErrorCode().equals(io.milvus.grpc.ErrorCode.Success) &&
+                        status.getErrorCode() != io.milvus.grpc.ErrorCode.RateLimit)) {
+            cacheCollectionInfo.remove(combineCacheKey(databaseName, collectionName));
+        }
+    }
 
     public InsertResp insert(MilvusServiceGrpc.MilvusServiceBlockingStub blockingStub, InsertReq request) {
         String title = String.format("InsertRequest collectionName:%s", request.getCollectionName());
 
-        DescribeCollectionRequest describeCollectionRequest = DescribeCollectionRequest.newBuilder()
-                .setCollectionName(request.getCollectionName()).build();
-        DescribeCollectionResponse descResp = blockingStub.describeCollection(describeCollectionRequest);
+        // TODO: set the database name
+        DescribeCollectionResponse descResp = getCollectionInfo(blockingStub, "", request.getCollectionName());
 
         MutationResult response = blockingStub.insert(dataUtils.convertGrpcInsertRequest(request, new DescCollResponseWrapper(descResp)));
+        cleanCacheIfFailed(response.getStatus(), "", request.getCollectionName());
         rpcUtils.handleResponse(title, response.getStatus());
         return InsertResp.builder()
                 .InsertCnt(response.getInsertCnt())
                 .build();
     }
 
-    public UpsertResp upsert(MilvusServiceGrpc.MilvusServiceBlockingStub milvusServiceBlockingStub, UpsertReq request) {
+    public UpsertResp upsert(MilvusServiceGrpc.MilvusServiceBlockingStub blockingStub, UpsertReq request) {
         String title = String.format("UpsertRequest collectionName:%s", request.getCollectionName());
 
-        DescribeCollectionRequest describeCollectionRequest = DescribeCollectionRequest.newBuilder()
-                .setCollectionName(request.getCollectionName()).build();
-        DescribeCollectionResponse descResp = milvusServiceBlockingStub.describeCollection(describeCollectionRequest);
-
-        MutationResult response = milvusServiceBlockingStub.upsert(dataUtils.convertGrpcUpsertRequest(request, new DescCollResponseWrapper(descResp)));
+        // TODO: set the database name
+        DescribeCollectionResponse descResp = getCollectionInfo(blockingStub, "", request.getCollectionName());
+        MutationResult response = blockingStub.upsert(dataUtils.convertGrpcUpsertRequest(request, new DescCollResponseWrapper(descResp)));
+        cleanCacheIfFailed(response.getStatus(), "", request.getCollectionName());
         rpcUtils.handleResponse(title, response.getStatus());
         return UpsertResp.builder()
                 .upsertCnt(response.getInsertCnt())
@@ -106,6 +163,22 @@ public class VectorService extends BaseService {
                 .build();
     }
 
+    public QueryIterator queryIterator(MilvusServiceGrpc.MilvusServiceBlockingStub blockingStub,
+                                       QueryIteratorReq request) {
+        DescribeCollectionResponse descResp = getCollectionInfo(blockingStub, "", request.getCollectionName());
+        DescribeCollectionResp respR = CollectionService.convertDescCollectionResp(descResp);
+        CreateCollectionReq.FieldSchema pkField = respR.getCollectionSchema().getField(respR.getPrimaryFieldName());
+        return new QueryIterator(request, blockingStub, pkField);
+    }
+
+    public SearchIterator searchIterator(MilvusServiceGrpc.MilvusServiceBlockingStub blockingStub,
+                                         SearchIteratorReq request) {
+        DescribeCollectionResponse descResp = getCollectionInfo(blockingStub, "", request.getCollectionName());
+        DescribeCollectionResp respR = CollectionService.convertDescCollectionResp(descResp);
+        CreateCollectionReq.FieldSchema pkField = respR.getCollectionSchema().getField(respR.getPrimaryFieldName());
+        return new SearchIterator(request, blockingStub, pkField);
+    }
+
     public DeleteResp delete(MilvusServiceGrpc.MilvusServiceBlockingStub milvusServiceBlockingStub, DeleteReq request) {
         String title = String.format("DeleteRequest collectionName:%s", request.getCollectionName());
 
@@ -113,7 +186,8 @@ public class VectorService extends BaseService {
             throw new MilvusClientException(ErrorCode.INVALID_PARAMS, "filter and ids can't be set at the same time");
         }
 
-        DescribeCollectionResp respR = collectionService.describeCollection(milvusServiceBlockingStub, DescribeCollectionReq.builder().collectionName(request.getCollectionName()).build());
+        DescribeCollectionResponse descResp = getCollectionInfo(milvusServiceBlockingStub, "", request.getCollectionName());
+        DescribeCollectionResp respR = CollectionService.convertDescCollectionResp(descResp);
         if (request.getFilter() == null) {
             request.setFilter(vectorUtils.getExprById(respR.getPrimaryFieldName(), request.getIds()));
         }

+ 32 - 0
src/main/java/io/milvus/v2/service/vector/request/QueryIteratorReq.java

@@ -0,0 +1,32 @@
+package io.milvus.v2.service.vector.request;
+
+import com.google.common.collect.Lists;
+import io.milvus.v2.common.ConsistencyLevel;
+import lombok.Builder;
+import lombok.Data;
+import lombok.experimental.SuperBuilder;
+
+import java.util.List;
+
+@Data
+@SuperBuilder
+public class QueryIteratorReq {
+    private String databaseName;
+    private String collectionName;
+    @Builder.Default
+    private List<String> partitionNames = Lists.newArrayList();
+    @Builder.Default
+    private List<String> outputFields = Lists.newArrayList();
+    @Builder.Default
+    private String expr = "";
+    @Builder.Default
+    private ConsistencyLevel consistencyLevel = ConsistencyLevel.BOUNDED;
+    @Builder.Default
+    private long offset = 0;
+    @Builder.Default
+    private long limit = -1;
+    @Builder.Default
+    private boolean ignoreGrowing = false;
+    @Builder.Default
+    private long batchSize = 1000L;
+}

+ 41 - 0
src/main/java/io/milvus/v2/service/vector/request/SearchIteratorReq.java

@@ -0,0 +1,41 @@
+package io.milvus.v2.service.vector.request;
+
+import com.google.common.collect.Lists;
+import io.milvus.v2.common.ConsistencyLevel;
+import io.milvus.v2.common.IndexParam;
+import io.milvus.v2.service.vector.request.data.BaseVector;
+import lombok.Builder;
+import lombok.Data;
+import lombok.experimental.SuperBuilder;
+
+import java.util.List;
+
+@Data
+@SuperBuilder
+public class SearchIteratorReq {
+    private String databaseName;
+    private String collectionName;
+    @Builder.Default
+    private List<String> partitionNames = Lists.newArrayList();
+    @Builder.Default
+    private IndexParam.MetricType metricType = IndexParam.MetricType.INVALID;
+    private String vectorFieldName;
+    @Builder.Default
+    private int topK = -1;
+    @Builder.Default
+    private String expr = "";
+    @Builder.Default
+    private List<String> outputFields = Lists.newArrayList();
+    @Builder.Default
+    private List<BaseVector> vectors = Lists.newArrayList();
+    @Builder.Default
+    private int roundDecimal = -1;
+    @Builder.Default
+    private String params = "{}";
+    @Builder.Default
+    private ConsistencyLevel consistencyLevel = ConsistencyLevel.BOUNDED;
+    @Builder.Default
+    private boolean ignoreGrowing = false;
+    @Builder.Default
+    private long batchSize = 1000L;
+}

+ 4 - 0
src/main/java/io/milvus/v2/service/vector/request/data/BinaryVec.java

@@ -30,6 +30,10 @@ public class BinaryVec implements BaseVector {
         this.data = data;
     }
 
+    public BinaryVec(byte[] data) {
+        this.data = ByteBuffer.wrap(data);
+    }
+
     @Override
     public PlaceholderType getPlaceholderType() {
         return PlaceholderType.BinaryVector;

+ 9 - 0
src/main/java/io/milvus/v2/service/vector/request/data/FloatVec.java

@@ -21,6 +21,8 @@ package io.milvus.v2.service.vector.request.data;
 
 
 import io.milvus.grpc.PlaceholderType;
+
+import java.util.ArrayList;
 import java.util.List;
 
 public class FloatVec implements BaseVector {
@@ -30,6 +32,13 @@ public class FloatVec implements BaseVector {
         this.data = data;
     }
 
+    public FloatVec(float[] data) {
+        this.data = new ArrayList<>();
+        for (float f : data) {
+            this.data.add(f);
+        }
+    }
+
     @Override
     public PlaceholderType getPlaceholderType() {
         return PlaceholderType.FloatVector;

+ 5 - 1
src/main/java/io/milvus/v2/utils/ConvertUtils.java

@@ -77,14 +77,18 @@ public class ConvertUtils {
                 .indexName(response.getIndexName())
                 .fieldName(response.getFieldName())
                 .build();
+        Map<String, Object> extraParams = new HashMap<>();
         List<KeyValuePair> params = response.getParamsList();
         for(KeyValuePair param : params) {
             if (param.getKey().equals("index_type")) {
                 describeIndexResp.setIndexType(param.getValue());
-            }else if (param.getKey().equals("metric_type")) {
+            } else if (param.getKey().equals("metric_type")) {
                 describeIndexResp.setMetricType(param.getValue());
+            } else {
+                extraParams.put(param.getKey(), param.getValue());
             }
         }
+        describeIndexResp.setExtraParams(extraParams);
         return describeIndexResp;
     }
 }

+ 5 - 0
src/main/java/io/milvus/v2/utils/SchemaUtils.java

@@ -74,10 +74,15 @@ public class SchemaUtils {
                 .dataType(io.milvus.v2.common.DataType.valueOf(fieldSchema.getDataType().name()))
                 .isPrimaryKey(fieldSchema.getIsPrimaryKey())
                 .autoID(fieldSchema.getAutoID())
+                .elementType(io.milvus.v2.common.DataType.valueOf(fieldSchema.getElementType().name()))
                 .build();
         for (KeyValuePair keyValuePair : fieldSchema.getTypeParamsList()) {
             if(keyValuePair.getKey().equals("dim")){
                 schema.setDimension(Integer.parseInt(keyValuePair.getValue()));
+            } else if(keyValuePair.getKey().equals("max_length")){
+                schema.setMaxLength(Integer.parseInt(keyValuePair.getValue()));
+            } else if(keyValuePair.getKey().equals("max_capacity")){
+                schema.setMaxCapacity(Integer.parseInt(keyValuePair.getValue()));
             }
         }
         return schema;

+ 69 - 0
src/test/java/io/milvus/client/MilvusClientDockerTest.java

@@ -2442,4 +2442,73 @@ class MilvusClientDockerTest {
         }
         Assertions.assertEquals(50, counter);
     }
+
+    @Test
+    void testCacheCollectionSchema() {
+        String randomCollectionName = generator.generate(10);
+
+        // collection schema
+        List<FieldType> fieldsSchema = new ArrayList<>();
+        fieldsSchema.add(FieldType.newBuilder()
+                .withPrimaryKey(true)
+                .withAutoID(true)
+                .withDataType(DataType.Int64)
+                .withName("id")
+                .build());
+
+        fieldsSchema.add(FieldType.newBuilder()
+                .withDataType(DataType.FloatVector)
+                .withName("vector")
+                .withDimension(dimension)
+                .build());
+
+        // create collection
+        R<RpcStatus> createR = client.createCollection(CreateCollectionParam.newBuilder()
+                .withCollectionName(randomCollectionName)
+                .withFieldTypes(fieldsSchema)
+                .build());
+        Assertions.assertEquals(R.Status.Success.getCode(), createR.getStatus().intValue());
+
+        // insert
+        JsonObject row = new JsonObject();
+        row.add("vector", GSON_INSTANCE.toJsonTree(generateFloatVectors(1).get(0)));
+        R<MutationResult> insertR = client.insert(InsertParam.newBuilder()
+                .withCollectionName(randomCollectionName)
+                .withRows(Collections.singletonList(row))
+                .build());
+        Assertions.assertEquals(R.Status.Success.getCode(), insertR.getStatus().intValue());
+
+        // drop collection
+        client.dropCollection(DropCollectionParam.newBuilder()
+                .withCollectionName(randomCollectionName)
+                .build());
+
+        // create a new collection with the same name, different schema
+        fieldsSchema.add(FieldType.newBuilder()
+                .withDataType(DataType.VarChar)
+                .withName("title")
+                .withMaxLength(100)
+                .build());
+
+        createR = client.createCollection(CreateCollectionParam.newBuilder()
+                .withCollectionName(randomCollectionName)
+                .withFieldTypes(fieldsSchema)
+                .build());
+        Assertions.assertEquals(R.Status.Success.getCode(), createR.getStatus().intValue());
+
+        // insert wrong data
+        insertR = client.insert(InsertParam.newBuilder()
+                .withCollectionName(randomCollectionName)
+                .withRows(Collections.singletonList(row))
+                .build());
+        Assertions.assertNotEquals(R.Status.Success.getCode(), insertR.getStatus().intValue());
+
+        // insert correct data
+        row.addProperty("title", "hello world");
+        insertR = client.insert(InsertParam.newBuilder()
+                .withCollectionName(randomCollectionName)
+                .withRows(Collections.singletonList(row))
+                .build());
+        Assertions.assertEquals(R.Status.Success.getCode(), insertR.getStatus().intValue());
+    }
 }

+ 544 - 23
src/test/java/io/milvus/v2/client/MilvusClientV2DockerTest.java

@@ -19,16 +19,27 @@
 
 package io.milvus.v2.client;
 
+import com.google.common.collect.Lists;
 import com.google.gson.*;
-
 import com.google.gson.reflect.TypeToken;
+
+import io.milvus.orm.iterator.QueryIterator;
+import io.milvus.orm.iterator.SearchIterator;
+import io.milvus.response.QueryResultsWrapper;
 import io.milvus.v2.common.ConsistencyLevel;
 import io.milvus.v2.common.DataType;
 import io.milvus.v2.common.IndexParam;
-import io.milvus.v2.service.collection.request.AddFieldReq;
-import io.milvus.v2.service.collection.request.CreateCollectionReq;
-import io.milvus.v2.service.collection.request.DropCollectionReq;
-import io.milvus.v2.service.partition.request.CreatePartitionReq;
+import io.milvus.v2.exception.MilvusClientException;
+import io.milvus.v2.service.collection.request.*;
+import io.milvus.v2.service.collection.response.DescribeCollectionResp;
+import io.milvus.v2.service.index.request.CreateIndexReq;
+import io.milvus.v2.service.index.request.DescribeIndexReq;
+import io.milvus.v2.service.index.request.DropIndexReq;
+import io.milvus.v2.service.index.response.DescribeIndexResp;
+import io.milvus.v2.service.partition.request.*;
+import io.milvus.v2.service.utility.request.AlterAliasReq;
+import io.milvus.v2.service.utility.request.CreateAliasReq;
+import io.milvus.v2.service.utility.request.DropAliasReq;
 import io.milvus.v2.service.vector.request.*;
 import io.milvus.v2.service.vector.request.data.*;
 import io.milvus.v2.service.vector.response.*;
@@ -56,7 +67,7 @@ class MilvusClientV2DockerTest {
     private static final Random RANDOM = new Random();
 
     @Container
-    private static final MilvusContainer milvus = new MilvusContainer("milvusdb/milvus:v2.4.1");
+    private static final MilvusContainer milvus = new MilvusContainer("milvusdb/milvus:v2.3.13");
 
     @BeforeAll
     public static void setUp() {
@@ -275,6 +286,7 @@ class MilvusClientV2DockerTest {
                     case JSON: {
                         JsonObject jsonObj = new JsonObject();
                         jsonObj.addProperty(String.format("JSON_%d", i), i);
+                        jsonObj.add("flags", GSON_INSTANCE.toJsonTree(new long[]{i, i+1, i + 2}));
                         row.add(field.getName(), jsonObj);
                         break;
                     }
@@ -334,6 +346,18 @@ class MilvusClientV2DockerTest {
         Assertions.assertEquals(arrStrOri, arrStr);
     }
 
+    private long getRowCount(String collectionName) {
+        QueryResp queryResp = client.query(QueryReq.builder()
+                .collectionName(collectionName)
+                .filter("")
+                .outputFields(Collections.singletonList("count(*)"))
+                .consistencyLevel(ConsistencyLevel.STRONG)
+                .build());
+        List<QueryResp.QueryResult> queryResults = queryResp.getQueryResults();
+        Assertions.assertEquals(1, queryResults.size());
+        return (long)queryResults.get(0).getEntity().get("count(*)");
+    }
+
 
     @Test
     void testFloatVectors() {
@@ -359,6 +383,7 @@ class MilvusClientV2DockerTest {
 
         CreateCollectionReq requestCreate = CreateCollectionReq.builder()
                 .collectionName(randomCollectionName)
+                .description("dummy")
                 .collectionSchema(collectionSchema)
                 .indexParams(Collections.singletonList(indexParam))
                 .build();
@@ -390,15 +415,41 @@ class MilvusClientV2DockerTest {
         Assertions.assertEquals(1, upsertResp.getUpsertCnt());
 
         // get row count
-        QueryResp queryResp = client.query(QueryReq.builder()
+        long rowCount = getRowCount(randomCollectionName);
+        Assertions.assertEquals(count + 1, rowCount);
+
+        // describe collection
+        DescribeCollectionResp descResp = client.describeCollection(DescribeCollectionReq.builder()
                 .collectionName(randomCollectionName)
-                .filter("")
-                .outputFields(Collections.singletonList("count(*)"))
-                .consistencyLevel(ConsistencyLevel.STRONG)
                 .build());
-        List<QueryResp.QueryResult> queryResults = queryResp.getQueryResults();
-        Assertions.assertEquals(1, queryResults.size());
-        Assertions.assertEquals(count + 1, queryResults.get(0).getEntity().get("count(*)"));
+        Assertions.assertEquals(randomCollectionName, descResp.getCollectionName());
+        Assertions.assertEquals("dummy", descResp.getDescription());
+        Assertions.assertEquals(2, descResp.getNumOfPartitions());
+        Assertions.assertEquals(1, descResp.getVectorFieldNames().size());
+        Assertions.assertEquals("id", descResp.getPrimaryFieldName());
+        Assertions.assertTrue(descResp.getEnableDynamicField());
+        Assertions.assertFalse(descResp.getAutoID());
+
+        List<String> fieldNames = descResp.getFieldNames();
+        Assertions.assertEquals(collectionSchema.getFieldSchemaList().size(), fieldNames.size());
+        CreateCollectionReq.CollectionSchema schema = descResp.getCollectionSchema();
+        for (String name : fieldNames) {
+            CreateCollectionReq.FieldSchema f1 = collectionSchema.getField(name);
+            CreateCollectionReq.FieldSchema f2 = schema.getField(name);
+            Assertions.assertNotNull(f1);
+            Assertions.assertNotNull(f2);
+            Assertions.assertEquals(f1.getName(), f2.getName());
+            Assertions.assertEquals(f1.getDescription(), f2.getDescription());
+            Assertions.assertEquals(f1.getDataType(), f2.getDataType());
+            Assertions.assertEquals(f1.getDimension(), f2.getDimension());
+            Assertions.assertEquals(f1.getMaxLength(), f2.getMaxLength());
+            Assertions.assertEquals(f1.getIsPrimaryKey(), f2.getIsPrimaryKey());
+            Assertions.assertEquals(f1.getIsPartitionKey(), f2.getIsPartitionKey());
+            if (f1.getDataType() == io.milvus.v2.common.DataType.Array) {
+                Assertions.assertEquals(f1.getElementType(), f2.getElementType());
+                Assertions.assertEquals(f1.getMaxCapacity(), f2.getMaxCapacity());
+            }
+        }
 
         // search in partition
         SearchResp searchResp = client.search(SearchReq.builder()
@@ -468,6 +519,14 @@ class MilvusClientV2DockerTest {
             verifyOutput(row, entity);
         }
 
+        // query
+        QueryResp queryResp = client.query(QueryReq.builder()
+                .collectionName(randomCollectionName)
+                .filter("JSON_CONTAINS_ANY(json_field[\"flags\"], [4, 100])")
+                .build());
+        List<QueryResp.QueryResult> queryResults = queryResp.getQueryResults();
+        Assertions.assertEquals(6, queryResults.size());
+
         client.dropCollection(DropCollectionReq.builder().collectionName(randomCollectionName).build());
     }
 
@@ -509,15 +568,8 @@ class MilvusClientV2DockerTest {
         Assertions.assertEquals(count, insertResp.getInsertCnt());
 
         // get row count
-        QueryResp queryResp = client.query(QueryReq.builder()
-                .collectionName(randomCollectionName)
-                .filter("")
-                .outputFields(Collections.singletonList("count(*)"))
-                .consistencyLevel(ConsistencyLevel.STRONG)
-                .build());
-        List<QueryResp.QueryResult> queryResults = queryResp.getQueryResults();
-        Assertions.assertEquals(1, queryResults.size());
-        Assertions.assertEquals(count, queryResults.get(0).getEntity().get("count(*)"));
+        long rowCount = getRowCount(randomCollectionName);
+        Assertions.assertEquals(count, rowCount);
 
         // search in collection
         int nq = 5;
@@ -528,7 +580,7 @@ class MilvusClientV2DockerTest {
             JsonObject row = data.get(RANDOM.nextInt((int)count));
             targetIDs.add(row.get("id").getAsLong());
             byte[] vector = GSON_INSTANCE.fromJson(row.get(vectorFieldName), new TypeToken<byte[]>() {}.getType());
-            targetVectors.add(new BinaryVec(ByteBuffer.wrap(vector)));
+            targetVectors.add(new BinaryVec(vector));
         }
         SearchResp searchResp = client.search(SearchReq.builder()
                 .collectionName(randomCollectionName)
@@ -546,4 +598,473 @@ class MilvusClientV2DockerTest {
 
         client.dropCollection(DropCollectionReq.builder().collectionName(randomCollectionName).build());
     }
+
+    @Test
+    void testDeleteUpsert() {
+        String randomCollectionName = generator.generate(10);
+
+        CreateCollectionReq.CollectionSchema collectionSchema = CreateCollectionReq.CollectionSchema.builder()
+                .build();
+        collectionSchema.addField(AddFieldReq.builder()
+                .fieldName("pk")
+                .dataType(DataType.VarChar)
+                .isPrimaryKey(Boolean.TRUE)
+                .build());
+        collectionSchema.addField(AddFieldReq.builder()
+                .fieldName("float_vector")
+                .dataType(DataType.FloatVector)
+                .dimension(4)
+                .build());
+
+        List<IndexParam> indexParams = new ArrayList<>();
+        indexParams.add(IndexParam.builder()
+                .fieldName("float_vector")
+                .indexType(IndexParam.IndexType.IVF_FLAT)
+                .metricType(IndexParam.MetricType.L2)
+                .extraParams(new HashMap<String,Object>(){{put("nlist", 64);}})
+                .build());
+        CreateCollectionReq requestCreate = CreateCollectionReq.builder()
+                .collectionName(randomCollectionName)
+                .collectionSchema(collectionSchema)
+                .indexParams(indexParams)
+                .build();
+        client.createCollection(requestCreate);
+
+        // insert
+        List<JsonObject> data = new ArrayList<>();
+        Gson gson = new Gson();
+        for (int i = 0; i < 10; i++) {
+            JsonObject row = new JsonObject();
+            row.addProperty("pk", String.format("pk_%d", i));
+            row.add("float_vector", gson.toJsonTree(new float[]{(float)i, (float)(i + 1), (float)(i + 2), (float)(i + 3)}));
+            data.add(row);
+        }
+
+        InsertResp insertResp = client.insert(InsertReq.builder()
+                .collectionName(randomCollectionName)
+                .data(data)
+                .build());
+        Assertions.assertEquals(10, insertResp.getInsertCnt());
+
+        // delete
+        DeleteResp deleteResp = client.delete(DeleteReq.builder()
+                .collectionName(randomCollectionName)
+                .ids(Arrays.asList("pk_5", "pk_8"))
+                .build());
+        Assertions.assertEquals(2, deleteResp.getDeleteCnt());
+
+        // get row count
+        long rowCount = getRowCount(randomCollectionName);
+        Assertions.assertEquals(8L, rowCount);
+
+        // upsert
+        List<JsonObject> dataUpdate = new ArrayList<>();
+        JsonObject row1 = new JsonObject();
+        row1.addProperty("pk", "pk_5");
+        row1.add("float_vector", gson.toJsonTree(new float[]{5.0f, 5.0f, 5.0f, 5.0f}));
+        dataUpdate.add(row1);
+        JsonObject row2 = new JsonObject();
+        row2.addProperty("pk", "pk_2");
+        row2.add("float_vector", gson.toJsonTree(new float[]{2.0f, 2.0f, 2.0f, 2.0f}));
+        dataUpdate.add(row2);
+        UpsertResp upsertResp = client.upsert(UpsertReq.builder()
+                .collectionName(randomCollectionName)
+                .data(dataUpdate)
+                .build());
+        Assertions.assertEquals(2, upsertResp.getUpsertCnt());
+
+        // get row count
+        rowCount = getRowCount(randomCollectionName);
+        Assertions.assertEquals(9L, rowCount);
+
+        // verify
+        QueryResp queryResp = client.query(QueryReq.builder()
+                .collectionName(randomCollectionName)
+                .filter("pk == \"pk_2\" or pk == \"pk_5\"")
+                .outputFields(Collections.singletonList("*"))
+                .build());
+        List<QueryResp.QueryResult> queryResults = queryResp.getQueryResults();
+        Assertions.assertEquals(2, queryResults.size());
+
+        QueryResp.QueryResult result1 = queryResults.get(0);
+        Map<String, Object> entity1 = result1.getEntity();
+        Assertions.assertTrue(entity1.containsKey("pk"));
+        Assertions.assertEquals("pk_2", entity1.get("pk"));
+        Assertions.assertTrue(entity1.containsKey("float_vector"));
+        Assertions.assertTrue(entity1.get("float_vector") instanceof List);
+        List<Float> vector1 = (List<Float>) entity1.get("float_vector");
+        for (Float f : vector1) {
+            Assertions.assertEquals(2.0f, f);
+        }
+
+        QueryResp.QueryResult result2 = queryResults.get(1);
+        Map<String, Object> entity2 = result2.getEntity();
+        Assertions.assertTrue(entity2.containsKey("pk"));
+        Assertions.assertEquals("pk_5", entity2.get("pk"));
+        Assertions.assertTrue(entity2.containsKey("float_vector"));
+        Assertions.assertTrue(entity2.get("float_vector") instanceof List);
+        List<Float> vector2 = (List<Float>) entity2.get("float_vector");
+        for (Float f : vector2) {
+            Assertions.assertEquals(5.0f, f);
+        }
+
+        client.dropCollection(DropCollectionReq.builder().collectionName(randomCollectionName).build());
+    }
+
+    @Test
+    void testAlias() {
+        client.createCollection(CreateCollectionReq.builder()
+                .collectionName("AAA")
+                .description("desc_A")
+                .dimension(100)
+                .build());
+
+        client.createCollection(CreateCollectionReq.builder()
+                .collectionName("BBB")
+                .description("desc_B")
+                .dimension(50)
+                .build());
+
+        client.createAlias(CreateAliasReq.builder()
+                .collectionName("BBB")
+                .alias("CCC")
+                .build());
+
+        DescribeCollectionResp descResp = client.describeCollection(DescribeCollectionReq.builder()
+                .collectionName("CCC")
+                .build());
+        Assertions.assertEquals("desc_B", descResp.getDescription());
+
+        client.dropCollection(DropCollectionReq.builder()
+                .collectionName("BBB")
+                .build());
+
+        Assertions.assertThrows(MilvusClientException.class, ()->client.describeCollection(DescribeCollectionReq.builder()
+                .collectionName("CCC")
+                .build()));
+
+        client.alterAlias(AlterAliasReq.builder()
+                .collectionName("AAA")
+                .alias("CCC")
+                .build());
+
+        descResp = client.describeCollection(DescribeCollectionReq.builder()
+                .collectionName("CCC")
+                .build());
+        Assertions.assertEquals("desc_A", descResp.getDescription());
+
+        client.dropAlias(DropAliasReq.builder()
+                .alias("CCC")
+                .build());
+
+        Assertions.assertThrows(MilvusClientException.class, ()->client.describeCollection(DescribeCollectionReq.builder()
+                .collectionName("CCC")
+                .build()));
+    }
+
+    @Test
+    void testPartition() {
+        String randomCollectionName = generator.generate(10);
+        client.createCollection(CreateCollectionReq.builder()
+                .collectionName(randomCollectionName)
+                .dimension(4)
+                .build());
+
+        client.createPartition(CreatePartitionReq.builder()
+                .collectionName(randomCollectionName)
+                .partitionName("P1")
+                .build());
+
+        client.createPartition(CreatePartitionReq.builder()
+                .collectionName(randomCollectionName)
+                .partitionName("P2")
+                .build());
+
+        List<String> partitions = client.listPartitions(ListPartitionsReq.builder()
+                .collectionName(randomCollectionName)
+                .build());
+        Assertions.assertEquals(3, partitions.size());
+        Assertions.assertTrue(partitions.contains("P1"));
+        Assertions.assertTrue(partitions.contains("P2"));
+        Assertions.assertTrue(partitions.contains("_default"));
+
+        Boolean has = client.hasPartition(HasPartitionReq.builder()
+                .collectionName(randomCollectionName)
+                .partitionName("P1")
+                .build());
+        Assertions.assertTrue(has);
+
+        client.releasePartitions(ReleasePartitionsReq.builder()
+                .collectionName(randomCollectionName)
+                .partitionNames(Collections.singletonList("P1"))
+                .build());
+
+        client.dropPartition(DropPartitionReq.builder()
+                .collectionName(randomCollectionName)
+                .partitionName("P1")
+                .build());
+
+        has = client.hasPartition(HasPartitionReq.builder()
+                .collectionName(randomCollectionName)
+                .partitionName("P1")
+                .build());
+        Assertions.assertFalse(has);
+
+        partitions = client.listPartitions(ListPartitionsReq.builder()
+                .collectionName(randomCollectionName)
+                .build());
+        Assertions.assertEquals(2, partitions.size());
+        Assertions.assertFalse(partitions.contains("P1"));
+    }
+
+    @Test
+    void testIndex() {
+        String randomCollectionName = generator.generate(10);
+        client.createCollection(CreateCollectionReq.builder()
+                .collectionName(randomCollectionName)
+                .dimension(dimension)
+                .build());
+
+        client.releaseCollection(ReleaseCollectionReq.builder()
+                .collectionName(randomCollectionName)
+                .build());
+
+        DescribeIndexResp descResp = client.describeIndex(DescribeIndexReq.builder()
+                .collectionName(randomCollectionName)
+                .fieldName("vector")
+                .build());
+        Assertions.assertEquals(IndexParam.IndexType.AUTOINDEX.name(), descResp.getIndexType());
+
+        client.dropIndex(DropIndexReq.builder()
+                .collectionName(randomCollectionName)
+                .fieldName("vector")
+                .build());
+
+        IndexParam param = IndexParam.builder()
+                .fieldName("vector")
+                .indexName("XXX")
+                .indexType(IndexParam.IndexType.IVF_FLAT)
+                .metricType(IndexParam.MetricType.COSINE)
+                .extraParams(new HashMap<String,Object>(){{put("nlist", 64);}})
+                .build();
+
+        client.createIndex(CreateIndexReq.builder()
+                .collectionName(randomCollectionName)
+                .indexParams(Collections.singletonList(param))
+                .build());
+
+        descResp = client.describeIndex(DescribeIndexReq.builder()
+                .collectionName(randomCollectionName)
+                .fieldName("vector")
+                .build());
+        Assertions.assertEquals("XXX", descResp.getIndexName());
+        Assertions.assertEquals(IndexParam.IndexType.IVF_FLAT.name(), descResp.getIndexType());
+        Assertions.assertEquals(IndexParam.MetricType.COSINE.name(), descResp.getMetricType());
+        Map<String, Object> extraParams = descResp.getExtraParams();
+        Assertions.assertTrue(extraParams.containsKey("nlist"));
+        Assertions.assertEquals("64", extraParams.get("nlist"));
+    }
+
+    @Test
+    void testCacheCollectionSchema() {
+        String randomCollectionName = generator.generate(10);
+
+        client.createCollection(CreateCollectionReq.builder()
+                .collectionName(randomCollectionName)
+                .autoID(true)
+                .dimension(dimension)
+                .build());
+
+        // insert
+        JsonObject row = new JsonObject();
+        row.add("vector", GSON_INSTANCE.toJsonTree(generateFloatVectors(1).get(0)));
+        InsertResp insertResp = client.insert(InsertReq.builder()
+                .collectionName(randomCollectionName)
+                .data(Collections.singletonList(row))
+                .build());
+        Assertions.assertEquals(1L, insertResp.getInsertCnt());
+
+        // drop collection
+        client.dropCollection(DropCollectionReq.builder()
+                .collectionName(randomCollectionName)
+                .build());
+
+        // create a new collection with the same name, different schema
+        client.createCollection(CreateCollectionReq.builder()
+                .collectionName(randomCollectionName)
+                .autoID(true)
+                .dimension(100)
+                .build());
+
+        // insert wrong data
+        Assertions.assertThrows(MilvusClientException.class, ()->client.insert(InsertReq.builder()
+                .collectionName(randomCollectionName)
+                .data(Collections.singletonList(row))
+                .build()));
+
+        // insert correct data
+        List<Float> vector = new ArrayList<>();
+        for (int i = 0; i < 100; ++i) {
+            vector.add(RANDOM.nextFloat());
+        }
+        row.add("vector", GSON_INSTANCE.toJsonTree(vector));
+        insertResp = client.insert(InsertReq.builder()
+                .collectionName(randomCollectionName)
+                .data(Collections.singletonList(row))
+                .build());
+        Assertions.assertEquals(1L, insertResp.getInsertCnt());
+    }
+
+    @Test
+    public void testIterator() {
+        String randomCollectionName = generator.generate(10);
+        CreateCollectionReq.CollectionSchema collectionSchema = baseSchema();
+        collectionSchema.addField(AddFieldReq.builder()
+                .fieldName("float_vector")
+                .dataType(DataType.FloatVector)
+                .dimension(dimension)
+                .build());
+
+        List<IndexParam> indexParams = new ArrayList<>();
+        indexParams.add(IndexParam.builder()
+                .fieldName("float_vector")
+                .indexType(IndexParam.IndexType.FLAT)
+                .metricType(IndexParam.MetricType.L2)
+                .build());
+
+        CreateCollectionReq requestCreate = CreateCollectionReq.builder()
+                .collectionName(randomCollectionName)
+                .collectionSchema(collectionSchema)
+                .indexParams(indexParams)
+                .build();
+        client.createCollection(requestCreate);
+
+        // insert rows
+        long count = 10000;
+        List<JsonObject> data = generateRandomData(collectionSchema, count);
+        InsertResp insertResp = client.insert(InsertReq.builder()
+                .collectionName(randomCollectionName)
+                .data(data)
+                .build());
+        Assertions.assertEquals(count, insertResp.getInsertCnt());
+
+        // get row count
+        long rowCount = getRowCount(randomCollectionName);
+        Assertions.assertEquals(count, rowCount);
+
+        // search iterator
+        SearchIterator searchIterator = client.searchIterator(SearchIteratorReq.builder()
+                .collectionName(randomCollectionName)
+                .outputFields(Lists.newArrayList("*"))
+                .batchSize(20L)
+                .vectorFieldName("float_vector")
+                .vectors(Collections.singletonList(new FloatVec(generateFolatVector())))
+                .expr("int64_field > 500 && int64_field < 1000")
+                .params("{\"range_filter\": 5.0, \"radius\": 50.0}")
+                .topK(1000)
+                .metricType(IndexParam.MetricType.L2)
+                .consistencyLevel(ConsistencyLevel.EVENTUALLY)
+                .build());
+
+        int counter = 0;
+        while (true) {
+            List<QueryResultsWrapper.RowRecord> res = searchIterator.next();
+            if (res.isEmpty()) {
+                System.out.println("search iteration finished, close");
+                searchIterator.close();
+                break;
+            }
+
+            for (QueryResultsWrapper.RowRecord record : res) {
+                Assertions.assertInstanceOf(Float.class, record.get("score"));
+                Assertions.assertTrue((float)record.get("score") >= 5.0);
+                Assertions.assertTrue((float)record.get("score") <= 50.0);
+
+                Assertions.assertInstanceOf(Boolean.class, record.get("bool_field"));
+                Assertions.assertInstanceOf(Integer.class, record.get("int8_field"));
+                Assertions.assertInstanceOf(Integer.class, record.get("int16_field"));
+                Assertions.assertInstanceOf(Integer.class, record.get("int32_field"));
+                Assertions.assertInstanceOf(Long.class, record.get("int64_field"));
+                Assertions.assertInstanceOf(Float.class, record.get("float_field"));
+                Assertions.assertInstanceOf(Double.class, record.get("double_field"));
+                Assertions.assertInstanceOf(String.class, record.get("varchar_field"));
+                Assertions.assertInstanceOf(JsonObject.class, record.get("json_field"));
+                Assertions.assertInstanceOf(List.class, record.get("arr_int_field"));
+                Assertions.assertInstanceOf(List.class, record.get("float_vector"));
+
+                long int64Val = (long)record.get("int64_field");
+                Assertions.assertTrue(int64Val > 500L && int64Val < 1000L);
+
+                String varcharVal = (String)record.get("varchar_field");
+                Assertions.assertTrue(varcharVal.startsWith("varchar_"));
+
+                JsonObject jsonObj = (JsonObject)record.get("json_field");
+                Assertions.assertTrue(jsonObj.has(String.format("JSON_%d", int64Val)));
+
+                List<Integer> intArr = (List<Integer>)record.get("arr_int_field");
+                Assertions.assertTrue(intArr.size() <= 50); // max capacity 50 is defined in the baseSchema()
+
+                List<Float> floatVector = (List<Float>)record.get("float_vector");
+                Assertions.assertEquals(dimension, floatVector.size());
+
+                counter++;
+            }
+        }
+        System.out.println(String.format("There are %d items match distance between [5.0, 50.0]", counter));
+
+        // query iterator
+        QueryIterator queryIterator = client.queryIterator(QueryIteratorReq.builder()
+                .collectionName(randomCollectionName)
+                .expr("int64_field < 300")
+                .outputFields(Lists.newArrayList("*"))
+                .batchSize(50L)
+                .offset(5)
+                .limit(400)
+                .consistencyLevel(ConsistencyLevel.EVENTUALLY)
+                .build());
+
+        counter = 0;
+        while (true) {
+            List<QueryResultsWrapper.RowRecord> res = queryIterator.next();
+            if (res.isEmpty()) {
+                System.out.println("query iteration finished, close");
+                queryIterator.close();
+                break;
+            }
+
+            for (QueryResultsWrapper.RowRecord record : res) {
+                Assertions.assertInstanceOf(Boolean.class, record.get("bool_field"));
+                Assertions.assertInstanceOf(Integer.class, record.get("int8_field"));
+                Assertions.assertInstanceOf(Integer.class, record.get("int16_field"));
+                Assertions.assertInstanceOf(Integer.class, record.get("int32_field"));
+                Assertions.assertInstanceOf(Long.class, record.get("int64_field"));
+                Assertions.assertInstanceOf(Float.class, record.get("float_field"));
+                Assertions.assertInstanceOf(Double.class, record.get("double_field"));
+                Assertions.assertInstanceOf(String.class, record.get("varchar_field"));
+                Assertions.assertInstanceOf(JsonObject.class, record.get("json_field"));
+                Assertions.assertInstanceOf(List.class, record.get("arr_int_field"));
+                Assertions.assertInstanceOf(List.class, record.get("float_vector"));
+
+                long int64Val = (long)record.get("int64_field");
+                Assertions.assertTrue(int64Val < 300L);
+
+                String varcharVal = (String)record.get("varchar_field");
+                Assertions.assertTrue(varcharVal.startsWith("varchar_"));
+
+                JsonObject jsonObj = (JsonObject)record.get("json_field");
+                Assertions.assertTrue(jsonObj.has(String.format("JSON_%d", int64Val)));
+
+                List<Integer> intArr = (List<Integer>)record.get("arr_int_field");
+                Assertions.assertTrue(intArr.size() <= 50); // max capacity 50 is defined in the baseSchema()
+
+                List<Float> floatVector = (List<Float>)record.get("float_vector");
+                Assertions.assertEquals(dimension, floatVector.size());
+
+                counter++;
+            }
+        }
+        Assertions.assertEquals(295, counter);
+
+        client.dropCollection(DropCollectionReq.builder().collectionName(randomCollectionName).build());
+    }
 }