From ecaf455f2fd152e31aee6d6dc4f789044b3e5f85 Mon Sep 17 00:00:00 2001 From: yhmo Date: Wed, 24 Jun 2026 14:35:58 +0800 Subject: [PATCH] Support AddFunctionField/DropCollectionField/DropFunctionField Signed-off-by: yhmo --- .../java/io/milvus/v2/AddFieldExample.java | 65 +++++++ .../main/java/io/milvus/v2/CDCExample.java | 42 ++++- .../io/milvus/v2/client/MilvusClientV2.java | 30 ++++ .../service/collection/CollectionService.java | 117 ++++++++++++- .../request/AddFunctionFieldReq.java | 100 +++++++++++ .../request/DropCollectionFieldReq.java | 114 +++++++++++++ .../request/DropFunctionFieldReq.java | 101 +++++++++++ .../src/test/java/io/milvus/v2/BaseTest.java | 1 + .../v2/client/MilvusClientV2DockerTest.java | 67 ++++++++ .../milvus/v2/client/MilvusClientV2Test.java | 3 + .../v2/service/collection/CollectionTest.java | 160 ++++++++++++++++++ 11 files changed, 792 insertions(+), 8 deletions(-) create mode 100644 sdk-core/src/main/java/io/milvus/v2/service/collection/request/AddFunctionFieldReq.java create mode 100644 sdk-core/src/main/java/io/milvus/v2/service/collection/request/DropCollectionFieldReq.java create mode 100644 sdk-core/src/main/java/io/milvus/v2/service/collection/request/DropFunctionFieldReq.java diff --git a/examples/src/main/java/io/milvus/v2/AddFieldExample.java b/examples/src/main/java/io/milvus/v2/AddFieldExample.java index e23be0da9..bc3cf1065 100644 --- a/examples/src/main/java/io/milvus/v2/AddFieldExample.java +++ b/examples/src/main/java/io/milvus/v2/AddFieldExample.java @@ -22,6 +22,7 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; import io.milvus.v1.CommonUtils; +import io.milvus.common.clientenum.FunctionType; import io.milvus.v2.client.ConnectConfig; import io.milvus.v2.client.MilvusClientV2; import io.milvus.v2.common.ConsistencyLevel; @@ -29,8 +30,13 @@ import io.milvus.v2.common.IndexParam; import io.milvus.v2.service.collection.request.AddCollectionFieldReq; import io.milvus.v2.service.collection.request.AddFieldReq; +import io.milvus.v2.service.collection.request.AddFunctionFieldReq; import io.milvus.v2.service.collection.request.CreateCollectionReq; +import io.milvus.v2.service.collection.request.DescribeCollectionReq; +import io.milvus.v2.service.collection.request.DropCollectionFieldReq; import io.milvus.v2.service.collection.request.DropCollectionReq; +import io.milvus.v2.service.collection.request.DropFunctionFieldReq; +import io.milvus.v2.service.collection.response.DescribeCollectionResp; import io.milvus.v2.service.vector.request.InsertReq; import io.milvus.v2.service.vector.request.QueryReq; import io.milvus.v2.service.vector.response.QueryResp; @@ -107,6 +113,15 @@ private static void query(long id) { System.out.println("============================================================="); } + private static void describeCollection() { + DescribeCollectionResp resp = client.describeCollection(DescribeCollectionReq.builder() + .collectionName(COLLECTION_NAME) + .build()); + System.out.println("\nCollection fields:"); + System.out.println(resp.getFieldNames()); + System.out.println("============================================================="); + } + public static void main(String[] args) { createCollection(); @@ -132,6 +147,8 @@ public static void main(String[] args) { .isNullable(true) .build()); + describeCollection(); + // Query the previous row query(100L); } @@ -151,6 +168,54 @@ public static void main(String[] args) { query(500L); } + { + // Drop the field added above + client.dropCollectionField(DropCollectionFieldReq.builder() + .collectionName(COLLECTION_NAME) + .fieldName("text") + .build()); + System.out.println("Field 'text' dropped"); + describeCollection(); + } + + { + // Add the text field back for function-field demo + client.addCollectionField(AddCollectionFieldReq.builder() + .collectionName(COLLECTION_NAME) + .fieldName("text") + .dataType(DataType.VarChar) + .maxLength(100) + .enableAnalyzer(true) + .enableMatch(true) + .isNullable(true) + .build()); + + // Add a function-backed sparse field + client.addFunctionField(AddFunctionFieldReq.builder() + .collectionName(COLLECTION_NAME) + .fieldName("sparse") + .dataType(DataType.SparseFloatVector) + .function(CreateCollectionReq.Function.builder() + .name("bm25") + .functionType(FunctionType.BM25) + .inputFieldNames(Collections.singletonList("text")) + .outputFieldNames(Collections.singletonList("sparse")) + .build()) + .build()); + System.out.println("Function-backed field 'sparse' added"); + describeCollection(); + } + + { + // Drop the function and its output field + client.dropFunctionField(DropFunctionFieldReq.builder() + .collectionName(COLLECTION_NAME) + .functionName("bm25") + .build()); + System.out.println("Function-backed field 'sparse' dropped"); + describeCollection(); + } + client.close(); } } diff --git a/examples/src/main/java/io/milvus/v2/CDCExample.java b/examples/src/main/java/io/milvus/v2/CDCExample.java index fd2e53cb2..ae95f421e 100644 --- a/examples/src/main/java/io/milvus/v2/CDCExample.java +++ b/examples/src/main/java/io/milvus/v2/CDCExample.java @@ -22,19 +22,25 @@ import io.milvus.v2.client.ConnectConfig; import io.milvus.v2.client.MilvusClientV2; import io.milvus.v2.service.cdc.request.CrossClusterTopology; +import io.milvus.v2.service.cdc.request.DumpMessagesReq; +import io.milvus.v2.service.cdc.request.GetReplicateInfoReq; import io.milvus.v2.service.cdc.request.MilvusCluster; import io.milvus.v2.service.cdc.request.ReplicateConfiguration; import io.milvus.v2.service.cdc.request.UpdateReplicateConfigurationReq; +import io.milvus.v2.service.cdc.response.DumpMessageInfo; +import io.milvus.v2.service.cdc.response.DumpMessagesResp; +import io.milvus.v2.service.cdc.response.GetReplicateConfigurationResp; +import io.milvus.v2.service.cdc.response.GetReplicateInfoResp; import java.util.ArrayList; import java.util.List; public class CDCExample { - private static final String clusterAURI = "http://192.168.1.1:19530"; - private static final String clusterBURI = "http://192.168.1.1:19500"; + private static final String clusterAURI = "http://localhost:19530"; + private static final String clusterBURI = "http://localhost:29530"; - private static final String clusterAId = "cdc-test-upstream"; - private static final String clusterBId = "cdc-test-downstream"; + private static final String clusterAId = "cdc-a"; + private static final String clusterBId = "cdc-b"; private static final Integer pchannelNum = 16; @@ -51,11 +57,13 @@ public static void main(String[] args) { .uri(clusterAURI) .build(); MilvusClientV2 clusterAClient = new MilvusClientV2(clusterA); + System.out.println("Cluster A connected: " + clusterAClient.getServerVersion()); ConnectConfig clusterB = ConnectConfig.builder() .uri(clusterBURI) .build(); MilvusClientV2 clusterBClient = new MilvusClientV2(clusterB); + System.out.println("Cluster B connected: " + clusterBClient.getServerVersion()); MilvusCluster milvusClusterA = MilvusCluster.builder() .clusterId(clusterAId) @@ -88,6 +96,32 @@ public static void main(String[] args) { .build(); clusterAClient.updateReplicateConfiguration(updateReq); + System.out.println("Replicate configuration updated for cluster A"); clusterBClient.updateReplicateConfiguration(updateReq); + System.out.println("Replicate configuration updated for cluster B"); + + GetReplicateConfigurationResp replicateConfigurationResp = clusterAClient.getReplicateConfiguration(); + System.out.println("Replicate configuration: " + replicateConfigurationResp.getReplicateConfiguration()); + + GetReplicateInfoResp replicateInfoResp = clusterBClient.getReplicateInfo(GetReplicateInfoReq.builder() + .sourceClusterId(clusterAId) + .targetPchannel(generatePChannels(clusterBId).get(0)) + .build()); + System.out.println("Replicate info: " + replicateInfoResp); + + String dumpStartMessageId = replicateInfoResp.getSalvageCheckpoint().getMessageID().getId(); + + DumpMessagesResp dumpMessagesResp = clusterAClient.dumpMessages(DumpMessagesReq.builder() + .pchannel(generatePChannels(clusterAId).get(0)) + .startMessageID(GetReplicateInfoResp.MessageID.builder() + .id(dumpStartMessageId) + .walName("RocksMQ") + .build()) + .build()); + System.out.println("Dump messages:"); + for (DumpMessageInfo message : dumpMessagesResp) { + System.out.println("\tmessage id: " + message.getMessageID().getId()); + System.out.println("\tpayload size: " + message.getPayload().length); + } } } diff --git a/sdk-core/src/main/java/io/milvus/v2/client/MilvusClientV2.java b/sdk-core/src/main/java/io/milvus/v2/client/MilvusClientV2.java index 295f80d04..04f57fa75 100644 --- a/sdk-core/src/main/java/io/milvus/v2/client/MilvusClientV2.java +++ b/sdk-core/src/main/java/io/milvus/v2/client/MilvusClientV2.java @@ -509,6 +509,15 @@ public void alterCollectionField(AlterCollectionFieldReq request) { rpcUtils.retry(() -> collectionService.alterCollectionField(this.getRpcStub(), request)); } + /** + * Drop a field from collection. + * + * @param request drop field request + */ + public void dropCollectionField(DropCollectionFieldReq request) { + rpcUtils.retry(() -> collectionService.dropCollectionField(this.getRpcStub(), request)); + } + /** * drop a collection's properties. * @@ -643,6 +652,15 @@ public void addCollectionFunction(AddCollectionFunctionReq request) { rpcUtils.retry(() -> collectionService.addCollectionFunction(this.getRpcStub(), request)); } + /** + * Add a function-backed field to collection. + * + * @param request add function field request + */ + public void addFunctionField(AddFunctionFieldReq request) { + rpcUtils.retry(() -> collectionService.addFunctionField(this.getRpcStub(), request)); + } + /** * Alter a function of collection. * @@ -654,6 +672,7 @@ public void alterCollectionFunction(AlterCollectionFunctionReq request) { /** * Drop a function of collection. + * This removes only the function definition and keeps its output field(s). * * @param request drop function request */ @@ -661,6 +680,17 @@ public void dropCollectionFunction(DropCollectionFunctionReq request) { rpcUtils.retry(() -> collectionService.dropCollectionFunction(this.getRpcStub(), request)); } + /** + * Drop a function-backed field from collection. + * This removes the function and its output field(s), unlike dropCollectionFunction, + * which only drops the function definition. + * + * @param request drop function field request + */ + public void dropFunctionField(DropFunctionFieldReq request) { + rpcUtils.retry(() -> collectionService.dropFunctionField(this.getRpcStub(), request)); + } + ///////////////////////////////////////////////////////////////////////////////////////////// // Index Operations ///////////////////////////////////////////////////////////////////////////////////////////// diff --git a/sdk-core/src/main/java/io/milvus/v2/service/collection/CollectionService.java b/sdk-core/src/main/java/io/milvus/v2/service/collection/CollectionService.java index 1f63488aa..15fc1e207 100644 --- a/sdk-core/src/main/java/io/milvus/v2/service/collection/CollectionService.java +++ b/sdk-core/src/main/java/io/milvus/v2/service/collection/CollectionService.java @@ -343,6 +343,40 @@ public Void alterCollectionField(MilvusServiceGrpc.MilvusServiceBlockingStub blo return null; } + public Void dropCollectionField(MilvusServiceGrpc.MilvusServiceBlockingStub blockingStub, DropCollectionFieldReq request) { + String dbName = request.getDatabaseName(); + String collectionName = request.getCollectionName(); + String fieldName = request.getFieldName(); + Long fieldId = request.getFieldId(); + boolean hasFieldName = StringUtils.isNotBlank(fieldName); + boolean hasFieldId = fieldId != null && fieldId > 0; + if (hasFieldName == hasFieldId) { + throw new MilvusClientException(ErrorCode.INVALID_PARAMS, + "Exactly one of fieldName or fieldId must be provided."); + } + + String title = String.format("Drop field of collection: '%s' in database: '%s'", collectionName, dbName); + AlterCollectionSchemaRequest.DropRequest.Builder dropBuilder = AlterCollectionSchemaRequest.DropRequest.newBuilder(); + if (hasFieldName) { + dropBuilder.setFieldName(fieldName); + } else { + dropBuilder.setFieldId(fieldId); + } + + AlterCollectionSchemaRequest.Builder builder = AlterCollectionSchemaRequest.newBuilder() + .setCollectionName(collectionName) + .setAction(AlterCollectionSchemaRequest.Action.newBuilder() + .setDropRequest(dropBuilder) + .build()); + if (StringUtils.isNotEmpty(dbName)) { + builder.setDbName(dbName); + } + + AlterCollectionSchemaResponse response = blockingStub.alterCollectionSchema(builder.build()); + rpcUtils.handleResponse(title, response.getAlterStatus()); + return null; + } + public Void dropCollectionProperties(MilvusServiceGrpc.MilvusServiceBlockingStub blockingStub, DropCollectionPropertiesReq request) { String dbName = request.getDatabaseName(); String collectionName = request.getCollectionName(); @@ -697,6 +731,49 @@ public Void addCollectionFunction(MilvusServiceGrpc.MilvusServiceBlockingStub bl return null; } + public Void addFunctionField(MilvusServiceGrpc.MilvusServiceBlockingStub blockingStub, + AddFunctionFieldReq request) { + if (request.getFunction() == null) { + throw new MilvusClientException(ErrorCode.INVALID_PARAMS, "Function cannot be null."); + } + if (CollectionUtils.isEmpty(request.getFunction().getOutputFieldNames()) + || request.getFunction().getOutputFieldNames().size() != 1 + || !StringUtils.equals(request.getFieldName(), request.getFunction().getOutputFieldNames().get(0))) { + throw new MilvusClientException(ErrorCode.INVALID_PARAMS, + "Function must have exactly one output field matching the field being added."); + } + + String dbName = request.getDatabaseName(); + String collectionName = request.getCollectionName(); + String title = String.format("Add function field to collection: '%s' in database: '%s'", collectionName, dbName); + + CreateCollectionReq.FieldSchema fieldSchema = SchemaUtils.convertFieldReqToFieldSchema(request); + FieldSchema grpcFieldSchema = SchemaUtils.convertToGrpcFieldSchema(fieldSchema) + .toBuilder() + .setIsFunctionOutput(true) + .build(); + AlterCollectionSchemaRequest.FieldInfo fieldInfo = AlterCollectionSchemaRequest.FieldInfo.newBuilder() + .setFieldSchema(grpcFieldSchema) + .build(); + AlterCollectionSchemaRequest.AddRequest addRequest = AlterCollectionSchemaRequest.AddRequest.newBuilder() + .addFieldInfos(fieldInfo) + .addFuncSchema(SchemaUtils.convertToGrpcFunction(request.getFunction())) + .build(); + + AlterCollectionSchemaRequest.Builder builder = AlterCollectionSchemaRequest.newBuilder() + .setCollectionName(collectionName) + .setAction(AlterCollectionSchemaRequest.Action.newBuilder() + .setAddRequest(addRequest) + .build()); + if (StringUtils.isNotEmpty(dbName)) { + builder.setDbName(dbName); + } + + AlterCollectionSchemaResponse response = blockingStub.alterCollectionSchema(builder.build()); + rpcUtils.handleResponse(title, response.getAlterStatus()); + return null; + } + public Void alterCollectionFunction(MilvusServiceGrpc.MilvusServiceBlockingStub blockingStub, AlterCollectionFunctionReq request) { if (request.getFunction() == null) { @@ -728,15 +805,47 @@ public Void dropCollectionFunction(MilvusServiceGrpc.MilvusServiceBlockingStub b String dbName = request.getDatabaseName(); String collectionName = request.getCollectionName(); String title = String.format("Drop function to collection: '%s' in database: '%s'", collectionName, dbName); - DropCollectionFunctionRequest.Builder builder = DropCollectionFunctionRequest.newBuilder() + AlterCollectionSchemaRequest.Builder builder = AlterCollectionSchemaRequest.newBuilder() .setCollectionName(collectionName) - .setFunctionName(request.getFunctionName()); + .setAction(AlterCollectionSchemaRequest.Action.newBuilder() + .setDropRequest(AlterCollectionSchemaRequest.DropRequest.newBuilder() + .setFunctionName(request.getFunctionName()) + .setDropFunctionOutputFields(false) + .build()) + .build()); + if (StringUtils.isNotEmpty(dbName)) { + builder.setDbName(dbName); + } + + AlterCollectionSchemaResponse response = blockingStub.alterCollectionSchema(builder.build()); + rpcUtils.handleResponse(title, response.getAlterStatus()); + + return null; + } + + public Void dropFunctionField(MilvusServiceGrpc.MilvusServiceBlockingStub blockingStub, + DropFunctionFieldReq request) { + if (StringUtils.isEmpty(request.getFunctionName())) { + throw new MilvusClientException(ErrorCode.INVALID_PARAMS, "Function name cannot be empty."); + } + + String dbName = request.getDatabaseName(); + String collectionName = request.getCollectionName(); + String title = String.format("Drop function field of collection: '%s' in database: '%s'", collectionName, dbName); + AlterCollectionSchemaRequest.Builder builder = AlterCollectionSchemaRequest.newBuilder() + .setCollectionName(collectionName) + .setAction(AlterCollectionSchemaRequest.Action.newBuilder() + .setDropRequest(AlterCollectionSchemaRequest.DropRequest.newBuilder() + .setFunctionName(request.getFunctionName()) + .setDropFunctionOutputFields(true) + .build()) + .build()); if (StringUtils.isNotEmpty(dbName)) { builder.setDbName(dbName); } - Status status = blockingStub.dropCollectionFunction(builder.build()); - rpcUtils.handleResponse(title, status); + AlterCollectionSchemaResponse response = blockingStub.alterCollectionSchema(builder.build()); + rpcUtils.handleResponse(title, response.getAlterStatus()); return null; } diff --git a/sdk-core/src/main/java/io/milvus/v2/service/collection/request/AddFunctionFieldReq.java b/sdk-core/src/main/java/io/milvus/v2/service/collection/request/AddFunctionFieldReq.java new file mode 100644 index 000000000..a5e6cbbf1 --- /dev/null +++ b/sdk-core/src/main/java/io/milvus/v2/service/collection/request/AddFunctionFieldReq.java @@ -0,0 +1,100 @@ +/* + * 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.service.collection.request; + +public class AddFunctionFieldReq extends AddFieldReq { + private String collectionName; + private String databaseName; + private CreateCollectionReq.Function function; + + private AddFunctionFieldReq(AddFunctionFieldReqBuilder builder) { + super(builder); + this.collectionName = builder.collectionName; + this.databaseName = builder.databaseName; + this.function = builder.function; + } + + public String getCollectionName() { + return collectionName; + } + + public void setCollectionName(String collectionName) { + this.collectionName = collectionName; + } + + public String getDatabaseName() { + return databaseName; + } + + public void setDatabaseName(String databaseName) { + this.databaseName = databaseName; + } + + public CreateCollectionReq.Function getFunction() { + return function; + } + + public void setFunction(CreateCollectionReq.Function function) { + this.function = function; + } + + @Override + public String toString() { + return "AddFunctionFieldReq{" + + "collectionName='" + collectionName + '\'' + + ", databaseName='" + databaseName + '\'' + + ", function=" + function + + ", " + super.toString() + + '}'; + } + + public static AddFunctionFieldReqBuilder builder() { + return new AddFunctionFieldReqBuilder(); + } + + public static class AddFunctionFieldReqBuilder extends AddFieldReq.AddFieldReqBuilder { + private String collectionName = ""; + private String databaseName = ""; + private CreateCollectionReq.Function function; + + private AddFunctionFieldReqBuilder() { + } + + public AddFunctionFieldReqBuilder collectionName(String collectionName) { + this.collectionName = collectionName; + return this; + } + + public AddFunctionFieldReqBuilder databaseName(String databaseName) { + this.databaseName = databaseName; + return this; + } + + public AddFunctionFieldReqBuilder function(CreateCollectionReq.Function function) { + this.function = function; + return this; + } + + @Override + public AddFunctionFieldReq build() { + return new AddFunctionFieldReq(this); + } + } +} diff --git a/sdk-core/src/main/java/io/milvus/v2/service/collection/request/DropCollectionFieldReq.java b/sdk-core/src/main/java/io/milvus/v2/service/collection/request/DropCollectionFieldReq.java new file mode 100644 index 000000000..70cb40422 --- /dev/null +++ b/sdk-core/src/main/java/io/milvus/v2/service/collection/request/DropCollectionFieldReq.java @@ -0,0 +1,114 @@ +/* + * 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.service.collection.request; + +public class DropCollectionFieldReq { + private String collectionName; + private String databaseName; + private String fieldName; + private Long fieldId; + + private DropCollectionFieldReq(DropCollectionFieldReqBuilder builder) { + this.collectionName = builder.collectionName; + this.databaseName = builder.databaseName; + this.fieldName = builder.fieldName; + this.fieldId = builder.fieldId; + } + + public String getCollectionName() { + return collectionName; + } + + public void setCollectionName(String collectionName) { + this.collectionName = collectionName; + } + + public String getDatabaseName() { + return databaseName; + } + + public void setDatabaseName(String databaseName) { + this.databaseName = databaseName; + } + + public String getFieldName() { + return fieldName; + } + + public void setFieldName(String fieldName) { + this.fieldName = fieldName; + } + + public Long getFieldId() { + return fieldId; + } + + public void setFieldId(Long fieldId) { + this.fieldId = fieldId; + } + + @Override + public String toString() { + return "DropCollectionFieldReq{" + + "collectionName='" + collectionName + '\'' + + ", databaseName='" + databaseName + '\'' + + ", fieldName='" + fieldName + '\'' + + ", fieldId=" + fieldId + + '}'; + } + + public static DropCollectionFieldReqBuilder builder() { + return new DropCollectionFieldReqBuilder(); + } + + public static class DropCollectionFieldReqBuilder { + private String collectionName = ""; + private String databaseName = ""; + private String fieldName = ""; + private Long fieldId; + + private DropCollectionFieldReqBuilder() { + } + + public DropCollectionFieldReqBuilder collectionName(String collectionName) { + this.collectionName = collectionName; + return this; + } + + public DropCollectionFieldReqBuilder databaseName(String databaseName) { + this.databaseName = databaseName; + return this; + } + + public DropCollectionFieldReqBuilder fieldName(String fieldName) { + this.fieldName = fieldName; + return this; + } + + public DropCollectionFieldReqBuilder fieldId(Long fieldId) { + this.fieldId = fieldId; + return this; + } + + public DropCollectionFieldReq build() { + return new DropCollectionFieldReq(this); + } + } +} diff --git a/sdk-core/src/main/java/io/milvus/v2/service/collection/request/DropFunctionFieldReq.java b/sdk-core/src/main/java/io/milvus/v2/service/collection/request/DropFunctionFieldReq.java new file mode 100644 index 000000000..035d06d82 --- /dev/null +++ b/sdk-core/src/main/java/io/milvus/v2/service/collection/request/DropFunctionFieldReq.java @@ -0,0 +1,101 @@ +/* + * 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.service.collection.request; + +/** + * Request to drop a function and its output field(s) from a collection. + * Unlike DropCollectionFunctionReq, this also removes the function output fields. + */ +public class DropFunctionFieldReq { + private String collectionName; + private String databaseName; + private String functionName; + + private DropFunctionFieldReq(DropFunctionFieldReqBuilder builder) { + this.collectionName = builder.collectionName; + this.databaseName = builder.databaseName; + this.functionName = builder.functionName; + } + + public String getCollectionName() { + return collectionName; + } + + public void setCollectionName(String collectionName) { + this.collectionName = collectionName; + } + + public String getDatabaseName() { + return databaseName; + } + + public void setDatabaseName(String databaseName) { + this.databaseName = databaseName; + } + + public String getFunctionName() { + return functionName; + } + + public void setFunctionName(String functionName) { + this.functionName = functionName; + } + + @Override + public String toString() { + return "DropFunctionFieldReq{" + + "collectionName='" + collectionName + '\'' + + ", databaseName='" + databaseName + '\'' + + ", functionName='" + functionName + '\'' + + '}'; + } + + public static DropFunctionFieldReqBuilder builder() { + return new DropFunctionFieldReqBuilder(); + } + + public static class DropFunctionFieldReqBuilder { + private String collectionName = ""; + private String databaseName = ""; + private String functionName = ""; + + private DropFunctionFieldReqBuilder() { + } + + public DropFunctionFieldReqBuilder collectionName(String collectionName) { + this.collectionName = collectionName; + return this; + } + + public DropFunctionFieldReqBuilder databaseName(String databaseName) { + this.databaseName = databaseName; + return this; + } + + public DropFunctionFieldReqBuilder functionName(String functionName) { + this.functionName = functionName; + return this; + } + + public DropFunctionFieldReq build() { + return new DropFunctionFieldReq(this); + } + } +} diff --git a/sdk-core/src/test/java/io/milvus/v2/BaseTest.java b/sdk-core/src/test/java/io/milvus/v2/BaseTest.java index 35c47a537..690151cc3 100644 --- a/sdk-core/src/test/java/io/milvus/v2/BaseTest.java +++ b/sdk-core/src/test/java/io/milvus/v2/BaseTest.java @@ -113,6 +113,7 @@ public void setUp() { when(blockingStub.showCollections(any(ShowCollectionsRequest.class))).thenReturn(ShowCollectionsResponse.newBuilder().setStatus(successStatus).addAllCollectionNames(Collections.singletonList("test")).build()); when(blockingStub.createCollection(any(CreateCollectionRequest.class))).thenReturn(successStatus); when(blockingStub.addCollectionStructField(any())).thenReturn(successStatus); + when(blockingStub.alterCollectionSchema(any())).thenReturn(AlterCollectionSchemaResponse.newBuilder().setAlterStatus(successStatus).build()); when(blockingStub.loadCollection(any())).thenReturn(successStatus); when(blockingStub.releaseCollection(any())).thenReturn(successStatus); when(blockingStub.getLoadState(any())).thenReturn(GetLoadStateResponse.newBuilder().setState(LoadState.LoadStateLoaded).setStatus(successStatus).build()); diff --git a/sdk-core/src/test/java/io/milvus/v2/client/MilvusClientV2DockerTest.java b/sdk-core/src/test/java/io/milvus/v2/client/MilvusClientV2DockerTest.java index 60de15ebd..72778e7c0 100644 --- a/sdk-core/src/test/java/io/milvus/v2/client/MilvusClientV2DockerTest.java +++ b/sdk-core/src/test/java/io/milvus/v2/client/MilvusClientV2DockerTest.java @@ -3855,6 +3855,73 @@ void testDynamicField() { Assertions.assertEquals(DataType.VarChar, field.getDataType()); Assertions.assertEquals(100, field.getMaxLength()); Assertions.assertTrue(field.getIsNullable()); + + client.dropCollectionField(DropCollectionFieldReq.builder() + .collectionName(collectionName) + .fieldName("flag") + .build()); + descResp = client.describeCollection(DescribeCollectionReq.builder() + .collectionName(collectionName) + .build()); + fieldNames = descResp.getFieldNames(); + Assertions.assertFalse(fieldNames.contains("flag")); + Assertions.assertTrue(fieldNames.contains("text")); + } + + @Test + void testFunctionFieldLifecycle() { + String collectionName = generator.generate(10); + createSimpleCollection(client, "", collectionName, "id", false, DIMENSION, ConsistencyLevel.BOUNDED); + + client.addCollectionField(AddCollectionFieldReq.builder() + .collectionName(collectionName) + .fieldName("text") + .dataType(DataType.VarChar) + .maxLength(100) + .enableAnalyzer(true) + .enableMatch(true) + .analyzerParams(new HashMap() {{ + put("tokenizer", "standard"); + }}) + .isNullable(true) + .build()); + + client.addFunctionField(AddFunctionFieldReq.builder() + .collectionName(collectionName) + .fieldName("sparse") + .dataType(DataType.SparseFloatVector) + .function(CreateCollectionReq.Function.builder() + .name("bm25") + .description("desc bm25") + .functionType(FunctionType.BM25) + .inputFieldNames(Collections.singletonList("text")) + .outputFieldNames(Collections.singletonList("sparse")) + .build()) + .build()); + + DescribeCollectionResp descResp = client.describeCollection(DescribeCollectionReq.builder() + .collectionName(collectionName) + .build()); + List fieldNames = descResp.getFieldNames(); + Assertions.assertTrue(fieldNames.contains("text")); + Assertions.assertTrue(fieldNames.contains("sparse")); + List functions = descResp.getCollectionSchema().getFunctionList(); + Assertions.assertEquals(1, functions.size()); + Assertions.assertEquals("bm25", functions.get(0).getName()); + Assertions.assertEquals("sparse", functions.get(0).getOutputFieldNames().get(0)); + + client.dropFunctionField(DropFunctionFieldReq.builder() + .collectionName(collectionName) + .functionName("bm25") + .build()); + + descResp = client.describeCollection(DescribeCollectionReq.builder() + .collectionName(collectionName) + .build()); + fieldNames = descResp.getFieldNames(); + Assertions.assertTrue(fieldNames.contains("text")); + Assertions.assertFalse(fieldNames.contains("sparse")); + Assertions.assertTrue(descResp.getCollectionSchema().getFunctionList().isEmpty()); } @Test diff --git a/sdk-core/src/test/java/io/milvus/v2/client/MilvusClientV2Test.java b/sdk-core/src/test/java/io/milvus/v2/client/MilvusClientV2Test.java index 20a49cb23..e1e895061 100644 --- a/sdk-core/src/test/java/io/milvus/v2/client/MilvusClientV2Test.java +++ b/sdk-core/src/test/java/io/milvus/v2/client/MilvusClientV2Test.java @@ -552,8 +552,11 @@ void testV2BuilderClasses() { VerifyClass(CreateCollectionReq.Function.class.getName(), config); VerifyClass(CreateCollectionReq.StructFieldSchema.class.getName(), config); VerifyClass(DescribeCollectionReq.class.getName(), config); + VerifyClass(DropCollectionFieldReq.class.getName(), config); VerifyClass(DropCollectionFieldPropertiesReq.class.getName(), config); + VerifyClass(DropFunctionFieldReq.class.getName(), config); VerifyClass(DropCollectionPropertiesReq.class.getName(), config); + VerifyClass(AddFunctionFieldReq.class.getName(), config); VerifyClass(DropCollectionReq.class.getName(), config); VerifyClass(GetCollectionStatsReq.class.getName(), config); VerifyClass(GetLoadStateReq.class.getName(), config); diff --git a/sdk-core/src/test/java/io/milvus/v2/service/collection/CollectionTest.java b/sdk-core/src/test/java/io/milvus/v2/service/collection/CollectionTest.java index 5c3e765bf..b71eaddc1 100644 --- a/sdk-core/src/test/java/io/milvus/v2/service/collection/CollectionTest.java +++ b/sdk-core/src/test/java/io/milvus/v2/service/collection/CollectionTest.java @@ -420,6 +420,166 @@ void testGetLoadState() { logger.info("resp: {}", resp); } + @Test + void testDropCollectionFieldByName() { + client_v2.dropCollectionField(DropCollectionFieldReq.builder() + .databaseName("default") + .collectionName("test") + .fieldName("text") + .build()); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(io.milvus.grpc.AlterCollectionSchemaRequest.class); + verify(blockingStub).alterCollectionSchema(captor.capture()); + io.milvus.grpc.AlterCollectionSchemaRequest rpcRequest = captor.getValue(); + Assertions.assertEquals("default", rpcRequest.getDbName()); + Assertions.assertEquals("test", rpcRequest.getCollectionName()); + Assertions.assertEquals("text", rpcRequest.getAction().getDropRequest().getFieldName()); + Assertions.assertFalse(rpcRequest.getAction().getDropRequest().hasFieldId()); + } + + @Test + void testDropCollectionFieldById() { + client_v2.dropCollectionField(DropCollectionFieldReq.builder() + .collectionName("test") + .fieldId(100L) + .build()); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(io.milvus.grpc.AlterCollectionSchemaRequest.class); + verify(blockingStub).alterCollectionSchema(captor.capture()); + io.milvus.grpc.AlterCollectionSchemaRequest rpcRequest = captor.getValue(); + Assertions.assertEquals("test", rpcRequest.getCollectionName()); + Assertions.assertEquals(100L, rpcRequest.getAction().getDropRequest().getFieldId()); + Assertions.assertFalse(rpcRequest.getAction().getDropRequest().hasFieldName()); + } + + @Test + void testDropCollectionFieldRejectsMissingIdentifier() { + MilvusClientException exception = Assertions.assertThrows(MilvusClientException.class, + () -> client_v2.dropCollectionField(DropCollectionFieldReq.builder() + .collectionName("test") + .build())); + Assertions.assertEquals(io.milvus.v2.exception.ErrorCode.INVALID_PARAMS, exception.getErrorCode()); + } + + @Test + void testDropCollectionFieldRejectsMultipleIdentifiers() { + MilvusClientException exception = Assertions.assertThrows(MilvusClientException.class, + () -> client_v2.dropCollectionField(DropCollectionFieldReq.builder() + .collectionName("test") + .fieldName("text") + .fieldId(100L) + .build())); + Assertions.assertEquals(io.milvus.v2.exception.ErrorCode.INVALID_PARAMS, exception.getErrorCode()); + } + + @Test + void testAddFunctionField() { + client_v2.addFunctionField(AddFunctionFieldReq.builder() + .databaseName("default") + .collectionName("test") + .fieldName("sparse") + .dataType(DataType.SparseFloatVector) + .function(CreateCollectionReq.Function.builder() + .name("bm25") + .functionType(io.milvus.common.clientenum.FunctionType.BM25) + .inputFieldNames(Collections.singletonList("text")) + .outputFieldNames(Collections.singletonList("sparse")) + .build()) + .build()); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(io.milvus.grpc.AlterCollectionSchemaRequest.class); + verify(blockingStub).alterCollectionSchema(captor.capture()); + io.milvus.grpc.AlterCollectionSchemaRequest rpcRequest = captor.getValue(); + Assertions.assertEquals("default", rpcRequest.getDbName()); + Assertions.assertEquals("test", rpcRequest.getCollectionName()); + Assertions.assertEquals(1, rpcRequest.getAction().getAddRequest().getFieldInfosCount()); + Assertions.assertEquals(1, rpcRequest.getAction().getAddRequest().getFuncSchemaCount()); + Assertions.assertEquals("sparse", rpcRequest.getAction().getAddRequest().getFieldInfos(0).getFieldSchema().getName()); + Assertions.assertEquals(io.milvus.grpc.DataType.SparseFloatVector, + rpcRequest.getAction().getAddRequest().getFieldInfos(0).getFieldSchema().getDataType()); + Assertions.assertTrue(rpcRequest.getAction().getAddRequest().getFieldInfos(0).getFieldSchema().getIsFunctionOutput()); + Assertions.assertFalse(rpcRequest.getAction().getAddRequest().getFieldInfos(0).getFieldSchema().getNullable()); + Assertions.assertEquals("bm25", rpcRequest.getAction().getAddRequest().getFuncSchema(0).getName()); + Assertions.assertEquals("text", rpcRequest.getAction().getAddRequest().getFuncSchema(0).getInputFieldNames(0)); + Assertions.assertEquals("sparse", rpcRequest.getAction().getAddRequest().getFuncSchema(0).getOutputFieldNames(0)); + } + + @Test + void testAddFunctionFieldRejectsNullFunction() { + MilvusClientException exception = Assertions.assertThrows(MilvusClientException.class, + () -> client_v2.addFunctionField(AddFunctionFieldReq.builder() + .collectionName("test") + .fieldName("sparse") + .dataType(DataType.SparseFloatVector) + .build())); + Assertions.assertEquals(io.milvus.v2.exception.ErrorCode.INVALID_PARAMS, exception.getErrorCode()); + } + + @Test + void testAddFunctionFieldRejectsMismatchedOutputField() { + MilvusClientException exception = Assertions.assertThrows(MilvusClientException.class, + () -> client_v2.addFunctionField(AddFunctionFieldReq.builder() + .collectionName("test") + .fieldName("sparse") + .dataType(DataType.SparseFloatVector) + .function(CreateCollectionReq.Function.builder() + .name("bm25") + .functionType(io.milvus.common.clientenum.FunctionType.BM25) + .inputFieldNames(Collections.singletonList("text")) + .outputFieldNames(Collections.singletonList("other")) + .build()) + .build())); + Assertions.assertEquals(io.milvus.v2.exception.ErrorCode.INVALID_PARAMS, exception.getErrorCode()); + } + + @Test + void testDropCollectionFunction() { + client_v2.dropCollectionFunction(DropCollectionFunctionReq.builder() + .databaseName("default") + .collectionName("test") + .functionName("bm25") + .build()); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(io.milvus.grpc.AlterCollectionSchemaRequest.class); + verify(blockingStub).alterCollectionSchema(captor.capture()); + io.milvus.grpc.AlterCollectionSchemaRequest rpcRequest = captor.getValue(); + Assertions.assertEquals("default", rpcRequest.getDbName()); + Assertions.assertEquals("test", rpcRequest.getCollectionName()); + Assertions.assertEquals("bm25", rpcRequest.getAction().getDropRequest().getFunctionName()); + Assertions.assertFalse(rpcRequest.getAction().getDropRequest().getDropFunctionOutputFields()); + } + + @Test + void testDropFunctionField() { + client_v2.dropFunctionField(DropFunctionFieldReq.builder() + .databaseName("default") + .collectionName("test") + .functionName("bm25") + .build()); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(io.milvus.grpc.AlterCollectionSchemaRequest.class); + verify(blockingStub).alterCollectionSchema(captor.capture()); + io.milvus.grpc.AlterCollectionSchemaRequest rpcRequest = captor.getValue(); + Assertions.assertEquals("default", rpcRequest.getDbName()); + Assertions.assertEquals("test", rpcRequest.getCollectionName()); + Assertions.assertEquals("bm25", rpcRequest.getAction().getDropRequest().getFunctionName()); + Assertions.assertTrue(rpcRequest.getAction().getDropRequest().getDropFunctionOutputFields()); + } + + @Test + void testDropFunctionFieldRejectsEmptyFunctionName() { + MilvusClientException exception = Assertions.assertThrows(MilvusClientException.class, + () -> client_v2.dropFunctionField(DropFunctionFieldReq.builder() + .collectionName("test") + .build())); + Assertions.assertEquals(io.milvus.v2.exception.ErrorCode.INVALID_PARAMS, exception.getErrorCode()); + } + @Test void testGetCollectionStats() { GetCollectionStatsReq req = GetCollectionStatsReq.builder()