Files
milvus-io--milvus/internal/distributed/proxy/httpserver/handler_v2.go
T
wehub-resource-sync 498b235461
Build and test / Build and test AMD64 Ubuntu 22.04 (push) Failing after 0s
Publish Builder / amazonlinux2023 (push) Failing after 1s
Build and test / UT for Go (push) Has been skipped
Publish KRTE Images / KRTE (push) Failing after 1s
Build and test / Integration Test (push) Has been skipped
Build and test / Upload Code Coverage (push) Has been skipped
Publish Builder / rockylinux9 (push) Failing after 1s
Publish Builder / ubuntu22.04 (push) Failing after 0s
Publish Builder / ubuntu24.04 (push) Failing after 0s
Publish Gpu Builder / publish-gpu-builder (push) Failing after 1s
Publish Test Images / PyTest (push) Failing after 0s
Build and test / UT for Cpp (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:31:17 +08:00

4039 lines
175 KiB
Go

// Licensed to the LF AI & Data foundation 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 httpserver
import (
"context"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
"github.com/cockroachdb/errors"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
validator "github.com/go-playground/validator/v10"
"github.com/samber/lo"
"github.com/tidwall/gjson"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc"
"google.golang.org/protobuf/proto"
"github.com/milvus-io/milvus-proto/go-api/v3/commonpb"
"github.com/milvus-io/milvus-proto/go-api/v3/hook"
"github.com/milvus-io/milvus-proto/go-api/v3/milvuspb"
"github.com/milvus-io/milvus-proto/go-api/v3/schemapb"
"github.com/milvus-io/milvus/internal/distributed/streaming"
"github.com/milvus-io/milvus/internal/json"
"github.com/milvus-io/milvus/internal/proxy"
"github.com/milvus-io/milvus/internal/types"
"github.com/milvus-io/milvus/internal/util/hookutil"
"github.com/milvus-io/milvus/internal/util/indexparamcheck"
"github.com/milvus-io/milvus/pkg/v3/common"
"github.com/milvus-io/milvus/pkg/v3/metrics"
"github.com/milvus-io/milvus/pkg/v3/mlog"
"github.com/milvus-io/milvus/pkg/v3/proto/datapb"
"github.com/milvus-io/milvus/pkg/v3/proto/internalpb"
"github.com/milvus-io/milvus/pkg/v3/util/commonpbutil"
"github.com/milvus-io/milvus/pkg/v3/util/crypto"
"github.com/milvus-io/milvus/pkg/v3/util/externalspec"
"github.com/milvus-io/milvus/pkg/v3/util/funcutil"
"github.com/milvus-io/milvus/pkg/v3/util/merr"
"github.com/milvus-io/milvus/pkg/v3/util/metric"
"github.com/milvus-io/milvus/pkg/v3/util/paramtable"
"github.com/milvus-io/milvus/pkg/v3/util/requestutil"
"github.com/milvus-io/milvus/pkg/v3/util/typeutil"
)
type HandlersV2 struct {
proxy types.ProxyComponent
checkAuth bool
}
func NewHandlersV2(proxyClient types.ProxyComponent) *HandlersV2 {
return &HandlersV2{
proxy: proxyClient,
checkAuth: proxy.Params.CommonCfg.AuthorizationEnabled.GetAsBool(),
}
}
var routeToMethod = map[string]string{ //nolint:gosec // not credentials, just a route-to-method mapping
"/v2/vectordb/collections/list": "ShowCollections",
"/v2/vectordb/collections/has": "HasCollection",
"/v2/vectordb/collections/describe": "DescribeCollection",
"/v2/vectordb/collections/get_stats": "GetCollectionStatistics",
"/v2/vectordb/collections/get_load_state": "GetLoadState",
"/v2/vectordb/collections/create": "CreateCollection",
"/v2/vectordb/collections/drop": "DropCollection",
"/v2/vectordb/collections/truncate": "TruncateCollection",
"/v2/vectordb/collections/rename": "RenameCollection",
"/v2/vectordb/collections/load": "LoadCollection",
"/v2/vectordb/collections/refresh_load": "LoadCollection",
"/v2/vectordb/collections/release": "ReleaseCollection",
"/v2/vectordb/collections/alter_properties": "AlterCollection",
"/v2/vectordb/collections/add_function": "AddCollectionFunction",
"/v2/vectordb/collections/alter_function": "AlterCollectionFunction",
"/v2/vectordb/collections/drop_function": "DropCollectionFunction",
"/v2/vectordb/collections/drop_properties": "AlterCollection",
"/v2/vectordb/collections/compact": "ManualCompaction",
"/v2/vectordb/collections/get_compaction_state": "GetCompactionState",
"/v2/vectordb/collections/flush": "Flush",
"/v2/vectordb/collections/fields/alter_properties": "AlterCollectionField",
"/v2/vectordb/collections/fields/add": "AddCollectionField",
"/v2/vectordb/collections/struct_fields/add": "AddCollectionStructField",
"/v2/vectordb/databases/create": "CreateDatabase",
"/v2/vectordb/databases/drop": "DropDatabase",
"/v2/vectordb/databases/drop_properties": "AlterDatabase",
"/v2/vectordb/databases/list": "ListDatabases",
"/v2/vectordb/databases/describe": "DescribeDatabase",
"/v2/vectordb/databases/alter": "AlterDatabase",
"/v2/vectordb/databases/alter_properties": "AlterDatabase",
"/v2/vectordb/entities/query": "Query",
"/v2/vectordb/entities/get": "Query",
"/v2/vectordb/entities/delete": "Delete",
"/v2/vectordb/entities/insert": "Insert",
"/v2/vectordb/entities/upsert": "Upsert",
"/v2/vectordb/entities/search": "Search",
"/v2/vectordb/entities/advanced_search": "HybridSearch",
"/v2/vectordb/entities/hybrid_search": "HybridSearch",
"/v2/vectordb/partitions/list": "ShowPartitions",
"/v2/vectordb/partitions/has": "HasPartition",
"/v2/vectordb/partitions/get_stats": "GetPartitionStatistics",
"/v2/vectordb/partitions/create": "CreatePartition",
"/v2/vectordb/partitions/drop": "DropPartition",
"/v2/vectordb/partitions/load": "LoadPartitions",
"/v2/vectordb/partitions/release": "ReleasePartitions",
"/v2/vectordb/users/list": "ListCredUsers",
"/v2/vectordb/users/describe": "SelectUser",
"/v2/vectordb/users/create": "CreateCredential",
"/v2/vectordb/users/update_password": "UpdateCredential",
"/v2/vectordb/users/drop": "DeleteCredential",
"/v2/vectordb/users/grant_role": "OperateUserRole",
"/v2/vectordb/users/revoke_role": "OperateUserRole",
"/v2/vectordb/roles/list": "SelectRole",
"/v2/vectordb/roles/describe": "SelectGrant",
"/v2/vectordb/roles/create": "CreateRole",
"/v2/vectordb/roles/alter": "AlterRole",
"/v2/vectordb/roles/drop": "DropRole",
"/v2/vectordb/roles/grant_privilege": "OperatePrivilege",
"/v2/vectordb/roles/revoke_privilege": "OperatePrivilege",
"/v2/vectordb/roles/grant_privilege_v2": "OperatePrivilegeV2",
"/v2/vectordb/roles/revoke_privilege_v2": "OperatePrivilegeV2",
"/v2/vectordb/privilege_groups/create": "CreatePrivilegeGroup",
"/v2/vectordb/privilege_groups/drop": "DropPrivilegeGroup",
"/v2/vectordb/privilege_groups/list": "ListPrivilegeGroups",
"/v2/vectordb/privilege_groups/add_privileges_to_group": "OperatePrivilegeGroup",
"/v2/vectordb/privilege_groups/remove_privileges_from_group": "OperatePrivilegeGroup",
"/v2/vectordb/indexes/list": "DescribeIndex",
"/v2/vectordb/indexes/describe": "DescribeIndex",
"/v2/vectordb/indexes/create": "CreateIndex",
"/v2/vectordb/indexes/drop": "DropIndex",
"/v2/vectordb/indexes/alter_properties": "AlterIndex",
"/v2/vectordb/indexes/drop_properties": "AlterIndex",
"/v2/vectordb/aliases/list": "ListAliases",
"/v2/vectordb/aliases/describe": "DescribeAlias",
"/v2/vectordb/aliases/create": "CreateAlias",
"/v2/vectordb/aliases/drop": "DropAlias",
"/v2/vectordb/aliases/alter": "AlterAlias",
"/v2/vectordb/jobs/import/list": "ListImports",
"/v2/vectordb/jobs/import/create": "Import",
"/v2/vectordb/jobs/import/get_progress": "GetImportProgress",
"/v2/vectordb/jobs/import/describe": "GetImportProgress",
"/v2/vectordb/jobs/import/commit": "CommitImport",
"/v2/vectordb/jobs/import/abort": "AbortImport",
"/v2/vectordb/jobs/snapshot/restore_external": "RestoreExternalSnapshot",
"/v2/vectordb/jobs/snapshot/export": "ExportSnapshot",
"/v2/vectordb/jobs/snapshot/describe": "GetRestoreSnapshotState",
"/v2/vectordb/jobs/snapshot/list": "ListRestoreSnapshotJobs",
"/v2/vectordb/jobs/external_collection/refresh": "RefreshExternalCollection",
"/v2/vectordb/jobs/external_collection/describe": "GetRefreshExternalCollectionProgress",
"/v2/vectordb/jobs/external_collection/list": "ListRefreshExternalCollectionJobs",
"/v2/vectordb/resource_groups/create": "CreateResourceGroup",
"/v2/vectordb/resource_groups/drop": "DropResourceGroup",
"/v2/vectordb/resource_groups/alter": "UpdateResourceGroups",
"/v2/vectordb/resource_groups/describe": "DescribeResourceGroup",
"/v2/vectordb/resource_groups/list": "ListResourceGroups",
"/v2/vectordb/resource_groups/transfer_replica": "TransferMaster",
"/v2/vectordb/segments/describe": "GetSegmentsInfo",
"/v2/vectordb/quotacenter/describe": "GetQuotaMetrics",
"/v2/vectordb/common/run_analyzer": "RunAnalyzer",
}
func (h *HandlersV2) RegisterRoutesToV2(router gin.IRouter) {
router.POST(CollectionCategory+ListAction, timeoutMiddleware(wrapperPost(func() any { return &DatabaseReq{} }, wrapperTraceLog(h.listCollections))))
router.POST(CollectionCategory+HasAction, timeoutMiddleware(wrapperPost(func() any { return &CollectionNameReq{} }, wrapperTraceLog(h.hasCollection))))
// todo review the return data
router.POST(CollectionCategory+DescribeAction, timeoutMiddleware(wrapperPost(func() any { return &CollectionNameReq{} }, wrapperTraceLog(h.getCollectionDetails))))
router.POST(CollectionCategory+StatsAction, timeoutMiddleware(wrapperPost(func() any { return &CollectionNameReq{} }, wrapperTraceLog(h.getCollectionStats))))
router.POST(CollectionCategory+LoadStateAction, timeoutMiddleware(wrapperPost(func() any { return &CollectionNameReq{} }, wrapperTraceLog(h.getCollectionLoadState))))
router.POST(CollectionCategory+CreateAction, timeoutMiddleware(wrapperPost(func() any { return &CollectionReq{AutoID: DisableAutoID} }, wrapperTraceLog(h.createCollection))))
router.POST(CollectionCategory+DropAction, timeoutMiddleware(wrapperPost(func() any { return &CollectionNameReq{} }, wrapperTraceLog(h.dropCollection))))
router.POST(CollectionCategory+TruncateAction, timeoutMiddleware(wrapperPost(func() any { return &CollectionNameReq{} }, wrapperTraceLog(h.truncateCollection))))
router.POST(CollectionCategory+RenameAction, timeoutMiddleware(wrapperPost(func() any { return &RenameCollectionReq{} }, wrapperTraceLog(h.renameCollection))))
router.POST(CollectionCategory+LoadAction, timeoutMiddleware(wrapperPost(func() any { return &CollectionNameReq{} }, wrapperTraceLog(h.loadCollection))))
router.POST(CollectionCategory+RefreshLoadAction, timeoutMiddleware(wrapperPost(func() any { return &CollectionNameReq{} }, wrapperTraceLog(h.refreshLoadCollection))))
router.POST(CollectionCategory+ReleaseAction, timeoutMiddleware(wrapperPost(func() any { return &CollectionNameReq{} }, wrapperTraceLog(h.releaseCollection))))
router.POST(CollectionCategory+AlterPropertiesAction, timeoutMiddleware(wrapperPost(func() any { return &CollectionReqWithProperties{} }, wrapperTraceLog(h.alterCollectionProperties))))
router.POST(CollectionCategory+AddFunctionAction, timeoutMiddleware(wrapperPost(func() any { return &CollectionAddFunction{} }, wrapperTraceLog(h.addCollectionFunction))))
router.POST(CollectionCategory+AlterFunctionAction, timeoutMiddleware(wrapperPost(func() any { return &CollectionAlterFunction{} }, wrapperTraceLog(h.alterCollectionFunction))))
router.POST(CollectionCategory+DropFunctionAction, timeoutMiddleware(wrapperPost(func() any { return &CollectionDropFunction{} }, wrapperTraceLog(h.dropCollectionFunction))))
router.POST(CollectionCategory+DropPropertiesAction, timeoutMiddleware(wrapperPost(func() any { return &DropCollectionPropertiesReq{} }, wrapperTraceLog(h.dropCollectionProperties))))
router.POST(CollectionCategory+CompactAction, timeoutMiddleware(wrapperPost(func() any { return &CompactReq{} }, wrapperTraceLog(h.compact))))
router.POST(CollectionCategory+CompactionStateAction, timeoutMiddleware(wrapperPost(func() any { return &GetCompactionStateReq{} }, wrapperTraceLog(h.getcompactionState))))
router.POST(CollectionCategory+FlushAction, timeoutMiddleware(wrapperPost(func() any { return &FlushReq{} }, wrapperTraceLog(h.flush))))
router.POST(CollectionFieldCategory+AlterPropertiesAction, timeoutMiddleware(wrapperPost(func() any { return &CollectionFieldReqWithParams{} }, wrapperTraceLog(h.alterCollectionFieldProperties))))
// /collections/fields/add
router.POST(CollectionFieldCategory+AddAction, timeoutMiddleware(wrapperPost(func() any { return &CollectionFieldReqWithSchema{} }, wrapperTraceLog(h.addCollectionField))))
router.POST(CollectionStructFieldCategory+AddAction, timeoutMiddleware(wrapperPost(func() any { return &CollectionFieldReqWithSchema{} }, wrapperTraceLog(h.addCollectionStructField))))
router.POST(DataBaseCategory+CreateAction, timeoutMiddleware(wrapperPost(func() any { return &DatabaseReqWithProperties{} }, wrapperTraceLog(h.createDatabase))))
router.POST(DataBaseCategory+DropAction, timeoutMiddleware(wrapperPost(func() any { return &DatabaseReqRequiredName{} }, wrapperTraceLog(h.dropDatabase))))
router.POST(DataBaseCategory+DropPropertiesAction, timeoutMiddleware(wrapperPost(func() any { return &DropDatabasePropertiesReq{} }, wrapperTraceLog(h.dropDatabaseProperties))))
router.POST(DataBaseCategory+ListAction, timeoutMiddleware(wrapperPost(func() any { return &EmptyReq{} }, wrapperTraceLog(h.listDatabases))))
router.POST(DataBaseCategory+DescribeAction, timeoutMiddleware(wrapperPost(func() any { return &DatabaseReqRequiredName{} }, wrapperTraceLog(h.describeDatabase))))
router.POST(DataBaseCategory+AlterAction, timeoutMiddleware(wrapperPost(func() any { return &DatabaseReqWithProperties{} }, wrapperTraceLog(h.alterDatabase))))
router.POST(DataBaseCategory+AlterPropertiesAction, timeoutMiddleware(wrapperPost(func() any { return &DatabaseReqWithProperties{} }, wrapperTraceLog(h.alterDatabase))))
// Query
router.POST(EntityCategory+QueryAction, restfulSizeMiddleware(timeoutMiddleware(wrapperPost(func() any {
return &QueryReqV2{
Limit: 100,
OutputFields: []string{DefaultOutputFields},
}
}, wrapperTraceLog(h.query))), true))
// Get
router.POST(EntityCategory+GetAction, restfulSizeMiddleware(timeoutMiddleware(wrapperPost(func() any {
return &CollectionIDReq{
OutputFields: []string{DefaultOutputFields},
}
}, wrapperTraceLog(h.get))), true))
// Delete
router.POST(EntityCategory+DeleteAction, restfulSizeMiddleware(timeoutMiddleware(wrapperPost(func() any {
return &CollectionFilterReq{}
}, wrapperTraceLog(h.delete))), false))
// Insert
router.POST(EntityCategory+InsertAction, restfulSizeMiddleware(timeoutMiddleware(wrapperPost(func() any {
return &CollectionDataReq{}
}, wrapperTraceLog(h.insert))), false))
// Upsert
router.POST(EntityCategory+UpsertAction, restfulSizeMiddleware(timeoutMiddleware(wrapperPost(func() any {
return &CollectionDataReq{}
}, wrapperTraceLog(h.upsert))), false))
// Search
router.POST(EntityCategory+SearchAction, restfulSizeMiddleware(timeoutMiddleware(wrapperPost(func() any {
return &SearchReqV2{
Limit: 100,
}
}, wrapperTraceLog(h.search))), true))
// advanced_search, backward compatible uri
router.POST(EntityCategory+AdvancedSearchAction, restfulSizeMiddleware(timeoutMiddleware(wrapperPost(func() any {
return &HybridSearchReq{
Limit: 100,
}
}, wrapperTraceLog(h.advancedSearch))), true))
// HybridSearch
router.POST(EntityCategory+HybridSearchAction, restfulSizeMiddleware(timeoutMiddleware(wrapperPost(func() any {
return &HybridSearchReq{
Limit: 100,
}
}, wrapperTraceLog(h.advancedSearch))), true))
router.POST(PartitionCategory+ListAction, timeoutMiddleware(wrapperPost(func() any { return &CollectionNameReq{} }, wrapperTraceLog(h.listPartitions))))
router.POST(PartitionCategory+HasAction, timeoutMiddleware(wrapperPost(func() any { return &PartitionReq{} }, wrapperTraceLog(h.hasPartitions))))
router.POST(PartitionCategory+StatsAction, timeoutMiddleware(wrapperPost(func() any { return &PartitionReq{} }, wrapperTraceLog(h.statsPartition))))
router.POST(PartitionCategory+CreateAction, timeoutMiddleware(wrapperPost(func() any { return &PartitionReq{} }, wrapperTraceLog(h.createPartition))))
router.POST(PartitionCategory+DropAction, timeoutMiddleware(wrapperPost(func() any { return &PartitionReq{} }, wrapperTraceLog(h.dropPartition))))
router.POST(PartitionCategory+LoadAction, timeoutMiddleware(wrapperPost(func() any { return &PartitionsReq{} }, wrapperTraceLog(h.loadPartitions))))
router.POST(PartitionCategory+ReleaseAction, timeoutMiddleware(wrapperPost(func() any { return &PartitionsReq{} }, wrapperTraceLog(h.releasePartitions))))
router.POST(UserCategory+ListAction, timeoutMiddleware(wrapperPost(func() any { return &DatabaseReq{} }, wrapperTraceLog(h.listUsers))))
router.POST(UserCategory+DescribeAction, timeoutMiddleware(wrapperPost(func() any { return &UserReq{} }, wrapperTraceLog(h.describeUser))))
router.POST(UserCategory+CreateAction, timeoutMiddleware(wrapperPost(func() any { return &PasswordReq{} }, wrapperTraceLog(h.createUser))))
router.POST(UserCategory+UpdatePasswordAction, timeoutMiddleware(wrapperPost(func() any { return &NewPasswordReq{} }, wrapperTraceLog(h.updateUser))))
router.POST(UserCategory+DropAction, timeoutMiddleware(wrapperPost(func() any { return &UserReq{} }, wrapperTraceLog(h.dropUser))))
router.POST(UserCategory+GrantRoleAction, timeoutMiddleware(wrapperPost(func() any { return &UserRoleReq{} }, wrapperTraceLog(h.addRoleToUser))))
router.POST(UserCategory+RevokeRoleAction, timeoutMiddleware(wrapperPost(func() any { return &UserRoleReq{} }, wrapperTraceLog(h.removeRoleFromUser))))
router.POST(RoleCategory+ListAction, timeoutMiddleware(wrapperPost(func() any { return &DatabaseReq{} }, wrapperTraceLog(h.listRoles))))
router.POST(RoleCategory+DescribeAction, timeoutMiddleware(wrapperPost(func() any { return &RoleReq{} }, wrapperTraceLog(h.describeRole))))
router.POST(RoleCategory+CreateAction, timeoutMiddleware(wrapperPost(func() any { return &RoleReq{} }, wrapperTraceLog(h.createRole))))
router.POST(RoleCategory+AlterAction, timeoutMiddleware(wrapperPost(func() any { return &RoleReq{} }, wrapperTraceLog(h.alterRole))))
router.POST(RoleCategory+DropAction, timeoutMiddleware(wrapperPost(func() any { return &RoleReq{} }, wrapperTraceLog(h.dropRole))))
router.POST(RoleCategory+GrantPrivilegeAction, timeoutMiddleware(wrapperPost(func() any { return &GrantReq{} }, wrapperTraceLog(h.addPrivilegeToRole))))
router.POST(RoleCategory+RevokePrivilegeAction, timeoutMiddleware(wrapperPost(func() any { return &GrantReq{} }, wrapperTraceLog(h.removePrivilegeFromRole))))
router.POST(RoleCategory+GrantPrivilegeActionV2, timeoutMiddleware(wrapperPost(func() any { return &GrantV2Req{} }, wrapperTraceLog(h.grantV2))))
router.POST(RoleCategory+RevokePrivilegeActionV2, timeoutMiddleware(wrapperPost(func() any { return &GrantV2Req{} }, wrapperTraceLog(h.revokeV2))))
// privilege group
router.POST(PrivilegeGroupCategory+CreateAction, timeoutMiddleware(wrapperPost(func() any { return &PrivilegeGroupReq{} }, wrapperTraceLog(h.createPrivilegeGroup))))
router.POST(PrivilegeGroupCategory+DropAction, timeoutMiddleware(wrapperPost(func() any { return &PrivilegeGroupReq{} }, wrapperTraceLog(h.dropPrivilegeGroup))))
router.POST(PrivilegeGroupCategory+ListAction, timeoutMiddleware(wrapperPost(func() any { return &DatabaseReq{} }, wrapperTraceLog(h.listPrivilegeGroups))))
router.POST(PrivilegeGroupCategory+AddPrivilegesToGroupAction, timeoutMiddleware(wrapperPost(func() any { return &PrivilegeGroupReq{} }, wrapperTraceLog(h.addPrivilegesToGroup))))
router.POST(PrivilegeGroupCategory+RemovePrivilegesFromGroupAction, timeoutMiddleware(wrapperPost(func() any { return &PrivilegeGroupReq{} }, wrapperTraceLog(h.removePrivilegesFromGroup))))
router.POST(IndexCategory+ListAction, timeoutMiddleware(wrapperPost(func() any { return &CollectionNameReq{} }, wrapperTraceLog(h.listIndexes))))
router.POST(IndexCategory+DescribeAction, timeoutMiddleware(wrapperPost(func() any { return &IndexReq{} }, wrapperTraceLog(h.describeIndex))))
router.POST(IndexCategory+CreateAction, timeoutMiddleware(wrapperPost(func() any { return &IndexParamReq{} }, wrapperTraceLog(h.createIndex))))
// todo cannot drop index before release it ?
router.POST(IndexCategory+DropAction, timeoutMiddleware(wrapperPost(func() any { return &IndexReq{} }, wrapperTraceLog(h.dropIndex))))
router.POST(IndexCategory+AlterPropertiesAction, timeoutMiddleware(wrapperPost(func() any { return &IndexReqWithProperties{} }, wrapperTraceLog(h.alterIndexProperties))))
router.POST(IndexCategory+DropPropertiesAction, timeoutMiddleware(wrapperPost(func() any { return &DropIndexPropertiesReq{} }, wrapperTraceLog(h.dropIndexProperties))))
router.POST(AliasCategory+ListAction, timeoutMiddleware(wrapperPost(func() any { return &OptionalCollectionNameReq{} }, wrapperTraceLog(h.listAlias))))
router.POST(AliasCategory+DescribeAction, timeoutMiddleware(wrapperPost(func() any { return &AliasReq{} }, wrapperTraceLog(h.describeAlias))))
router.POST(AliasCategory+CreateAction, timeoutMiddleware(wrapperPost(func() any { return &AliasCollectionReq{} }, wrapperTraceLog(h.createAlias))))
router.POST(AliasCategory+DropAction, timeoutMiddleware(wrapperPost(func() any { return &AliasReq{} }, wrapperTraceLog(h.dropAlias))))
router.POST(AliasCategory+AlterAction, timeoutMiddleware(wrapperPost(func() any { return &AliasCollectionReq{} }, wrapperTraceLog(h.alterAlias))))
router.POST(ImportJobCategory+ListAction, timeoutMiddleware(wrapperPost(func() any { return &OptionalCollectionNameReq{} }, wrapperTraceLog(h.listImportJob))))
router.POST(ImportJobCategory+CreateAction, timeoutMiddleware(wrapperPost(func() any { return &ImportReq{} }, wrapperTraceLog(h.createImportJob))))
router.POST(ImportJobCategory+GetProgressAction, timeoutMiddleware(wrapperPost(func() any { return &JobIDReq{} }, wrapperTraceLog(h.getImportJobProcess))))
router.POST(ImportJobCategory+DescribeAction, timeoutMiddleware(wrapperPost(func() any { return &JobIDReq{} }, wrapperTraceLog(h.getImportJobProcess))))
router.POST(ImportJobCategory+CommitAction, timeoutMiddleware(wrapperPost(func() any { return &JobIDReq{} }, wrapperTraceLog(h.commitImportJob))))
router.POST(ImportJobCategory+AbortAction, timeoutMiddleware(wrapperPost(func() any { return &JobIDReq{} }, wrapperTraceLog(h.abortImportJob))))
router.POST(SnapshotJobCategory+RestoreExternalAction, timeoutMiddleware(wrapperPost(func() any { return &RestoreExternalSnapshotReq{} }, wrapperTraceLog(h.restoreExternalSnapshot))))
router.POST(SnapshotJobCategory+ExportAction, timeoutMiddleware(wrapperPost(func() any { return &ExportSnapshotReq{} }, wrapperTraceLog(h.exportSnapshot))))
router.POST(SnapshotJobCategory+DescribeAction, timeoutMiddleware(wrapperPost(func() any { return &JobIDReq{} }, wrapperTraceLog(h.getRestoreSnapshotState))))
router.POST(SnapshotJobCategory+ListAction, timeoutMiddleware(wrapperPost(func() any { return &OptionalCollectionNameReq{} }, wrapperTraceLog(h.listRestoreSnapshotJobs))))
router.POST(ExternalCollectionJobCategory+RefreshAction, timeoutMiddleware(wrapperPost(func() any { return &RefreshExternalCollectionReq{} }, wrapperTraceLog(h.refreshExternalCollection))))
router.POST(ExternalCollectionJobCategory+DescribeAction, timeoutMiddleware(wrapperPost(func() any { return &RefreshExternalCollectionProgressReq{} }, wrapperTraceLog(h.getRefreshExternalCollectionProgress))))
router.POST(ExternalCollectionJobCategory+ListAction, timeoutMiddleware(wrapperPost(func() any { return &OptionalCollectionNameReq{} }, wrapperTraceLog(h.listRefreshExternalCollectionJobs))))
// resource group
router.POST(ResourceGroupCategory+CreateAction, timeoutMiddleware(wrapperPost(func() any { return &ResourceGroupReq{} }, wrapperTraceLog(h.createResourceGroup))))
router.POST(ResourceGroupCategory+DropAction, timeoutMiddleware(wrapperPost(func() any { return &ResourceGroupReq{} }, wrapperTraceLog(h.dropResourceGroup))))
router.POST(ResourceGroupCategory+AlterAction, timeoutMiddleware(wrapperPost(func() any { return &UpdateResourceGroupReq{} }, wrapperTraceLog(h.updateResourceGroup))))
router.POST(ResourceGroupCategory+DescribeAction, timeoutMiddleware(wrapperPost(func() any { return &ResourceGroupReq{} }, wrapperTraceLog(h.describeResourceGroup))))
router.POST(ResourceGroupCategory+ListAction, timeoutMiddleware(wrapperPost(func() any { return &EmptyReq{} }, wrapperTraceLog(h.listResourceGroups))))
router.POST(ResourceGroupCategory+TransferReplicaAction, timeoutMiddleware(wrapperPost(func() any { return &TransferReplicaReq{} }, wrapperTraceLog(h.transferReplica))))
// segment group
router.POST(SegmentCategory+DescribeAction, timeoutMiddleware(wrapperPost(func() any { return &GetSegmentsInfoReq{} }, wrapperTraceLog(h.getSegmentsInfo))))
router.POST(QuotaCenterCategory+DescribeAction, timeoutMiddleware(wrapperPost(func() any { return &GetQuotaMetricsReq{} }, wrapperTraceLog(h.getQuotaMetrics))))
// common
router.POST(CommonCategory+RunAnalyzerAction, timeoutMiddleware(wrapperPost(func() any { return &RunAnalyzerReq{} }, wrapperTraceLog(h.runAnalyzer))))
}
type (
newReqFunc func() any
handlerFuncV2 func(ctx context.Context, c *gin.Context, req any, dbName string) (interface{}, error)
)
func wrapperPost(newReq newReqFunc, v2 handlerFuncV2) gin.HandlerFunc {
return func(gCtx *gin.Context) {
req := newReq()
if err := gCtx.ShouldBindBodyWith(req, binding.JSON); err != nil {
mlog.Warn(context.TODO(), "high level restful api, read parameters from request body fail", mlog.Err(err),
mlog.Any("url", gCtx.Request.URL.Path))
if _, ok := err.(validator.ValidationErrors); ok {
HTTPAbortReturn(gCtx, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrMissingRequiredParameters),
HTTPReturnMessage: merr.ErrMissingRequiredParameters.Error() + ", error: " + err.Error(),
})
} else if err == io.EOF {
HTTPAbortReturn(gCtx, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrIncorrectParameterFormat),
HTTPReturnMessage: merr.ErrIncorrectParameterFormat.Error() + ", the request body should be nil, however {} is valid",
})
} else {
HTTPAbortReturn(gCtx, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrIncorrectParameterFormat),
HTTPReturnMessage: merr.ErrIncorrectParameterFormat.Error() + ", error: " + err.Error(),
})
}
return
}
dbName := ""
collectionName := ""
if req != nil {
if getter, ok := req.(requestutil.DBNameGetter); ok {
dbName = getter.GetDbName()
}
if dbName == "" {
dbName = gCtx.Request.Header.Get(HTTPHeaderDBName)
if dbName == "" {
dbName = DefaultDbName
}
}
if getter, ok := req.(requestutil.CollectionNameGetter); ok {
collectionName = getter.GetCollectionName()
}
}
ctx := gCtx.Request.Context()
username, _ := gCtx.Get(ContextUsername)
ctx = proxy.NewContextWithMetadata(ctx, username.(string), dbName)
mlog.Debug(ctx, "high level restful api, read parameters from request body, then start to handle.",
mlog.Any("url", gCtx.Request.URL.Path))
resp, err := v2(ctx, gCtx, req, dbName)
methodTag, ok := routeToMethod[gCtx.FullPath()]
if !ok {
return
}
metrics.ProxyFunctionCall.WithLabelValues(
strconv.FormatInt(paramtable.GetNodeID(), 10),
methodTag,
metrics.TotalLabel,
dbName,
collectionName,
).Inc()
label := requestutil.ParseMetricLabel(resp, err)
// set metrics for state code
metrics.ProxyFunctionCall.WithLabelValues(
strconv.FormatInt(paramtable.GetNodeID(), 10),
methodTag,
label,
dbName,
collectionName,
).Inc()
// Mirror the fail_input/fail_system metric split into the logs so a
// failed REST request can be filtered by error_type. System failures are
// logged at Warn (actionable); input failures at Info (expected user
// mistakes — keeping them at Warn would spam the logs).
if label == metrics.FailSystemLabel || label == metrics.FailInputLabel {
var status *commonpb.Status
switch r := resp.(type) {
case interface{ GetStatus() *commonpb.Status }:
status = r.GetStatus()
case *commonpb.Status:
status = r
}
errType := merr.SystemError
if label == metrics.FailInputLabel {
errType = merr.InputError
}
logger := mlog.With(
mlog.String("method", methodTag),
mlog.String("error_type", errType.String()),
mlog.Int32("code", status.GetCode()),
mlog.String("reason", status.GetReason()),
)
if errType == merr.InputError {
logger.Info(ctx, "restful request returned an input error")
} else {
logger.Warn(ctx, "restful request returned a system error")
}
}
}
}
// restfulSizeMiddleware is the middleware fetchs metrics stats from gin struct.
func restfulSizeMiddleware(handler gin.HandlerFunc, observeOutbound bool) gin.HandlerFunc {
return func(ctx *gin.Context) {
h := metrics.WrapRestfulContext(ctx.Request.Context(), ctx.Request.ContentLength)
ctx.Request = ctx.Request.WithContext(h)
handler(ctx)
metrics.RecordRestfulMetrics(h, int64(ctx.Writer.Size()), observeOutbound)
}
}
func getTraceLogRequestFieldWithoutSensitiveInfo(req any) mlog.Field {
switch request := req.(type) {
case *RestoreExternalSnapshotReq:
if request == nil {
return proxy.GetRequestFieldWithoutSensitiveInfo(req)
}
redactedReq := *request
redactedReq.ExternalSpec = externalspec.RedactExternalSpec(request.ExternalSpec)
return mlog.Any("request", &redactedReq)
case *ExportSnapshotReq:
if request == nil {
return proxy.GetRequestFieldWithoutSensitiveInfo(req)
}
redactedReq := *request
redactedReq.ExternalSpec = externalspec.RedactExternalSpec(request.ExternalSpec)
return mlog.Any("request", &redactedReq)
default:
return proxy.GetRequestFieldWithoutSensitiveInfo(req)
}
}
func wrapperTraceLog(v2 handlerFuncV2) handlerFuncV2 {
return func(ctx context.Context, c *gin.Context, req any, dbName string) (interface{}, error) {
switch proxy.Params.CommonCfg.TraceLogMode.GetAsInt() {
case 1: // simple info
fields := proxy.GetRequestBaseInfo(ctx, req, &grpc.UnaryServerInfo{
FullMethod: c.Request.URL.Path,
}, false)
mlog.Info(ctx, "trace info: simple", fields...)
case 2: // detail info
fields := proxy.GetRequestBaseInfo(ctx, req, &grpc.UnaryServerInfo{
FullMethod: c.Request.URL.Path,
}, true)
fields = append(fields, getTraceLogRequestFieldWithoutSensitiveInfo(req))
mlog.Info(ctx, "trace info: detail", fields...)
case 3: // detail info with request and response
fields := proxy.GetRequestBaseInfo(ctx, req, &grpc.UnaryServerInfo{
FullMethod: c.Request.URL.Path,
}, true)
fields = append(fields, getTraceLogRequestFieldWithoutSensitiveInfo(req))
mlog.Info(ctx, "trace info: all request", fields...)
}
resp, err := v2(ctx, c, req, dbName)
if proxy.Params.CommonCfg.TraceLogMode.GetAsInt() > 2 {
if err != nil {
mlog.Info(ctx, "trace info: all, error", mlog.Err(err))
} else {
mlog.Info(ctx, "trace info: all, unknown")
}
}
return resp, err
}
}
func checkAuthorizationV2(ctx context.Context, c *gin.Context, ignoreErr bool, req interface{}) error {
username, ok := c.Get(ContextUsername)
if !ok || username.(string) == "" {
if !ignoreErr {
HTTPReturn(c, http.StatusUnauthorized, gin.H{HTTPReturnCode: merr.Code(merr.ErrNeedAuthenticate), HTTPReturnMessage: merr.ErrNeedAuthenticate.Error()})
}
hookutil.GetExtension().ReportAction(ctx, req, WrapErrorToResponse(merr.ErrNeedAuthenticate), nil, c.FullPath(), hookutil.ActionAuthorize)
return merr.ErrNeedAuthenticate
}
ctx, authErr := proxy.PrivilegeInterceptor(ctx, req)
if authErr != nil {
if !ignoreErr {
HTTPReturn(c, http.StatusForbidden, gin.H{HTTPReturnCode: merr.Code(authErr), HTTPReturnMessage: authErr.Error()})
}
hookutil.GetExtension().ReportAction(ctx, req, WrapErrorToResponse(authErr), nil, c.FullPath(), hookutil.ActionAuthorize)
return authErr
}
c.Request = c.Request.WithContext(ctx)
return nil
}
func checkAuthorizationHelper(ctx context.Context, c *gin.Context, req interface{}) error {
username, ok := c.Get(ContextUsername)
if !ok || username.(string) == "" {
return merr.ErrNeedAuthenticate
}
ctx, authErr := proxy.PrivilegeInterceptor(ctx, req)
if authErr != nil {
return authErr
}
c.Request = c.Request.WithContext(ctx)
return nil
}
func wrapperProxy(ctx context.Context, c *gin.Context, req any, checkAuth bool, ignoreErr bool, fullMethod string, handler func(reqCtx context.Context, req any) (any, error)) (interface{}, error) {
return wrapperProxyWithLimit(ctx, c, req, checkAuth, ignoreErr, fullMethod, false, nil, handler)
}
func wrapperProxyWithLimit(ctx context.Context, ginCtx *gin.Context, req any, checkAuth bool, ignoreErr bool, fullMethod string, checkLimit bool, pxy types.ProxyComponent, handler func(reqCtx context.Context, req any) (any, error)) (interface{}, error) {
if baseGetter, ok := req.(BaseGetter); ok {
span := trace.SpanFromContext(ctx)
span.AddEvent(baseGetter.GetBase().GetMsgType().String())
}
if checkAuth {
err := checkAuthorizationV2(ctx, ginCtx, ignoreErr, req)
if err != nil {
return nil, err
}
ctx = ginCtx.Request.Context()
}
if checkLimit {
_, err := CheckLimiter(ctx, req, pxy)
if err != nil {
mlog.Warn(context.TODO(), "high level restful api, fail to check limiter", mlog.Err(err), mlog.String("method", fullMethod))
hookutil.GetExtension().ReportAction(ctx, req, WrapErrorToResponse(merr.ErrHTTPRateLimit), nil, ginCtx.FullPath(), hookutil.ActionAuthorize)
HTTPAbortReturn(ginCtx, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrHTTPRateLimit),
HTTPReturnMessage: merr.ErrHTTPRateLimit.Error() + ", error: " + err.Error(),
})
return nil, RestRequestInterceptorErr
}
}
mlog.Debug(ctx, "high level restful api, try to do a grpc call")
username, ok := ginCtx.Get(ContextUsername)
if !ok {
username = ""
}
forwardHandler := func(reqCtx context.Context, req any) (any, error) {
interceptor := streaming.ForwardLegacyProxyUnaryServerInterceptor()
if token, ok := ginCtx.Get(ContextToken); ok {
interceptor = streaming.ForwardLegacyProxyUnaryServerInterceptor(streaming.OptForwardAuth(token.(string)))
}
return interceptor(reqCtx, req, &grpc.UnaryServerInfo{FullMethod: fullMethod}, func(ctx context.Context, req any) (interface{}, error) {
return handler(ctx, req)
})
}
response, err := proxy.HookInterceptor(context.WithValue(ctx, hook.GinParamsKey, ginCtx), req, username.(string), fullMethod, forwardHandler)
if response != nil {
ginCtx.Set(ContextResponse, response)
}
if err == nil {
status, ok := requestutil.GetStatusFromResponse(response)
if ok {
err = merr.Error(status)
}
}
if err != nil {
// Expose the exact classification (incl. boundary InputError marks) to
// the REST access log; key must match accesslog/info.ContextErrorType.
ginCtx.Set("error_type", merr.GetErrorType(err).String())
mlog.Warn(ctx, "high level restful api, grpc call failed", mlog.Err(err))
if !ignoreErr {
HTTPAbortReturn(ginCtx, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(err), HTTPReturnMessage: err.Error()})
}
}
return response, err
}
func (h *HandlersV2) hasCollection(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
getter, _ := anyReq.(requestutil.CollectionNameGetter)
collectionName := getter.GetCollectionName()
_, err := proxy.GetCachedCollectionSchema(ctx, dbName, collectionName)
has := true
if err != nil {
req := &milvuspb.HasCollectionRequest{
DbName: dbName,
CollectionName: collectionName,
}
// handle at rootcoord side
resp, err := wrapperProxy(ctx, c, req, false, false, "/milvus.proto.milvus.MilvusService/HasCollection", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.HasCollection(reqCtx, req.(*milvuspb.HasCollectionRequest))
})
if err != nil {
return nil, err
}
has = resp.(*milvuspb.BoolResponse).Value
}
HTTPReturn(c, http.StatusOK, wrapperReturnHas(has))
return has, nil
}
func (h *HandlersV2) listCollections(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
req := &milvuspb.ShowCollectionsRequest{
DbName: dbName,
}
c.Set(ContextRequest, req)
// handle at rootcoord side
resp, err := wrapperProxy(ctx, c, req, false, false, "/milvus.proto.milvus.MilvusService/ShowCollections", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.ShowCollections(reqCtx, req.(*milvuspb.ShowCollectionsRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnList(resp.(*milvuspb.ShowCollectionsResponse).CollectionNames))
}
return resp, err
}
func (h *HandlersV2) getCollectionDetails(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
collectionGetter, _ := anyReq.(requestutil.CollectionNameGetter)
collectionName := collectionGetter.GetCollectionName()
req := &milvuspb.DescribeCollectionRequest{
DbName: dbName,
CollectionName: collectionName,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/DescribeCollection", func(reqCtx context.Context, req any) (any, error) {
return h.proxy.DescribeCollection(reqCtx, req.(*milvuspb.DescribeCollectionRequest))
})
if err != nil {
return resp, err
}
coll := resp.(*milvuspb.DescribeCollectionResponse)
primaryField, ok := getPrimaryField(coll.Schema)
autoID := false
if !ok {
mlog.Warn(ctx, "high level restful api, get primary field from collection schema fail", mlog.Any("collection schema", coll.Schema), mlog.Any("request", anyReq))
} else {
autoID = primaryField.AutoID
}
errMessage := ""
loadStateReq := &milvuspb.GetLoadStateRequest{
DbName: dbName,
CollectionName: collectionName,
}
stateResp, err := wrapperProxy(ctx, c, loadStateReq, h.checkAuth, true, "/milvus.proto.milvus.MilvusService/GetLoadState", func(reqCtx context.Context, req any) (any, error) {
return h.proxy.GetLoadState(reqCtx, req.(*milvuspb.GetLoadStateRequest))
})
collLoadState := ""
if err == nil {
collLoadState = stateResp.(*milvuspb.GetLoadStateResponse).State.String()
} else {
errMessage += err.Error() + ";"
}
vectorField := ""
for _, field := range coll.Schema.Fields {
if typeutil.IsVectorType(field.DataType) {
vectorField = field.Name
break
}
}
indexDesc := []gin.H{}
descIndexReq := &milvuspb.DescribeIndexRequest{
DbName: dbName,
CollectionName: collectionName,
FieldName: vectorField,
}
indexResp, err := wrapperProxy(ctx, c, descIndexReq, h.checkAuth, true, "/milvus.proto.milvus.MilvusService/DescribeIndex", func(reqCtx context.Context, req any) (any, error) {
return h.proxy.DescribeIndex(reqCtx, req.(*milvuspb.DescribeIndexRequest))
})
if err == nil {
indexDesc = printIndexes(indexResp.(*milvuspb.DescribeIndexResponse).IndexDescriptions)
} else {
errMessage += err.Error() + ";"
}
var aliases []string
aliasReq := &milvuspb.ListAliasesRequest{
DbName: dbName,
CollectionName: collectionName,
}
aliasResp, err := wrapperProxy(ctx, c, aliasReq, h.checkAuth, true, "/milvus.proto.milvus.MilvusService/ListAliases", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.ListAliases(reqCtx, req.(*milvuspb.ListAliasesRequest))
})
if err == nil {
aliases = aliasResp.(*milvuspb.ListAliasesResponse).GetAliases()
} else {
errMessage += err.Error() + "."
}
if aliases == nil {
aliases = []string{}
}
if coll.Properties == nil {
coll.Properties = []*commonpb.KeyValuePair{}
}
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: gin.H{
HTTPCollectionName: coll.CollectionName,
HTTPCollectionID: coll.CollectionID,
HTTPReturnDescription: coll.Schema.Description,
HTTPReturnFieldAutoID: autoID,
"fields": printFieldsV2(coll.Schema.Fields),
"structFields": printStructArrayFieldsV2(coll.Schema.StructArrayFields),
"functions": printFunctionDetails(coll.Schema.Functions),
"aliases": aliases,
"indexes": indexDesc,
"load": collLoadState,
"shardsNum": coll.ShardsNum,
"partitionsNum": coll.NumPartitions,
"consistencyLevel": commonpb.ConsistencyLevel_name[int32(coll.ConsistencyLevel)],
"enableDynamicField": coll.Schema.EnableDynamicField,
"externalSource": coll.Schema.GetExternalSource(),
"externalSpec": coll.Schema.GetExternalSpec(),
"properties": coll.Properties,
}, HTTPReturnMessage: errMessage})
return resp, nil
}
func (h *HandlersV2) getCollectionStats(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
collectionGetter, _ := anyReq.(requestutil.CollectionNameGetter)
req := &milvuspb.GetCollectionStatisticsRequest{
DbName: dbName,
CollectionName: collectionGetter.GetCollectionName(),
}
c.Set(ContextRequest, req)
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/GetCollectionStatistics", func(reqCtx context.Context, req any) (any, error) {
return h.proxy.GetCollectionStatistics(reqCtx, req.(*milvuspb.GetCollectionStatisticsRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnRowCount(resp.(*milvuspb.GetCollectionStatisticsResponse).Stats))
}
return resp, err
}
func (h *HandlersV2) getCollectionLoadState(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
collectionGetter, _ := anyReq.(requestutil.CollectionNameGetter)
req := &milvuspb.GetLoadStateRequest{
DbName: dbName,
CollectionName: collectionGetter.GetCollectionName(),
}
c.Set(ContextRequest, req)
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/GetLoadState", func(reqCtx context.Context, req any) (any, error) {
return h.proxy.GetLoadState(reqCtx, req.(*milvuspb.GetLoadStateRequest))
})
if err != nil {
return resp, err
}
switch resp.(*milvuspb.GetLoadStateResponse).State {
case commonpb.LoadState_LoadStateNotExist:
err = merr.WrapErrCollectionNotFound(req.CollectionName)
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(err), HTTPReturnMessage: err.Error()})
return resp, err
case commonpb.LoadState_LoadStateNotLoad:
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: gin.H{
HTTPReturnLoadState: resp.(*milvuspb.GetLoadStateResponse).State.String(),
}})
return resp, err
}
partitionsGetter, _ := anyReq.(requestutil.PartitionNamesGetter)
progressReq := &milvuspb.GetLoadingProgressRequest{
CollectionName: collectionGetter.GetCollectionName(),
PartitionNames: partitionsGetter.GetPartitionNames(),
DbName: dbName,
}
progressResp, err := wrapperProxy(ctx, c, progressReq, h.checkAuth, true, "/milvus.proto.milvus.MilvusService/GetLoadingProgress", func(reqCtx context.Context, req any) (any, error) {
return h.proxy.GetLoadingProgress(reqCtx, req.(*milvuspb.GetLoadingProgressRequest))
})
progress := int64(-1)
errMessage := ""
if err == nil {
progress = progressResp.(*milvuspb.GetLoadingProgressResponse).Progress
} else {
errMessage += err.Error() + "."
}
state := commonpb.LoadState_LoadStateLoading.String()
if progress >= 100 {
state = commonpb.LoadState_LoadStateLoaded.String()
}
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: gin.H{
HTTPReturnLoadState: state,
HTTPReturnLoadProgress: progress,
}, HTTPReturnMessage: errMessage})
return resp, err
}
func (h *HandlersV2) dropCollection(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
getter, _ := anyReq.(requestutil.CollectionNameGetter)
req := &milvuspb.DropCollectionRequest{
DbName: dbName,
CollectionName: getter.GetCollectionName(),
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/DropCollection", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.DropCollection(reqCtx, req.(*milvuspb.DropCollectionRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) truncateCollection(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
getter, _ := anyReq.(requestutil.CollectionNameGetter)
req := &milvuspb.TruncateCollectionRequest{
DbName: dbName,
CollectionName: getter.GetCollectionName(),
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/TruncateCollection", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.TruncateCollection(reqCtx, req.(*milvuspb.TruncateCollectionRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) renameCollection(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*RenameCollectionReq)
req := &milvuspb.RenameCollectionRequest{
DbName: dbName,
OldName: httpReq.CollectionName,
NewName: httpReq.NewCollectionName,
NewDBName: httpReq.NewDbName,
}
c.Set(ContextRequest, req)
if req.NewDBName == "" {
req.NewDBName = dbName
}
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/RenameCollection", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.RenameCollection(reqCtx, req.(*milvuspb.RenameCollectionRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) refreshLoadCollection(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
getter, _ := anyReq.(requestutil.CollectionNameGetter)
req := &milvuspb.LoadCollectionRequest{
DbName: dbName,
CollectionName: getter.GetCollectionName(),
Refresh: true,
}
return h.loadCollectionInternal(ctx, c, req, dbName)
}
func (h *HandlersV2) loadCollection(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
getter, _ := anyReq.(requestutil.CollectionNameGetter)
req := &milvuspb.LoadCollectionRequest{
DbName: dbName,
CollectionName: getter.GetCollectionName(),
}
return h.loadCollectionInternal(ctx, c, req, dbName)
}
func (h *HandlersV2) loadCollectionInternal(ctx context.Context, c *gin.Context, req *milvuspb.LoadCollectionRequest, dbName string) (interface{}, error) {
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/LoadCollection", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.LoadCollection(reqCtx, req.(*milvuspb.LoadCollectionRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) releaseCollection(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
getter, _ := anyReq.(requestutil.CollectionNameGetter)
req := &milvuspb.ReleaseCollectionRequest{
DbName: dbName,
CollectionName: getter.GetCollectionName(),
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/ReleaseCollection", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.ReleaseCollection(reqCtx, req.(*milvuspb.ReleaseCollectionRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) alterCollectionProperties(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*CollectionReqWithProperties)
req := &milvuspb.AlterCollectionRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
}
properties := make([]*commonpb.KeyValuePair, 0, len(httpReq.Properties))
for key, value := range httpReq.Properties {
properties = append(properties, &commonpb.KeyValuePair{Key: key, Value: fmt.Sprintf("%v", value)})
}
req.Properties = properties
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/AlterCollection", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.AlterCollection(reqCtx, req.(*milvuspb.AlterCollectionRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) addCollectionFunction(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*CollectionAddFunction)
req := &milvuspb.AddCollectionFunctionRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
}
fSchema, err := genFunctionSchema(ctx, &httpReq.Function)
if err != nil {
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrParameterInvalid),
HTTPReturnMessage: err.Error(),
})
}
req.FunctionSchema = fSchema
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/AddCollectionFunction", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.AddCollectionFunction(reqCtx, req.(*milvuspb.AddCollectionFunctionRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) alterCollectionFunction(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*CollectionAlterFunction)
req := &milvuspb.AlterCollectionFunctionRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
FunctionName: httpReq.FunctionName,
}
fSchema, err := genFunctionSchema(ctx, &httpReq.Function)
if err != nil {
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrParameterInvalid),
HTTPReturnMessage: err.Error(),
})
}
req.FunctionSchema = fSchema
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/AlterCollectionFunction", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.AlterCollectionFunction(reqCtx, req.(*milvuspb.AlterCollectionFunctionRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) dropCollectionFunction(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*CollectionDropFunction)
req := &milvuspb.DropCollectionFunctionRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
FunctionName: httpReq.FunctionName,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/DropCollectionFunction", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.DropCollectionFunction(reqCtx, req.(*milvuspb.DropCollectionFunctionRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) dropCollectionProperties(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*DropCollectionPropertiesReq)
req := &milvuspb.AlterCollectionRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
DeleteKeys: httpReq.PropertyKeys,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/AlterCollection", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.AlterCollection(reqCtx, req.(*milvuspb.AlterCollectionRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) compact(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*CompactReq)
req := &milvuspb.ManualCompactionRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
MajorCompaction: httpReq.IsClustering,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/ManualCompaction", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.ManualCompaction(reqCtx, req.(*milvuspb.ManualCompactionRequest))
})
if err == nil {
resp := resp.(*milvuspb.ManualCompactionResponse)
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: gin.H{"compactionID": resp.CompactionID},
})
}
return resp, err
}
func (h *HandlersV2) getcompactionState(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*GetCompactionStateReq)
req := &milvuspb.GetCompactionStateRequest{
CompactionID: httpReq.JobID,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/GetCompactionState", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.GetCompactionState(reqCtx, req.(*milvuspb.GetCompactionStateRequest))
})
if err == nil {
resp := resp.(*milvuspb.GetCompactionStateResponse)
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: gin.H{"compactionID": httpReq.JobID, "state": resp.State.String(), "executingPlanNumber": resp.ExecutingPlanNo, "timeoutPlanNumber": resp.TimeoutPlanNo, "completedPlanNumber": resp.CompletedPlanNo},
})
}
return resp, err
}
func (h *HandlersV2) flush(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*FlushReq)
req := &milvuspb.FlushRequest{
DbName: dbName,
CollectionNames: []string{httpReq.CollectionName},
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/Flush", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.Flush(reqCtx, req.(*milvuspb.FlushRequest))
})
if err == nil {
err = h.waitForFlush(ctx, dbName, httpReq.CollectionName, resp.(*milvuspb.FlushResponse))
if err != nil {
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(err), HTTPReturnMessage: err.Error()})
return resp, err
}
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) waitForFlush(ctx context.Context, dbName string, collectionName string, flushResp *milvuspb.FlushResponse) error {
segmentIDs := flushResp.GetCollSegIDs()[collectionName].GetData()
flushTs, ok := flushResp.GetCollFlushTs()[collectionName]
if !ok {
return merr.WrapErrServiceInternalMsg("failed to get flush timestamp for collection %s", collectionName)
}
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
for {
stateResp, err := h.proxy.GetFlushState(ctx, &milvuspb.GetFlushStateRequest{
DbName: dbName,
CollectionName: collectionName,
SegmentIDs: segmentIDs,
FlushTs: flushTs,
})
if err := merr.CheckRPCCall(stateResp, err); err != nil {
return err
}
if stateResp.GetFlushed() {
return nil
}
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
}
}
}
func (h *HandlersV2) alterCollectionFieldProperties(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*CollectionFieldReqWithParams)
req := &milvuspb.AlterCollectionFieldRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
FieldName: httpReq.FieldName,
}
properties := make([]*commonpb.KeyValuePair, 0, len(httpReq.FieldParams))
for key, value := range httpReq.FieldParams {
properties = append(properties, &commonpb.KeyValuePair{Key: key, Value: fmt.Sprintf("%v", value)})
}
req.Properties = properties
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/AlterCollectionField", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.AlterCollectionField(reqCtx, req.(*milvuspb.AlterCollectionFieldRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) addCollectionField(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*CollectionFieldReqWithSchema)
if httpReq.Schema.IsStructArrayField() {
err := merr.WrapErrParameterInvalidMsg("StructArray field must be added through /v2/vectordb/collections/struct_fields/add")
HTTPAbortReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(err), HTTPReturnMessage: err.Error()})
return nil, err
}
schemaProto, err := httpReq.Schema.GetProto(ctx)
if err != nil {
HTTPAbortReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(err), HTTPReturnMessage: err.Error()})
return nil, err
}
bs, err := proto.Marshal(schemaProto)
if err != nil {
return nil, err
}
req := &milvuspb.AddCollectionFieldRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
Schema: bs,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/AddCollectionField", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.AddCollectionField(reqCtx, req.(*milvuspb.AddCollectionFieldRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) addCollectionStructField(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*CollectionFieldReqWithSchema)
schemaProto, err := httpReq.Schema.GetStructArrayProto(ctx)
if err != nil {
HTTPAbortReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(err), HTTPReturnMessage: err.Error()})
return nil, err
}
req := &milvuspb.AddCollectionStructFieldRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
StructArrayFieldSchema: schemaProto,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/AddCollectionStructField", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.AddCollectionStructField(reqCtx, req.(*milvuspb.AddCollectionStructFieldRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
// copy from internal/proxy/task_query.go
func matchCountRule(outputs []string) bool {
return len(outputs) == 1 && strings.ToLower(strings.TrimSpace(outputs[0])) == "count(*)"
}
func (h *HandlersV2) query(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*QueryReqV2)
req := &milvuspb.QueryRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
Expr: httpReq.Filter,
OutputFields: httpReq.OutputFields,
PartitionNames: httpReq.PartitionNames,
QueryParams: []*commonpb.KeyValuePair{},
}
var err error
collSchema, err := h.GetCollectionSchema(ctx, c, dbName, httpReq.CollectionName)
if err != nil {
return nil, err
}
req.ConsistencyLevel, req.UseDefaultConsistency, err = convertConsistencyLevel(httpReq.ConsistencyLevel)
if err != nil {
mlog.Warn(ctx, "high level restful api, query with consistency_level invalid", mlog.Err(err))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: "consistencyLevel can only be [Strong, Session, Bounded, Eventually, Customized], default: Bounded, err:" + err.Error(),
})
return nil, err
}
req.ExprTemplateValues = generateExpressionTemplate(httpReq.ExprParams)
c.Set(ContextRequest, req)
if httpReq.Offset > 0 {
req.QueryParams = append(req.QueryParams, &commonpb.KeyValuePair{Key: proxy.OffsetKey, Value: strconv.FormatInt(int64(httpReq.Offset), 10)})
}
if httpReq.Limit > 0 && !matchCountRule(httpReq.OutputFields) {
req.QueryParams = append(req.QueryParams, &commonpb.KeyValuePair{Key: proxy.LimitKey, Value: strconv.FormatInt(int64(httpReq.Limit), 10)})
}
if len(httpReq.OrderByFields) > 0 {
req.QueryParams = append(req.QueryParams, &commonpb.KeyValuePair{Key: proxy.OrderByFieldsKey, Value: strings.Join(httpReq.OrderByFields, ",")})
}
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/Query", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.Query(reqCtx, req.(*milvuspb.QueryRequest))
})
if err == nil {
queryResp := resp.(*milvuspb.QueryResults)
allowJS, _ := strconv.ParseBool(c.Request.Header.Get(HTTPHeaderAllowInt64))
outputData, err := buildQueryResp(int64(0), queryResp.OutputFields, queryResp.FieldsData, nil, nil, allowJS, collSchema)
if err != nil {
mlog.Warn(ctx, "high level restful api, fail to deal with query result", mlog.Any("response", resp), mlog.Err(err))
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrInvalidSearchResult),
HTTPReturnMessage: merr.ErrInvalidSearchResult.Error() + ", error: " + err.Error(),
})
} else {
scannedRemoteBytes, scannedTotalBytes, cacheHitRatio, isValid := proxy.GetStorageCost(queryResp.GetStatus())
if proxy.Params.QueryNodeCfg.StorageUsageTrackingEnabled.GetAsBool() && isValid {
HTTPReturnStream(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: outputData,
HTTPReturnCost: proxy.GetCostValue(queryResp.GetStatus()),
HTTPReturnScannedRemoteBytes: scannedRemoteBytes,
HTTPReturnScannedTotalBytes: scannedTotalBytes,
HTTPReturnCacheHitRatio: cacheHitRatio,
})
} else {
HTTPReturnStream(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: outputData,
HTTPReturnCost: proxy.GetCostValue(queryResp.GetStatus()),
})
}
}
}
return resp, err
}
func (h *HandlersV2) get(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*CollectionIDReq)
collSchema, err := h.GetCollectionSchema(ctx, c, dbName, httpReq.CollectionName)
if err != nil {
return nil, err
}
body, _ := c.Get(gin.BodyBytesKey)
filter, err := checkGetPrimaryKey(collSchema, gjson.Get(string(body.([]byte)), DefaultPrimaryFieldName))
if err != nil {
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrCheckPrimaryKey),
HTTPReturnMessage: merr.ErrCheckPrimaryKey.Error() + ", error: " + err.Error(),
})
return nil, err
}
req := &milvuspb.QueryRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
OutputFields: httpReq.OutputFields,
PartitionNames: httpReq.PartitionNames,
Expr: filter,
}
req.ConsistencyLevel, req.UseDefaultConsistency, err = convertConsistencyLevel(httpReq.ConsistencyLevel)
if err != nil {
mlog.Warn(ctx, "high level restful api, query with consistency_level invalid", mlog.Err(err))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: "consistencyLevel can only be [Strong, Session, Bounded, Eventually, Customized], default: Bounded, err:" + err.Error(),
})
return nil, err
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/Query", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.Query(reqCtx, req.(*milvuspb.QueryRequest))
})
if err == nil {
queryResp := resp.(*milvuspb.QueryResults)
allowJS, _ := strconv.ParseBool(c.Request.Header.Get(HTTPHeaderAllowInt64))
outputData, err := buildQueryResp(int64(0), queryResp.OutputFields, queryResp.FieldsData, nil, nil, allowJS, collSchema)
if err != nil {
mlog.Warn(ctx, "high level restful api, fail to deal with get result", mlog.Any("response", resp), mlog.Err(err))
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrInvalidSearchResult),
HTTPReturnMessage: merr.ErrInvalidSearchResult.Error() + ", error: " + err.Error(),
})
} else {
scannedRemoteBytes, scannedTotalBytes, cacheHitRatio, isValid := proxy.GetStorageCost(queryResp.GetStatus())
if proxy.Params.QueryNodeCfg.StorageUsageTrackingEnabled.GetAsBool() && isValid {
HTTPReturnStream(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: outputData,
HTTPReturnCost: proxy.GetCostValue(queryResp.GetStatus()),
HTTPReturnScannedRemoteBytes: scannedRemoteBytes,
HTTPReturnScannedTotalBytes: scannedTotalBytes,
HTTPReturnCacheHitRatio: cacheHitRatio,
})
} else {
HTTPReturnStream(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: outputData,
HTTPReturnCost: proxy.GetCostValue(queryResp.GetStatus()),
})
}
}
}
return resp, err
}
func (h *HandlersV2) delete(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*CollectionFilterReq)
collSchema, err := h.GetCollectionSchema(ctx, c, dbName, httpReq.CollectionName)
if err != nil {
return nil, err
}
req := &milvuspb.DeleteRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
PartitionName: httpReq.PartitionName,
Expr: httpReq.Filter,
}
req.ExprTemplateValues = generateExpressionTemplate(httpReq.ExprParams)
c.Set(ContextRequest, req)
if req.Expr == "" {
body, _ := c.Get(gin.BodyBytesKey)
filter, err := checkGetPrimaryKey(collSchema, gjson.Get(string(body.([]byte)), DefaultPrimaryFieldName))
if err != nil {
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrCheckPrimaryKey),
HTTPReturnMessage: merr.ErrCheckPrimaryKey.Error() + ", error: " + err.Error(),
})
return nil, err
}
req.Expr = filter
}
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/Delete", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.Delete(reqCtx, req.(*milvuspb.DeleteRequest))
})
if err == nil {
deleteResp := resp.(*milvuspb.MutationResult)
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: gin.H{"deleteCount": deleteResp.DeleteCnt},
})
}
return resp, err
}
func (h *HandlersV2) insert(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*CollectionDataReq)
req := &milvuspb.InsertRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
PartitionName: httpReq.PartitionName,
// PartitionName: "_default",
}
c.Set(ContextRequest, req)
collSchema, err := h.GetCollectionSchema(ctx, c, dbName, httpReq.CollectionName)
if err != nil {
return nil, err
}
body, _ := c.Get(gin.BodyBytesKey)
var validDataMap map[string][]bool
httpReq.Data, validDataMap, err = checkAndSetData(body.([]byte), collSchema, false)
if err != nil {
mlog.Warn(ctx, "high level restful api, fail to deal with insert data", mlog.Err(err), mlog.String("body", string(body.([]byte))))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrInvalidInsertData),
HTTPReturnMessage: merr.ErrInvalidInsertData.Error() + ", error: " + err.Error(),
})
return nil, err
}
req.NumRows = uint32(len(httpReq.Data))
req.FieldsData, err = anyToColumns(httpReq.Data, validDataMap, collSchema, true, false)
if err != nil {
mlog.Warn(ctx, "high level restful api, fail to deal with insert data", mlog.Any("data", httpReq.Data), mlog.Err(err))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrInvalidInsertData),
HTTPReturnMessage: merr.ErrInvalidInsertData.Error() + ", error: " + err.Error(),
})
return nil, err
}
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/Insert", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.Insert(reqCtx, req.(*milvuspb.InsertRequest))
})
if err == nil {
insertResp := resp.(*milvuspb.MutationResult)
cost := proxy.GetCostValue(insertResp.GetStatus())
switch insertResp.IDs.GetIdField().(type) {
case *schemapb.IDs_IntId:
allowJS, _ := strconv.ParseBool(c.Request.Header.Get(HTTPHeaderAllowInt64))
if allowJS {
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: gin.H{"insertCount": insertResp.InsertCnt, "insertIds": insertResp.IDs.IdField.(*schemapb.IDs_IntId).IntId.Data},
HTTPReturnCost: cost,
})
} else {
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: gin.H{"insertCount": insertResp.InsertCnt, "insertIds": formatInt64(insertResp.IDs.IdField.(*schemapb.IDs_IntId).IntId.Data)},
HTTPReturnCost: cost,
})
}
case *schemapb.IDs_StrId:
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: gin.H{"insertCount": insertResp.InsertCnt, "insertIds": insertResp.IDs.IdField.(*schemapb.IDs_StrId).StrId.Data},
HTTPReturnCost: cost,
})
default:
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrCheckPrimaryKey),
HTTPReturnMessage: merr.ErrCheckPrimaryKey.Error() + ", error: unsupported primary key data type",
})
}
}
return resp, err
}
func (h *HandlersV2) upsert(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*CollectionDataReq)
req := &milvuspb.UpsertRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
PartitionName: httpReq.PartitionName,
PartialUpdate: httpReq.PartialUpdate,
// PartitionName: "_default",
}
fieldOps, err := buildFieldPartialUpdateOps(httpReq.FieldOps)
if err != nil {
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: err.Error(),
})
return nil, err
}
req.FieldOps = fieldOps
c.Set(ContextRequest, req)
collSchema, err := h.GetCollectionSchema(ctx, c, dbName, httpReq.CollectionName)
if err != nil {
return nil, err
}
body, _ := c.Get(gin.BodyBytesKey)
var validDataMap map[string][]bool
httpReq.Data, validDataMap, err = checkAndSetData(body.([]byte), collSchema, httpReq.PartialUpdate)
if err != nil {
mlog.Warn(ctx, "high level restful api, fail to deal with upsert data", mlog.Any("body", body), mlog.Err(err))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrInvalidInsertData),
HTTPReturnMessage: merr.ErrInvalidInsertData.Error() + ", error: " + err.Error(),
})
return nil, err
}
req.NumRows = uint32(len(httpReq.Data))
req.FieldsData, err = anyToColumns(httpReq.Data, validDataMap, collSchema, false, httpReq.PartialUpdate)
if err != nil {
mlog.Warn(ctx, "high level restful api, fail to deal with upsert data", mlog.Any("data", httpReq.Data), mlog.Err(err))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrInvalidInsertData),
HTTPReturnMessage: merr.ErrInvalidInsertData.Error() + ", error: " + err.Error(),
})
return nil, err
}
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/Upsert", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.Upsert(reqCtx, req.(*milvuspb.UpsertRequest))
})
if err == nil {
upsertResp := resp.(*milvuspb.MutationResult)
cost := proxy.GetCostValue(upsertResp.GetStatus())
scannedRemoteBytes, scannedTotalBytes, cacheHitRatio, isValid := proxy.GetStorageCost(upsertResp.GetStatus())
switch upsertResp.IDs.GetIdField().(type) {
case *schemapb.IDs_IntId:
allowJS, _ := strconv.ParseBool(c.Request.Header.Get(HTTPHeaderAllowInt64))
if allowJS {
if proxy.Params.QueryNodeCfg.StorageUsageTrackingEnabled.GetAsBool() && isValid {
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: gin.H{"upsertCount": upsertResp.UpsertCnt, "upsertIds": upsertResp.IDs.IdField.(*schemapb.IDs_IntId).IntId.Data},
HTTPReturnCost: cost,
HTTPReturnScannedRemoteBytes: scannedRemoteBytes,
HTTPReturnScannedTotalBytes: scannedTotalBytes,
HTTPReturnCacheHitRatio: cacheHitRatio,
})
} else {
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: gin.H{"upsertCount": upsertResp.UpsertCnt, "upsertIds": upsertResp.IDs.IdField.(*schemapb.IDs_IntId).IntId.Data},
HTTPReturnCost: cost,
})
}
} else {
if proxy.Params.QueryNodeCfg.StorageUsageTrackingEnabled.GetAsBool() && isValid {
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: gin.H{"upsertCount": upsertResp.UpsertCnt, "upsertIds": formatInt64(upsertResp.IDs.IdField.(*schemapb.IDs_IntId).IntId.Data)},
HTTPReturnCost: cost,
HTTPReturnScannedRemoteBytes: scannedRemoteBytes,
HTTPReturnScannedTotalBytes: scannedTotalBytes,
HTTPReturnCacheHitRatio: cacheHitRatio,
})
} else {
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: gin.H{"upsertCount": upsertResp.UpsertCnt, "upsertIds": formatInt64(upsertResp.IDs.IdField.(*schemapb.IDs_IntId).IntId.Data)},
HTTPReturnCost: cost,
})
}
}
case *schemapb.IDs_StrId:
if proxy.Params.QueryNodeCfg.StorageUsageTrackingEnabled.GetAsBool() && isValid {
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: gin.H{"upsertCount": upsertResp.UpsertCnt, "upsertIds": upsertResp.IDs.IdField.(*schemapb.IDs_StrId).StrId.Data},
HTTPReturnCost: cost,
HTTPReturnScannedRemoteBytes: scannedRemoteBytes,
HTTPReturnScannedTotalBytes: scannedTotalBytes,
HTTPReturnCacheHitRatio: cacheHitRatio,
})
} else {
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: gin.H{"upsertCount": upsertResp.UpsertCnt, "upsertIds": upsertResp.IDs.IdField.(*schemapb.IDs_StrId).StrId.Data},
HTTPReturnCost: cost,
})
}
default:
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrCheckPrimaryKey),
HTTPReturnMessage: merr.ErrCheckPrimaryKey.Error() + ", error: unsupported primary key data type",
})
}
}
return resp, err
}
func generatePlaceholderGroup(ctx context.Context, body string, collSchema *schemapb.CollectionSchema, fieldName string) ([]byte, error) {
var err error
var vectorField *schemapb.FieldSchema
if len(fieldName) == 0 {
for _, field := range collSchema.Fields {
if typeutil.IsVectorType(field.DataType) {
if len(fieldName) == 0 {
fieldName = field.Name
vectorField = field
} else {
return nil, merr.WrapErrParameterInvalidMsg("search without annsField, but already found multiple vector fields: [%s, %s,,,]", fieldName, field.Name)
}
}
}
} else {
for _, field := range collSchema.Fields {
if field.Name == fieldName && typeutil.IsVectorType(field.DataType) {
vectorField = field
break
}
}
if vectorField == nil {
for _, sf := range collSchema.GetStructArrayFields() {
for _, sub := range sf.GetFields() {
if sub.GetName() == fieldName && typeutil.IsVectorType(sub.GetDataType()) {
vectorField = sub
break
}
}
if vectorField != nil {
break
}
}
}
}
if vectorField == nil {
return nil, merr.WrapErrFieldNotFound(fieldName, "cannot find a vector field")
}
dim := int64(0)
if !typeutil.IsSparseFloatVectorType(vectorField.DataType) {
dim, _ = getDim(vectorField)
}
dataType := vectorField.DataType
if vectorField.GetIsFunctionOutput() {
for _, function := range collSchema.Functions {
if function.Type == schemapb.FunctionType_BM25 || function.Type == schemapb.FunctionType_TextEmbedding || function.Type == schemapb.FunctionType_MinHash {
// TODO: currently only BM25, text & MinHash embedding function is supported, thus guarantees one input field to one output field
if function.OutputFieldNames[0] == vectorField.Name {
dataType = schemapb.DataType_VarChar
}
}
}
}
var phv *commonpb.PlaceholderValue
if vectorField.GetDataType() == schemapb.DataType_ArrayOfVector {
if isEmbeddingListData(body) {
phv, err = convertEmbListQueries2Placeholder(body, vectorField.GetElementType(), dim)
} else {
phv, err = convertQueries2Placeholder(body, vectorField.GetElementType(), dim)
}
} else {
phv, err = convertQueries2Placeholder(body, dataType, dim)
}
if err != nil {
return nil, err
}
return proto.Marshal(&commonpb.PlaceholderGroup{
Placeholders: []*commonpb.PlaceholderValue{
phv,
},
})
}
func (h *HandlersV2) search(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*SearchReqV2)
req := &milvuspb.SearchRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
Dsl: httpReq.Filter,
DslType: commonpb.DslType_BoolExprV1,
OutputFields: httpReq.OutputFields,
PartitionNames: httpReq.PartitionNames,
}
var err error
req.ConsistencyLevel, req.UseDefaultConsistency, err = convertConsistencyLevel(httpReq.ConsistencyLevel)
if err != nil {
mlog.Warn(ctx, "high level restful api, search with consistency_level invalid", mlog.Err(err))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: "consistencyLevel can only be [Strong, Session, Bounded, Eventually, Customized], default: Bounded, err:" + err.Error(),
})
return nil, err
}
c.Set(ContextRequest, req)
collSchema, err := h.GetCollectionSchema(ctx, c, dbName, httpReq.CollectionName)
if err != nil {
// has already throw http in GetCollectionSchema if fails to get schema
return nil, err
}
// Check if search by primary keys or by vectors
hasIDs := len(httpReq.Ids) > 0
hasData := len(httpReq.Data) > 0
// Primary keys and query vectors are mutually exclusive
if hasIDs && hasData {
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrParameterInvalid),
HTTPReturnMessage: "primary keys (ids) and query vectors (data) are mutually exclusive. Please provide either 'ids' or 'data', not both",
})
return nil, merr.ErrParameterInvalid
}
// At least one of ids or data must be provided
if !hasIDs && !hasData {
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrMissingRequiredParameters),
HTTPReturnMessage: "either 'ids' (for primary key search) or 'data' (for vector search) must be provided",
})
return nil, merr.ErrMissingRequiredParameters
}
if httpReq.SearchAggregation != nil {
if hasIDs {
err := merr.WrapErrParameterInvalidMsg("ids and searchAggregation cannot be used simultaneously")
HTTPAbortReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(err), HTTPReturnMessage: err.Error()})
return nil, err
}
if httpReq.Offset != 0 {
err := merr.WrapErrParameterInvalidMsg("offset is not supported with searchAggregation")
HTTPAbortReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(err), HTTPReturnMessage: err.Error()})
return nil, err
}
if searchParamsContainAny(httpReq.SearchParams, proxy.OffsetKey) {
err := merr.WrapErrParameterInvalidMsg("searchParams.offset is not supported with searchAggregation")
HTTPAbortReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(err), HTTPReturnMessage: err.Error()})
return nil, err
}
if httpReq.GroupByField != "" || httpReq.GroupSize != 0 || httpReq.StrictGroupSize {
err := merr.WrapErrParameterInvalidMsg("groupingField/groupSize/strictGroupSize and searchAggregation cannot be used simultaneously")
HTTPAbortReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(err), HTTPReturnMessage: err.Error()})
return nil, err
}
if searchParamsContainAny(httpReq.SearchParams, proxy.GroupByFieldKey, proxy.GroupByFieldsKey) {
err := merr.WrapErrParameterInvalidMsg("searchParams.group_by_field(s) and searchAggregation cannot be used simultaneously")
HTTPAbortReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(err), HTTPReturnMessage: err.Error()})
return nil, err
}
req.SearchAggregation, err = convertSearchAggregationReq(httpReq.SearchAggregation)
if err != nil {
mlog.Warn(ctx, "high level restful api, convert SearchAggregation failed", mlog.Err(err))
HTTPAbortReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(err), HTTPReturnMessage: err.Error()})
return nil, err
}
}
searchParams, err := generateSearchParams(httpReq.SearchParams)
if err != nil {
mlog.Warn(ctx, "high level restful api, generate SearchParams failed", mlog.Err(err))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: err.Error(),
})
return nil, err
}
searchParams = append(searchParams, &commonpb.KeyValuePair{Key: common.TopKKey, Value: strconv.FormatInt(int64(httpReq.Limit), 10)})
searchParams = append(searchParams, &commonpb.KeyValuePair{Key: proxy.OffsetKey, Value: strconv.FormatInt(int64(httpReq.Offset), 10)})
if httpReq.GroupByField != "" {
searchParams = append(searchParams, &commonpb.KeyValuePair{Key: ParamGroupByField, Value: httpReq.GroupByField})
}
if httpReq.GroupByField != "" && httpReq.GroupSize > 0 {
searchParams = append(searchParams, &commonpb.KeyValuePair{Key: ParamGroupSize, Value: strconv.FormatInt(int64(httpReq.GroupSize), 10)})
searchParams = append(searchParams, &commonpb.KeyValuePair{Key: ParamStrictGroupSize, Value: strconv.FormatBool(httpReq.StrictGroupSize)})
}
if len(httpReq.FunctionScore.Functions) != 0 {
if req.FunctionScore, err = genFunctionScore(ctx, &httpReq.FunctionScore); err != nil {
HTTPAbortReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(merr.ErrParameterInvalid), HTTPReturnMessage: err.Error()})
return nil, err
}
}
if len(httpReq.FunctionChains) != 0 {
if req.FunctionChains, err = genFunctionChains(httpReq.FunctionChains); err != nil {
HTTPAbortReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(merr.ErrParameterInvalid), HTTPReturnMessage: err.Error()})
return nil, err
}
}
if hasIDs {
// Search by primary keys
primaryField, ok := getPrimaryField(collSchema)
if !ok {
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrParameterInvalid),
HTTPReturnMessage: "collection has no primary key field",
})
return nil, merr.ErrParameterInvalid
}
// Convert ids to schemapb.IDs
ids, err := convertIDsToSchemapbIDs(httpReq.Ids, primaryField)
if err != nil {
mlog.Warn(ctx, "high level restful api, convert ids to schemapb.IDs failed", mlog.Err(err))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrParameterInvalid),
HTTPReturnMessage: merr.ErrParameterInvalid.Error() + ", error: " + err.Error(),
})
return nil, err
}
// Set Ids field using the oneof SearchInput field
req.SearchInput = &milvuspb.SearchRequest_Ids{
Ids: ids,
}
// Set anns_field in search params if provided
if httpReq.AnnsField != "" {
searchParams = append(searchParams, &commonpb.KeyValuePair{Key: proxy.AnnsFieldKey, Value: httpReq.AnnsField})
}
} else {
// Search by vectors (existing logic)
searchParams = append(searchParams, &commonpb.KeyValuePair{Key: proxy.AnnsFieldKey, Value: httpReq.AnnsField})
body, _ := c.Get(gin.BodyBytesKey)
placeholderGroup, err := generatePlaceholderGroup(ctx, string(body.([]byte)), collSchema, httpReq.AnnsField)
if err != nil {
mlog.Warn(ctx, "high level restful api, search with vector invalid", mlog.Err(err))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrIncorrectParameterFormat),
HTTPReturnMessage: merr.ErrIncorrectParameterFormat.Error() + ", error: " + err.Error(),
})
return nil, err
}
req.SearchInput = &milvuspb.SearchRequest_PlaceholderGroup{
PlaceholderGroup: placeholderGroup,
}
}
req.SearchParams = searchParams
req.ExprTemplateValues = generateExpressionTemplate(httpReq.ExprParams)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/Search", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.Search(reqCtx, req.(*milvuspb.SearchRequest))
})
if err == nil {
searchResp := resp.(*milvuspb.SearchResults)
cost := proxy.GetCostValue(searchResp.GetStatus())
scannedRemoteBytes, scannedTotalBytes, cacheHitRatio, isValid := proxy.GetStorageCost(searchResp.GetStatus())
if hasSearchAggregationResult(searchResp.Results) {
allowJS, _ := strconv.ParseBool(c.Request.Header.Get(HTTPHeaderAllowInt64))
outputData, err := buildSearchAggregationResp(searchResp.Results, allowJS, collSchema)
if err != nil {
mlog.Warn(ctx, "high level restful api, fail to deal with search aggregation result", mlog.Any("result", searchResp.Results), mlog.Err(err))
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrInvalidSearchResult),
HTTPReturnMessage: merr.ErrInvalidSearchResult.Error() + ", error: " + err.Error(),
})
} else {
respBody := gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: outputData,
HTTPReturnCost: cost,
HTTPReturnAggTopks: searchResp.Results.GetAggTopks(),
}
if len(searchResp.Results.Recalls) > 0 {
respBody[HTTPReturnRecalls] = searchResp.Results.Recalls
}
if proxy.Params.QueryNodeCfg.StorageUsageTrackingEnabled.GetAsBool() && isValid {
respBody[HTTPReturnScannedRemoteBytes] = scannedRemoteBytes
respBody[HTTPReturnScannedTotalBytes] = scannedTotalBytes
respBody[HTTPReturnCacheHitRatio] = cacheHitRatio
}
HTTPReturnStream(c, http.StatusOK, respBody)
}
} else if searchResp.Results.TopK == int64(0) {
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: []interface{}{}, HTTPReturnCost: cost})
} else {
allowJS, _ := strconv.ParseBool(c.Request.Header.Get(HTTPHeaderAllowInt64))
outputData, err := buildQueryResp(0, searchResp.Results.OutputFields, searchResp.Results.FieldsData, searchResp.Results.Ids, searchResp.Results.Scores, allowJS, collSchema)
if err != nil {
mlog.Warn(ctx, "high level restful api, fail to deal with search result", mlog.Any("result", searchResp.Results), mlog.Err(err))
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrInvalidSearchResult),
HTTPReturnMessage: merr.ErrInvalidSearchResult.Error() + ", error: " + err.Error(),
})
} else {
if len(searchResp.Results.Recalls) > 0 {
if proxy.Params.QueryNodeCfg.StorageUsageTrackingEnabled.GetAsBool() && isValid {
HTTPReturnStream(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: outputData,
HTTPReturnCost: cost,
HTTPReturnRecalls: searchResp.Results.Recalls,
HTTPReturnTopks: searchResp.Results.Topks,
HTTPReturnScannedRemoteBytes: scannedRemoteBytes,
HTTPReturnScannedTotalBytes: scannedTotalBytes,
HTTPReturnCacheHitRatio: cacheHitRatio,
})
} else {
HTTPReturnStream(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: outputData,
HTTPReturnCost: cost,
HTTPReturnRecalls: searchResp.Results.Recalls,
HTTPReturnTopks: searchResp.Results.Topks,
})
}
} else {
if proxy.Params.QueryNodeCfg.StorageUsageTrackingEnabled.GetAsBool() && isValid {
HTTPReturnStream(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: outputData,
HTTPReturnCost: cost,
HTTPReturnTopks: searchResp.Results.Topks,
HTTPReturnScannedRemoteBytes: scannedRemoteBytes,
HTTPReturnScannedTotalBytes: scannedTotalBytes,
HTTPReturnCacheHitRatio: cacheHitRatio,
})
} else {
HTTPReturnStream(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: outputData,
HTTPReturnCost: cost,
HTTPReturnTopks: searchResp.Results.Topks,
})
}
}
}
}
}
return resp, err
}
func (h *HandlersV2) advancedSearch(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*HybridSearchReq)
req := &milvuspb.HybridSearchRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
PartitionNames: httpReq.PartitionNames,
Requests: []*milvuspb.SearchRequest{},
OutputFields: httpReq.OutputFields,
}
var err error
req.ConsistencyLevel, req.UseDefaultConsistency, err = convertConsistencyLevel(httpReq.ConsistencyLevel)
if err != nil {
mlog.Warn(ctx, "high level restful api, search with consistency_level invalid", mlog.Err(err))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: "consistencyLevel can only be [Strong, Session, Bounded, Eventually, Customized], default: Bounded, err:" + err.Error(),
})
return nil, err
}
c.Set(ContextRequest, req)
if httpReq.SearchAggregation != nil {
err := merr.WrapErrParameterInvalidMsg("searchAggregation is not supported for hybrid search")
HTTPAbortReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(err), HTTPReturnMessage: err.Error()})
return nil, err
}
if len(httpReq.FunctionChains) != 0 {
err := merr.WrapErrParameterInvalidMsg("functionChains is not supported for hybrid search yet")
HTTPAbortReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(err), HTTPReturnMessage: err.Error()})
return nil, err
}
for _, subReq := range httpReq.Search {
if subReq.SearchAggregation != nil {
err := merr.WrapErrParameterInvalidMsg("searchAggregation is not supported for hybrid search")
HTTPAbortReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(err), HTTPReturnMessage: err.Error()})
return nil, err
}
}
collSchema, err := h.GetCollectionSchema(ctx, c, dbName, httpReq.CollectionName)
if err != nil {
// has already throw http in GetCollectionSchema if fails to get schema
return nil, err
}
body, _ := c.Get(gin.BodyBytesKey)
searchArray := gjson.Get(string(body.([]byte)), "search").Array()
for i, subReq := range httpReq.Search {
searchParams, err := generateSearchParams(subReq.SearchParams)
if err != nil {
mlog.Warn(ctx, "high level restful api, generate SearchParams failed", mlog.Err(err))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: err.Error(),
})
return nil, err
}
searchParams = append(searchParams, &commonpb.KeyValuePair{Key: common.TopKKey, Value: strconv.FormatInt(int64(subReq.Limit), 10)})
searchParams = append(searchParams, &commonpb.KeyValuePair{Key: proxy.OffsetKey, Value: strconv.FormatInt(int64(subReq.Offset), 10)})
searchParams = append(searchParams, &commonpb.KeyValuePair{Key: proxy.AnnsFieldKey, Value: subReq.AnnsField})
placeholderGroup, err := generatePlaceholderGroup(ctx, searchArray[i].Raw, collSchema, subReq.AnnsField)
if err != nil {
mlog.Warn(ctx, "high level restful api, search with vector invalid", mlog.Err(err))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrIncorrectParameterFormat),
HTTPReturnMessage: merr.ErrIncorrectParameterFormat.Error() + ", error: " + err.Error(),
})
return nil, err
}
searchReq := &milvuspb.SearchRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
Dsl: subReq.Filter,
SearchInput: &milvuspb.SearchRequest_PlaceholderGroup{
PlaceholderGroup: placeholderGroup,
},
DslType: commonpb.DslType_BoolExprV1,
OutputFields: httpReq.OutputFields,
PartitionNames: httpReq.PartitionNames,
SearchParams: searchParams,
}
searchReq.ExprTemplateValues = generateExpressionTemplate(subReq.ExprParams)
req.Requests = append(req.Requests, searchReq)
}
bs, _ := json.Marshal(httpReq.Rerank.Params)
// leave the rerank check to proxy side
req.RankParams = []*commonpb.KeyValuePair{
{Key: proxy.RankTypeKey, Value: httpReq.Rerank.Strategy},
{Key: proxy.ParamsKey, Value: string(bs)},
{Key: proxy.LimitKey, Value: strconv.FormatInt(int64(httpReq.Limit), 10)},
{Key: proxy.OffsetKey, Value: strconv.FormatInt(int64(httpReq.Offset), 10)},
{Key: ParamRoundDecimal, Value: "-1"},
}
if httpReq.GroupByField != "" {
req.RankParams = append(req.RankParams, &commonpb.KeyValuePair{Key: ParamGroupByField, Value: httpReq.GroupByField})
}
if httpReq.GroupByField != "" && httpReq.GroupSize > 0 {
req.RankParams = append(req.RankParams, &commonpb.KeyValuePair{Key: ParamGroupSize, Value: strconv.FormatInt(int64(httpReq.GroupSize), 10)})
req.RankParams = append(req.RankParams, &commonpb.KeyValuePair{Key: ParamStrictGroupSize, Value: strconv.FormatBool(httpReq.StrictGroupSize)})
}
if len(httpReq.FunctionScore.Functions) != 0 {
if req.FunctionScore, err = genFunctionScore(ctx, &httpReq.FunctionScore); err != nil {
HTTPAbortReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(merr.ErrParameterInvalid), HTTPReturnMessage: err.Error()})
return nil, err
}
}
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/HybridSearch", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.HybridSearch(reqCtx, req.(*milvuspb.HybridSearchRequest))
})
if err == nil {
searchResp := resp.(*milvuspb.SearchResults)
cost := proxy.GetCostValue(searchResp.GetStatus())
scannedRemoteBytes, scannedTotalBytes, cacheHitRatio, isValid := proxy.GetStorageCost(searchResp.GetStatus())
if searchResp.Results.TopK == int64(0) {
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: []interface{}{}, HTTPReturnCost: cost})
} else {
allowJS, _ := strconv.ParseBool(c.Request.Header.Get(HTTPHeaderAllowInt64))
outputData, err := buildQueryResp(0, searchResp.Results.OutputFields, searchResp.Results.FieldsData, searchResp.Results.Ids, searchResp.Results.Scores, allowJS, collSchema)
if err != nil {
mlog.Warn(ctx, "high level restful api, fail to deal with search result", mlog.Any("result", searchResp.Results), mlog.Err(err))
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrInvalidSearchResult),
HTTPReturnMessage: merr.ErrInvalidSearchResult.Error() + ", error: " + err.Error(),
})
} else {
if proxy.Params.QueryNodeCfg.StorageUsageTrackingEnabled.GetAsBool() && isValid {
HTTPReturnStream(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: outputData, HTTPReturnCost: cost, HTTPReturnTopks: searchResp.Results.Topks, HTTPReturnScannedRemoteBytes: scannedRemoteBytes, HTTPReturnScannedTotalBytes: scannedTotalBytes, HTTPReturnCacheHitRatio: cacheHitRatio})
} else {
HTTPReturnStream(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: outputData, HTTPReturnCost: cost, HTTPReturnTopks: searchResp.Results.Topks})
}
}
}
}
return resp, err
}
func defaultMetricTypeForQuickCreate(dataType schemapb.DataType) string {
switch dataType {
case schemapb.DataType_BinaryVector:
return paramtable.BinaryVectorDefaultMetricType
case schemapb.DataType_SparseFloatVector:
return paramtable.SparseFloatVectorDefaultMetricType
default:
return DefaultMetricType
}
}
func binaryMetricTypesForQuickCreate() []string {
binaryIndexType := paramtable.Get().AutoIndexConfig.BinaryIndexParams.GetAsJSONMap()[common.IndexTypeKey]
switch binaryIndexType {
case "BIN_FLAT":
return indexparamcheck.BinIDMapMetrics
case "BIN_IVF_FLAT", "":
return indexparamcheck.BinIvfMetrics
default:
return indexparamcheck.BinIvfMetrics
}
}
func validateMetricTypeForQuickCreate(dataType schemapb.DataType, metricType string) error {
switch dataType {
case schemapb.DataType_BinaryVector:
if !funcutil.SliceContain(binaryMetricTypesForQuickCreate(), metricType) {
return merr.WrapErrParameterInvalid("valid index params", "invalid index params", "binary vector index does not support metric type: "+metricType)
}
case schemapb.DataType_SparseFloatVector:
if !funcutil.SliceContain(indexparamcheck.SparseFloatVectorMetrics, metricType) {
return merr.WrapErrParameterInvalid("valid index params", "invalid index params", "only IP&BM25 is the supported metric type for sparse index")
}
if metricType == metric.BM25 {
return merr.WrapErrParameterInvalid("valid index params", "invalid index params", "only BM25 Function output field support BM25 metric type")
}
default:
if !funcutil.SliceContain(indexparamcheck.FloatVectorMetrics, metricType) {
return merr.WrapErrParameterInvalid("valid index params", "invalid index params", "float vector index does not support metric type: "+metricType)
}
}
return nil
}
func consistencyLevelForCreateCollection(httpReq *CollectionReq) (commonpb.ConsistencyLevel, error) {
consistencyLevel := commonpb.ConsistencyLevel_Bounded
paramConsistencyLevel, hasParamConsistencyLevel := httpReq.Params["consistencyLevel"]
paramConsistencyLevelStr := ""
if hasParamConsistencyLevel {
paramConsistencyLevelStr = fmt.Sprintf("%s", paramConsistencyLevel)
}
if httpReq.ConsistencyLevel != "" && hasParamConsistencyLevel && httpReq.ConsistencyLevel != paramConsistencyLevelStr {
return consistencyLevel, merr.WrapErrParameterInvalid("same consistencyLevel", paramConsistencyLevel,
"top-level consistencyLevel conflicts with params.consistencyLevel")
}
consistencyLevelStr := httpReq.ConsistencyLevel
var actualConsistencyLevel interface{} = httpReq.ConsistencyLevel
if consistencyLevelStr == "" && hasParamConsistencyLevel {
consistencyLevelStr = paramConsistencyLevelStr
actualConsistencyLevel = paramConsistencyLevel
}
if consistencyLevelStr == "" {
return consistencyLevel, nil
}
if level, ok := commonpb.ConsistencyLevel_value[consistencyLevelStr]; ok {
return commonpb.ConsistencyLevel(level), nil
}
return consistencyLevel, merr.WrapErrParameterInvalid("Strong, Session, Bounded, Eventually, Customized", actualConsistencyLevel,
"consistencyLevel can only be [Strong, Session, Bounded, Eventually, Customized], default: Bounded")
}
func (h *HandlersV2) createCollection(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*CollectionReq)
req := &milvuspb.CreateCollectionRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
Properties: []*commonpb.KeyValuePair{},
}
c.Set(ContextRequest, req)
if httpReq.HasTopLevelExternalConfig() {
err := merr.WrapErrParameterInvalid("schema.externalSource/schema.externalSpec", "top-level externalSource/externalSpec",
"externalSource and externalSpec must be set under schema when creating an external collection")
mlog.Warn(ctx, "high level restful api, create external collection with top-level external config fail", mlog.Err(err), mlog.Any("request", anyReq))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: err.Error(),
})
return nil, err
}
var schema []byte
var err error
fieldNames := map[string]bool{}
partitionsNum := int64(-1)
quickCreateVectorDataType := schemapb.DataType_FloatVector
if len(httpReq.Schema.Fields) == 0 {
if httpReq.GetExternalSource() != "" || httpReq.GetExternalSpec() != "" {
err := merr.WrapErrParameterInvalid("schema.fields", "empty schema fields",
"external collection is not supported by quick create; provide schema.fields with externalField")
mlog.Warn(ctx, "high level restful api, quickly create external collection fail", mlog.Err(err), mlog.Any("request", anyReq))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: err.Error(),
})
return nil, err
}
if len(httpReq.Schema.Functions) > 0 {
err := merr.WrapErrParameterInvalid("schema", "functions",
"functions are not supported for quickly create collection")
mlog.Warn(ctx, "high level restful api, quickly create collection fail", mlog.Err(err), mlog.Any("request", anyReq))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: err.Error(),
})
return nil, err
}
switch httpReq.VectorFieldType {
case "", "FloatVector":
httpReq.VectorFieldType = "FloatVector"
case "BinaryVector":
quickCreateVectorDataType = schemapb.DataType_BinaryVector
case "Float16Vector":
quickCreateVectorDataType = schemapb.DataType_Float16Vector
case "BFloat16Vector":
quickCreateVectorDataType = schemapb.DataType_BFloat16Vector
case "SparseFloatVector":
quickCreateVectorDataType = schemapb.DataType_SparseFloatVector
default:
err := merr.WrapErrParameterInvalid("FloatVector, BinaryVector, Float16Vector, BFloat16Vector, SparseFloatVector", httpReq.VectorFieldType,
"vectorFieldType can only be [FloatVector, BinaryVector, Float16Vector, BFloat16Vector, SparseFloatVector], default: FloatVector")
mlog.Warn(ctx, "high level restful api, quickly create collection fail", mlog.Err(err), mlog.Any("request", anyReq))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: err.Error(),
})
return nil, err
}
if quickCreateVectorDataType == schemapb.DataType_SparseFloatVector && httpReq.Dimension != 0 {
err := merr.WrapErrParameterInvalid(int32(0), httpReq.Dimension,
"dimension should not be specified for SparseFloatVector quick create")
mlog.Warn(ctx, "high level restful api, quickly create collection fail", mlog.Err(err), mlog.Any("request", anyReq))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: err.Error(),
})
return nil, err
}
if quickCreateVectorDataType != schemapb.DataType_SparseFloatVector && httpReq.Dimension == 0 {
err := merr.WrapErrParameterInvalid("collectionName & dimension", "collectionName",
"dimension is required for quickly create collection(default metric type: "+DefaultMetricType+")")
mlog.Warn(ctx, "high level restful api, quickly create collection fail", mlog.Err(err), mlog.Any("request", anyReq))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: err.Error(),
})
return nil, err
}
if len(httpReq.MetricType) == 0 {
httpReq.MetricType = defaultMetricTypeForQuickCreate(quickCreateVectorDataType)
}
if err := validateMetricTypeForQuickCreate(quickCreateVectorDataType, httpReq.MetricType); err != nil {
mlog.Warn(ctx, "high level restful api, quickly create collection fail", mlog.Err(err), mlog.Any("request", anyReq))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: err.Error(),
})
return nil, err
}
idDataType := schemapb.DataType_Int64
idParams := []*commonpb.KeyValuePair{}
switch httpReq.IDType {
case "VarChar", "Varchar":
idDataType = schemapb.DataType_VarChar
idParams = append(idParams, &commonpb.KeyValuePair{
Key: common.MaxLengthKey,
Value: fmt.Sprintf("%v", httpReq.Params["max_length"]),
})
httpReq.IDType = "VarChar"
case "", "Int64", "int64":
httpReq.IDType = "Int64"
default:
err := merr.WrapErrParameterInvalid("Int64, Varchar", httpReq.IDType,
"idType can only be [Int64, VarChar], default: Int64")
mlog.Warn(ctx, "high level restful api, quickly create collection fail", mlog.Err(err), mlog.Any("request", anyReq))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: err.Error(),
})
return nil, err
}
if len(httpReq.PrimaryFieldName) == 0 {
httpReq.PrimaryFieldName = DefaultPrimaryFieldName
}
if len(httpReq.VectorFieldName) == 0 {
httpReq.VectorFieldName = DefaultVectorFieldName
}
enableDynamic := EnableDynamic
if enStr, ok := httpReq.Params["enableDynamicField"]; ok {
enableDynamic, err = strconv.ParseBool(fmt.Sprintf("%v", enStr))
if err != nil {
mlog.Warn(ctx, "high level restful api, parse enableDynamicField fail", mlog.Err(err), mlog.Any("request", anyReq))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: "parse enableDynamicField fail, err:" + err.Error(),
})
return nil, err
}
}
vectorTypeParams := []*commonpb.KeyValuePair{}
if quickCreateVectorDataType != schemapb.DataType_SparseFloatVector {
vectorTypeParams = append(vectorTypeParams, &commonpb.KeyValuePair{
Key: Dim,
Value: strconv.FormatInt(int64(httpReq.Dimension), 10),
})
}
schema, err = proto.Marshal(&schemapb.CollectionSchema{
Name: httpReq.CollectionName,
Fields: []*schemapb.FieldSchema{
{
FieldID: common.StartOfUserFieldID,
Name: httpReq.PrimaryFieldName,
IsPrimaryKey: true,
DataType: idDataType,
AutoID: httpReq.AutoID,
TypeParams: idParams,
},
{
FieldID: common.StartOfUserFieldID + 1,
Name: httpReq.VectorFieldName,
IsPrimaryKey: false,
DataType: quickCreateVectorDataType,
TypeParams: vectorTypeParams,
AutoID: DisableAutoID,
},
},
EnableDynamicField: enableDynamic,
Description: httpReq.Description,
ExternalSource: httpReq.GetExternalSource(),
ExternalSpec: httpReq.GetExternalSpec(),
})
} else {
collSchema := schemapb.CollectionSchema{
Name: httpReq.CollectionName,
AutoID: httpReq.Schema.AutoId,
Fields: []*schemapb.FieldSchema{},
StructArrayFields: []*schemapb.StructArrayFieldSchema{},
Functions: []*schemapb.FunctionSchema{},
EnableDynamicField: httpReq.Schema.EnableDynamicField,
Description: httpReq.Description,
ExternalSource: httpReq.GetExternalSource(),
ExternalSpec: httpReq.GetExternalSpec(),
}
allOutputFields := []string{}
for _, function := range httpReq.Schema.Functions {
f, err := genFunctionSchema(ctx, &function)
if err != nil {
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrParameterInvalid),
HTTPReturnMessage: err.Error(),
})
return nil, err
}
collSchema.Functions = append(collSchema.Functions, f)
allOutputFields = append(allOutputFields, function.OutputFieldNames...)
}
for _, field := range httpReq.Schema.Fields {
fieldDataType, ok := schemapb.DataType_value[field.DataType]
if !ok {
mlog.Warn(ctx, "field's data type is invalid(case sensitive).", mlog.Any("fieldDataType", field.DataType), mlog.Any("field", field))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrParameterInvalid),
HTTPReturnMessage: merr.ErrParameterInvalid.Error() + ", data type " + field.DataType + " is invalid(case sensitive).",
})
return nil, merr.ErrParameterInvalid
}
dataType := schemapb.DataType(fieldDataType)
fieldSchema := schemapb.FieldSchema{
Name: field.FieldName,
IsPrimaryKey: field.IsPrimary,
IsPartitionKey: field.IsPartitionKey,
IsClusteringKey: field.IsClusteringKey,
DataType: dataType,
TypeParams: []*commonpb.KeyValuePair{},
Nullable: field.Nullable,
ExternalField: field.ExternalField,
}
fieldSchema.DefaultValue, err = convertDefaultValue(field.DefaultValue, dataType)
if err != nil {
mlog.Warn(ctx, "convert defaultValue fail", mlog.Any("defaultValue", field.DefaultValue))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: "convert defaultValue fail, err:" + err.Error(),
})
return nil, err
}
if dataType == schemapb.DataType_Array {
if _, ok := schemapb.DataType_value[field.ElementDataType]; !ok {
mlog.Warn(ctx, "element's data type is invalid(case sensitive).", mlog.Any("elementDataType", field.ElementDataType), mlog.Any("field", field))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrParameterInvalid),
HTTPReturnMessage: merr.ErrParameterInvalid.Error() + ", element data type " + field.ElementDataType + " is invalid(case sensitive).",
})
return nil, merr.ErrParameterInvalid
}
fieldSchema.ElementType = schemapb.DataType(schemapb.DataType_value[field.ElementDataType])
}
if field.IsPrimary {
fieldSchema.AutoID = httpReq.Schema.AutoId
}
if field.IsPartitionKey {
if partitionsNumStr, ok := httpReq.Params["partitionsNum"]; ok {
if partitions, err := strconv.ParseInt(fmt.Sprintf("%v", partitionsNumStr), 10, 64); err == nil {
partitionsNum = partitions
}
}
}
for key, fieldParam := range field.ElementTypeParams {
value, err := getElementTypeParams(fieldParam)
if err != nil {
return nil, err
}
fieldSchema.TypeParams = append(fieldSchema.TypeParams, &commonpb.KeyValuePair{Key: key, Value: value})
}
if lo.Contains(allOutputFields, field.FieldName) {
fieldSchema.IsFunctionOutput = true
}
collSchema.Fields = append(collSchema.Fields, &fieldSchema)
fieldNames[field.FieldName] = true
}
for i := range httpReq.Schema.StructFields {
structField := httpReq.Schema.StructFields[i]
if _, dup := fieldNames[structField.FieldName]; dup {
err := merr.WrapErrParameterInvalidMsg("duplicated field name: %s", structField.FieldName)
mlog.Warn(ctx, "high level restful api, create collection fail", mlog.Err(err), mlog.Any("request", anyReq))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: err.Error(),
})
return nil, err
}
structProto, err := structField.GetProto(ctx)
if err != nil {
mlog.Warn(ctx, "high level restful api, convert struct array field fail",
mlog.String("structField", structField.FieldName), mlog.Err(err))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: err.Error(),
})
return nil, err
}
collSchema.StructArrayFields = append(collSchema.StructArrayFields, structProto)
fieldNames[structField.FieldName] = true
for _, sub := range structProto.GetFields() {
qualified := typeutil.ConcatStructFieldName(structField.FieldName, sub.GetName())
fieldNames[qualified] = true
}
}
schema, err = proto.Marshal(&collSchema)
}
if err != nil {
mlog.Warn(ctx, "high level restful api, marshal collection schema fail", mlog.Err(err), mlog.Any("request", anyReq))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrMarshalCollectionSchema),
HTTPReturnMessage: merr.ErrMarshalCollectionSchema.Error() + ", error: " + err.Error(),
})
return nil, err
}
req.Schema = schema
shardsNum := int32(ShardNumDefault)
if shardsNumStr, ok := httpReq.Params["shardsNum"]; ok {
if shards, err := strconv.ParseInt(fmt.Sprintf("%v", shardsNumStr), 10, 64); err == nil {
shardsNum = int32(shards)
}
}
req.ShardsNum = shardsNum
consistencyLevel, err := consistencyLevelForCreateCollection(httpReq)
if err != nil {
mlog.Warn(ctx, "high level restful api, create collection fail", mlog.Err(err), mlog.Any("request", anyReq))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: err.Error(),
})
return nil, err
}
req.ConsistencyLevel = consistencyLevel
if partitionsNum > 0 {
req.NumPartitions = partitionsNum
}
ttlPropertySet := false
for _, propertyKey := range []string{common.CollectionTTLConfigKey, "ttl.seconds"} {
value, ok := httpReq.Properties[propertyKey]
if !ok {
continue
}
if ttlPropertySet {
err := merr.WrapErrParameterInvalidMsg("collection TTL is specified multiple times")
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: err.Error(),
})
return nil, err
}
ttlPropertySet = true
req.Properties = append(req.Properties, &commonpb.KeyValuePair{Key: common.CollectionTTLConfigKey, Value: fmt.Sprintf("%v", value)})
}
if _, ok := httpReq.Params["ttlSeconds"]; ok {
if ttlPropertySet {
err := merr.WrapErrParameterInvalidMsg("collection TTL is specified multiple times")
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: err.Error(),
})
return nil, err
}
req.Properties = append(req.Properties, &commonpb.KeyValuePair{
Key: common.CollectionTTLConfigKey,
Value: fmt.Sprintf("%v", httpReq.Params["ttlSeconds"]),
})
}
if _, ok := httpReq.Params["ttlField"]; ok {
req.Properties = append(req.Properties, &commonpb.KeyValuePair{
Key: common.CollectionTTLFieldKey,
Value: fmt.Sprintf("%v", httpReq.Params["ttlField"]),
})
}
if _, ok := httpReq.Params["partitionKeyIsolation"]; ok {
req.Properties = append(req.Properties, &commonpb.KeyValuePair{
Key: common.PartitionKeyIsolationKey,
Value: fmt.Sprintf("%v", httpReq.Params["partitionKeyIsolation"]),
})
}
if _, ok := httpReq.Params[common.MmapEnabledKey]; ok {
req.Properties = append(req.Properties, &commonpb.KeyValuePair{
Key: common.MmapEnabledKey,
Value: fmt.Sprintf("%v", httpReq.Params[common.MmapEnabledKey]),
})
}
for _, key := range []string{
common.WarmupScalarFieldKey,
common.WarmupScalarIndexKey,
common.WarmupVectorFieldKey,
common.WarmupVectorIndexKey,
} {
if _, ok := httpReq.Params[key]; ok {
req.Properties = append(req.Properties, &commonpb.KeyValuePair{
Key: key,
Value: fmt.Sprintf("%v", httpReq.Params[key]),
})
}
}
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/CreateCollection", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.CreateCollection(reqCtx, req.(*milvuspb.CreateCollectionRequest))
})
if err != nil {
return resp, err
}
if len(httpReq.Schema.Fields) == 0 {
createIndexReq := &milvuspb.CreateIndexRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
FieldName: httpReq.VectorFieldName,
IndexName: httpReq.VectorFieldName,
ExtraParams: []*commonpb.KeyValuePair{{Key: common.MetricTypeKey, Value: httpReq.MetricType}},
}
statusResponse, err := wrapperProxyWithLimit(ctx, c, createIndexReq, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/CreateIndex", false, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.CreateIndex(ctx, req.(*milvuspb.CreateIndexRequest))
})
if err != nil {
return statusResponse, err
}
} else {
if len(httpReq.IndexParams) == 0 {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
return nil, nil
}
for _, indexParam := range httpReq.IndexParams {
if _, ok := fieldNames[indexParam.FieldName]; !ok {
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(merr.ErrMissingRequiredParameters),
HTTPReturnMessage: merr.ErrMissingRequiredParameters.Error() + ", error: `" + indexParam.FieldName + "` hasn't defined in schema",
})
return nil, merr.ErrMissingRequiredParameters
}
createIndexReq := &milvuspb.CreateIndexRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
FieldName: indexParam.FieldName,
IndexName: indexParam.IndexName,
ExtraParams: []*commonpb.KeyValuePair{{Key: common.MetricTypeKey, Value: indexParam.MetricType}},
}
createIndexReq.ExtraParams, err = convertToExtraParams(indexParam)
if err != nil {
// will not happen
mlog.Warn(ctx, "high level restful api, convertToExtraParams fail", mlog.Err(err), mlog.Any("request", anyReq))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: err.Error(),
})
return resp, err
}
statusResponse, err := wrapperProxyWithLimit(ctx, c, createIndexReq, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/CreateIndex", false, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.CreateIndex(ctx, req.(*milvuspb.CreateIndexRequest))
})
if err != nil {
return statusResponse, err
}
}
}
loadReq := &milvuspb.LoadCollectionRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
}
statusResponse, err := wrapperProxyWithLimit(ctx, c, loadReq, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/LoadCollection", false, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.LoadCollection(ctx, req.(*milvuspb.LoadCollectionRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return statusResponse, err
}
func (h *HandlersV2) createDatabase(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*DatabaseReqWithProperties)
req := &milvuspb.CreateDatabaseRequest{
DbName: dbName,
}
properties := make([]*commonpb.KeyValuePair, 0, len(httpReq.Properties))
for key, value := range httpReq.Properties {
properties = append(properties, &commonpb.KeyValuePair{Key: key, Value: fmt.Sprintf("%v", value)})
}
req.Properties = properties
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/CreateDatabase", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.CreateDatabase(reqCtx, req.(*milvuspb.CreateDatabaseRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) dropDatabase(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
req := &milvuspb.DropDatabaseRequest{
DbName: dbName,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/DropDatabase", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.DropDatabase(reqCtx, req.(*milvuspb.DropDatabaseRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) dropDatabaseProperties(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*DropDatabasePropertiesReq)
req := &milvuspb.AlterDatabaseRequest{
DbName: dbName,
DeleteKeys: httpReq.PropertyKeys,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/AlterDatabase", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.AlterDatabase(reqCtx, req.(*milvuspb.AlterDatabaseRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
// todo: use a more flexible way to handle the number of input parameters of req
func (h *HandlersV2) listDatabases(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
req := &milvuspb.ListDatabasesRequest{}
c.Set(ContextRequest, req)
// handle at rootcoord side
resp, err := wrapperProxy(ctx, c, req, false, false, "/milvus.proto.milvus.MilvusService/ListDatabases", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.ListDatabases(reqCtx, req.(*milvuspb.ListDatabasesRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnList(resp.(*milvuspb.ListDatabasesResponse).DbNames))
}
return resp, err
}
func (h *HandlersV2) describeDatabase(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
req := &milvuspb.DescribeDatabaseRequest{
DbName: dbName,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/DescribeDatabase", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.DescribeDatabase(reqCtx, req.(*milvuspb.DescribeDatabaseRequest))
})
if err != nil {
return nil, err
}
info, _ := resp.(*milvuspb.DescribeDatabaseResponse)
if info.Properties == nil {
info.Properties = []*commonpb.KeyValuePair{}
}
dataBaseInfo := map[string]any{
HTTPDbName: info.DbName,
HTTPDbID: info.DbID,
HTTPProperties: info.Properties,
}
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: dataBaseInfo})
return resp, err
}
func (h *HandlersV2) alterDatabase(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*DatabaseReqWithProperties)
req := &milvuspb.AlterDatabaseRequest{
DbName: dbName,
}
properties := make([]*commonpb.KeyValuePair, 0, len(httpReq.Properties))
for key, value := range httpReq.Properties {
properties = append(properties, &commonpb.KeyValuePair{Key: key, Value: fmt.Sprintf("%v", value)})
}
req.Properties = properties
c.Set(ContextRequest, req)
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/AlterDatabase", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.AlterDatabase(reqCtx, req.(*milvuspb.AlterDatabaseRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) listPartitions(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
collectionGetter, _ := anyReq.(requestutil.CollectionNameGetter)
req := &milvuspb.ShowPartitionsRequest{
DbName: dbName,
CollectionName: collectionGetter.GetCollectionName(),
}
c.Set(ContextRequest, req)
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/ShowPartitions", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.ShowPartitions(reqCtx, req.(*milvuspb.ShowPartitionsRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnList(resp.(*milvuspb.ShowPartitionsResponse).PartitionNames))
}
return resp, err
}
func (h *HandlersV2) hasPartitions(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
collectionGetter, _ := anyReq.(requestutil.CollectionNameGetter)
partitionGetter, _ := anyReq.(requestutil.PartitionNameGetter)
req := &milvuspb.HasPartitionRequest{
DbName: dbName,
CollectionName: collectionGetter.GetCollectionName(),
PartitionName: partitionGetter.GetPartitionName(),
}
c.Set(ContextRequest, req)
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/HasPartition", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.HasPartition(reqCtx, req.(*milvuspb.HasPartitionRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnHas(resp.(*milvuspb.BoolResponse).Value))
}
return resp, err
}
// data coord will collect partitions' row_count
// proxy grpc call only support partition not partitions
func (h *HandlersV2) statsPartition(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
collectionGetter, _ := anyReq.(requestutil.CollectionNameGetter)
partitionGetter, _ := anyReq.(requestutil.PartitionNameGetter)
req := &milvuspb.GetPartitionStatisticsRequest{
DbName: dbName,
CollectionName: collectionGetter.GetCollectionName(),
PartitionName: partitionGetter.GetPartitionName(),
}
c.Set(ContextRequest, req)
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/GetPartitionStatistics", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.GetPartitionStatistics(reqCtx, req.(*milvuspb.GetPartitionStatisticsRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnRowCount(resp.(*milvuspb.GetPartitionStatisticsResponse).Stats))
}
return resp, err
}
func (h *HandlersV2) createPartition(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
collectionGetter, _ := anyReq.(requestutil.CollectionNameGetter)
partitionGetter, _ := anyReq.(requestutil.PartitionNameGetter)
req := &milvuspb.CreatePartitionRequest{
DbName: dbName,
CollectionName: collectionGetter.GetCollectionName(),
PartitionName: partitionGetter.GetPartitionName(),
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/CreatePartition", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.CreatePartition(reqCtx, req.(*milvuspb.CreatePartitionRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) dropPartition(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
collectionGetter, _ := anyReq.(requestutil.CollectionNameGetter)
partitionGetter, _ := anyReq.(requestutil.PartitionNameGetter)
req := &milvuspb.DropPartitionRequest{
DbName: dbName,
CollectionName: collectionGetter.GetCollectionName(),
PartitionName: partitionGetter.GetPartitionName(),
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/DropPartition", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.DropPartition(reqCtx, req.(*milvuspb.DropPartitionRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) loadPartitions(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*PartitionsReq)
req := &milvuspb.LoadPartitionsRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
PartitionNames: httpReq.PartitionNames,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/LoadPartitions", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.LoadPartitions(reqCtx, req.(*milvuspb.LoadPartitionsRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) releasePartitions(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*PartitionsReq)
req := &milvuspb.ReleasePartitionsRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
PartitionNames: httpReq.PartitionNames,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/ReleasePartitions", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.ReleasePartitions(reqCtx, req.(*milvuspb.ReleasePartitionsRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) listUsers(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
req := &milvuspb.ListCredUsersRequest{}
c.Set(ContextRequest, req)
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/ListCredUsers", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.ListCredUsers(reqCtx, req.(*milvuspb.ListCredUsersRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnList(resp.(*milvuspb.ListCredUsersResponse).Usernames))
}
return resp, err
}
func (h *HandlersV2) describeUser(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
userNameGetter, _ := anyReq.(UserNameGetter)
userName := userNameGetter.GetUserName()
req := &milvuspb.SelectUserRequest{
User: &milvuspb.UserEntity{
Name: userName,
},
IncludeRoleInfo: true,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/SelectUser", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.SelectUser(reqCtx, req.(*milvuspb.SelectUserRequest))
})
if err == nil {
roleNames := []string{}
description := ""
for _, userRole := range resp.(*milvuspb.SelectUserResponse).Results {
if userRole.User.Name == userName {
description = userRole.GetDescription()
for _, role := range userRole.Roles {
roleNames = append(roleNames, role.Name)
}
}
}
result := wrapperReturnList(roleNames)
result[HTTPReturnDescription] = description
HTTPReturn(c, http.StatusOK, result)
}
return resp, err
}
func (h *HandlersV2) createUser(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*PasswordReq)
req := &milvuspb.CreateCredentialRequest{
Username: httpReq.UserName,
Password: crypto.Base64Encode(httpReq.Password),
Description: httpReq.Description,
}
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/CreateCredential", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.CreateCredential(reqCtx, req.(*milvuspb.CreateCredentialRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) updateUser(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*NewPasswordReq)
req := &milvuspb.UpdateCredentialRequest{
Username: httpReq.UserName,
Description: httpReq.Description,
}
if httpReq.NewPassword != "" {
req.OldPassword = crypto.Base64Encode(httpReq.Password)
req.NewPassword = crypto.Base64Encode(httpReq.NewPassword)
}
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/UpdateCredential", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.UpdateCredential(reqCtx, req.(*milvuspb.UpdateCredentialRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) dropUser(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
getter, _ := anyReq.(UserNameGetter)
req := &milvuspb.DeleteCredentialRequest{
Username: getter.GetUserName(),
}
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/DeleteCredential", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.DeleteCredential(reqCtx, req.(*milvuspb.DeleteCredentialRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) operateRoleToUser(ctx context.Context, c *gin.Context, userName, roleName string, operateType milvuspb.OperateUserRoleType) (interface{}, error) {
req := &milvuspb.OperateUserRoleRequest{
Username: userName,
RoleName: roleName,
Type: operateType,
}
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/OperateUserRole", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.OperateUserRole(reqCtx, req.(*milvuspb.OperateUserRoleRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) addRoleToUser(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
return h.operateRoleToUser(ctx, c, anyReq.(*UserRoleReq).UserName, anyReq.(*UserRoleReq).RoleName, milvuspb.OperateUserRoleType_AddUserToRole)
}
func (h *HandlersV2) removeRoleFromUser(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
return h.operateRoleToUser(ctx, c, anyReq.(*UserRoleReq).UserName, anyReq.(*UserRoleReq).RoleName, milvuspb.OperateUserRoleType_RemoveUserFromRole)
}
func (h *HandlersV2) listRoles(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
req := &milvuspb.SelectRoleRequest{}
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/SelectRole", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.SelectRole(reqCtx, req.(*milvuspb.SelectRoleRequest))
})
if err == nil {
roleNames := []string{}
for _, role := range resp.(*milvuspb.SelectRoleResponse).Results {
roleNames = append(roleNames, role.Role.Name)
}
HTTPReturn(c, http.StatusOK, wrapperReturnList(roleNames))
}
return resp, err
}
func (h *HandlersV2) describeRole(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
getter, _ := anyReq.(RoleNameGetter)
roleReq := &milvuspb.SelectRoleRequest{
Role: &milvuspb.RoleEntity{Name: getter.GetRoleName()},
}
roleResp, err := wrapperProxy(ctx, c, roleReq, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/SelectRole", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.SelectRole(reqCtx, req.(*milvuspb.SelectRoleRequest))
})
if err != nil {
return roleResp, err
}
description := ""
if results := roleResp.(*milvuspb.SelectRoleResponse).GetResults(); len(results) > 0 {
description = results[0].GetRole().GetDescription()
}
req := &milvuspb.SelectGrantRequest{
Entity: &milvuspb.GrantEntity{Role: &milvuspb.RoleEntity{Name: getter.GetRoleName()}, DbName: dbName},
}
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/SelectGrant", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.SelectGrant(reqCtx, req.(*milvuspb.SelectGrantRequest))
})
if err == nil {
privileges := [](map[string]string){}
for _, grant := range resp.(*milvuspb.SelectGrantResponse).Entities {
privilege := map[string]string{
HTTPReturnObjectType: grant.Object.Name,
HTTPReturnObjectName: grant.ObjectName,
HTTPReturnPrivilege: grant.Grantor.Privilege.Name,
HTTPReturnDbName: grant.DbName,
HTTPReturnGrantor: grant.Grantor.User.Name,
}
privileges = append(privileges, privilege)
}
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: privileges, HTTPReturnDescription: description})
}
return resp, err
}
func (h *HandlersV2) createRole(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*RoleReq)
req := &milvuspb.CreateRoleRequest{
Entity: &milvuspb.RoleEntity{Name: httpReq.GetRoleName(), Description: httpReq.GetDescription()},
}
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/CreateRole", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.CreateRole(reqCtx, req.(*milvuspb.CreateRoleRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) alterRole(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*RoleReq)
req := &milvuspb.AlterRoleRequest{
RoleName: httpReq.GetRoleName(),
Description: httpReq.GetDescription(),
}
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/AlterRole", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.AlterRole(reqCtx, req.(*milvuspb.AlterRoleRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) dropRole(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
getter, _ := anyReq.(RoleNameGetter)
req := &milvuspb.DropRoleRequest{
RoleName: getter.GetRoleName(),
}
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/DropRole", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.DropRole(reqCtx, req.(*milvuspb.DropRoleRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) operatePrivilegeToRole(ctx context.Context, c *gin.Context, httpReq *GrantReq, operateType milvuspb.OperatePrivilegeType, dbName string) (interface{}, error) {
req := &milvuspb.OperatePrivilegeRequest{
Entity: &milvuspb.GrantEntity{
Role: &milvuspb.RoleEntity{Name: httpReq.RoleName},
Object: &milvuspb.ObjectEntity{Name: httpReq.ObjectType},
ObjectName: httpReq.ObjectName,
DbName: dbName,
Grantor: &milvuspb.GrantorEntity{
Privilege: &milvuspb.PrivilegeEntity{Name: httpReq.Privilege},
},
},
Type: operateType,
}
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/OperatePrivilege", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.OperatePrivilege(reqCtx, req.(*milvuspb.OperatePrivilegeRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) operatePrivilegeToRoleV2(ctx context.Context, c *gin.Context, httpReq *GrantV2Req, operateType milvuspb.OperatePrivilegeType) (interface{}, error) {
req := &milvuspb.OperatePrivilegeV2Request{
Role: &milvuspb.RoleEntity{Name: httpReq.RoleName},
Grantor: &milvuspb.GrantorEntity{
Privilege: &milvuspb.PrivilegeEntity{Name: httpReq.Privilege},
},
Type: operateType,
DbName: httpReq.DbName,
CollectionName: httpReq.CollectionName,
}
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/OperatePrivilegeV2", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.OperatePrivilegeV2(reqCtx, req.(*milvuspb.OperatePrivilegeV2Request))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) grantV2(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
return h.operatePrivilegeToRoleV2(ctx, c, anyReq.(*GrantV2Req), milvuspb.OperatePrivilegeType_Grant)
}
func (h *HandlersV2) revokeV2(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
return h.operatePrivilegeToRoleV2(ctx, c, anyReq.(*GrantV2Req), milvuspb.OperatePrivilegeType_Revoke)
}
func (h *HandlersV2) addPrivilegeToRole(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
return h.operatePrivilegeToRole(ctx, c, anyReq.(*GrantReq), milvuspb.OperatePrivilegeType_Grant, dbName)
}
func (h *HandlersV2) removePrivilegeFromRole(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
return h.operatePrivilegeToRole(ctx, c, anyReq.(*GrantReq), milvuspb.OperatePrivilegeType_Revoke, dbName)
}
func (h *HandlersV2) createPrivilegeGroup(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*PrivilegeGroupReq)
req := &milvuspb.CreatePrivilegeGroupRequest{
GroupName: httpReq.PrivilegeGroupName,
}
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/CreatePrivilegeGroup", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.CreatePrivilegeGroup(reqCtx, req.(*milvuspb.CreatePrivilegeGroupRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) dropPrivilegeGroup(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*PrivilegeGroupReq)
req := &milvuspb.DropPrivilegeGroupRequest{
GroupName: httpReq.PrivilegeGroupName,
}
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/DropPrivilegeGroup", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.DropPrivilegeGroup(reqCtx, req.(*milvuspb.DropPrivilegeGroupRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) listPrivilegeGroups(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
req := &milvuspb.ListPrivilegeGroupsRequest{}
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/ListPrivilegeGroups", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.ListPrivilegeGroups(reqCtx, req.(*milvuspb.ListPrivilegeGroupsRequest))
})
if err == nil {
privGroups := make([]map[string]interface{}, 0)
for _, group := range resp.(*milvuspb.ListPrivilegeGroupsResponse).PrivilegeGroups {
privileges := make([]string, len(group.Privileges))
for i, privilege := range group.Privileges {
privileges[i] = privilege.Name
}
groupInfo := map[string]interface{}{
HTTPReturnPrivilegeGroupName: group.GroupName,
HTTPReturnPrivileges: strings.Join(privileges, ","),
}
privGroups = append(privGroups, groupInfo)
}
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: gin.H{
HTTPReturnPrivilegeGroups: privGroups,
}})
}
return resp, err
}
func (h *HandlersV2) addPrivilegesToGroup(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
return h.operatePrivilegeGroup(ctx, c, anyReq, dbName, milvuspb.OperatePrivilegeGroupType_AddPrivilegesToGroup)
}
func (h *HandlersV2) removePrivilegesFromGroup(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
return h.operatePrivilegeGroup(ctx, c, anyReq, dbName, milvuspb.OperatePrivilegeGroupType_RemovePrivilegesFromGroup)
}
func (h *HandlersV2) operatePrivilegeGroup(ctx context.Context, c *gin.Context, anyReq any, dbName string, operateType milvuspb.OperatePrivilegeGroupType) (interface{}, error) {
httpReq := anyReq.(*PrivilegeGroupReq)
req := &milvuspb.OperatePrivilegeGroupRequest{
GroupName: httpReq.PrivilegeGroupName,
Privileges: lo.Map(httpReq.Privileges, func(p string, _ int) *milvuspb.PrivilegeEntity {
return &milvuspb.PrivilegeEntity{Name: p}
}),
Type: operateType,
}
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/OperatePrivilegeGroup", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.OperatePrivilegeGroup(reqCtx, req.(*milvuspb.OperatePrivilegeGroupRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) listIndexes(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
collectionGetter, _ := anyReq.(requestutil.CollectionNameGetter)
indexNames := []string{}
req := &milvuspb.DescribeIndexRequest{
DbName: dbName,
CollectionName: collectionGetter.GetCollectionName(),
}
c.Set(ContextRequest, req)
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/DescribeIndex", func(reqCtx context.Context, req any) (any, error) {
resp, err := h.proxy.DescribeIndex(reqCtx, req.(*milvuspb.DescribeIndexRequest))
if errors.Is(err, merr.ErrIndexNotFound) {
return &milvuspb.DescribeIndexResponse{
IndexDescriptions: []*milvuspb.IndexDescription{},
}, nil
}
if resp != nil && errors.Is(merr.Error(resp.Status), merr.ErrIndexNotFound) {
return &milvuspb.DescribeIndexResponse{
IndexDescriptions: []*milvuspb.IndexDescription{},
}, nil
}
return resp, err
})
if err != nil {
return resp, err
}
for _, index := range resp.(*milvuspb.DescribeIndexResponse).IndexDescriptions {
indexNames = append(indexNames, index.IndexName)
}
HTTPReturn(c, http.StatusOK, wrapperReturnList(indexNames))
return resp, err
}
func (h *HandlersV2) describeIndex(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
collectionGetter, _ := anyReq.(requestutil.CollectionNameGetter)
indexGetter, _ := anyReq.(IndexNameGetter)
timeGetter, _ := anyReq.(TimestampGetter)
req := &milvuspb.DescribeIndexRequest{
DbName: dbName,
CollectionName: collectionGetter.GetCollectionName(),
IndexName: indexGetter.GetIndexName(),
Timestamp: timeGetter.GetTimestamp(),
}
c.Set(ContextRequest, req)
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/DescribeIndex", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.DescribeIndex(reqCtx, req.(*milvuspb.DescribeIndexRequest))
})
if err == nil {
indexInfos := [](map[string]any){}
for _, indexDescription := range resp.(*milvuspb.DescribeIndexResponse).IndexDescriptions {
metricType := ""
indexType := ""
mmapEnabled := ""
indexOffsetCacheEnabled := ""
warmup := ""
for _, pair := range indexDescription.Params {
switch pair.Key {
case common.MetricTypeKey:
metricType = pair.Value
case common.IndexTypeKey:
indexType = pair.Value
case common.MmapEnabledKey:
mmapEnabled = pair.Value
case common.IndexOffsetCacheEnabledKey:
indexOffsetCacheEnabled = pair.Value
case common.WarmupKey:
warmup = pair.Value
}
}
indexInfo := map[string]any{
HTTPIndexName: indexDescription.IndexName,
HTTPIndexField: indexDescription.FieldName,
HTTPReturnIndexType: indexType,
HTTPReturnIndexMetricType: metricType,
HTTPMmapEnabledKey: mmapEnabled,
HTTPIndexOffsetCacheEnabledKey: indexOffsetCacheEnabled,
HTTPWarmupKey: warmup,
HTTPReturnIndexTotalRows: indexDescription.TotalRows,
HTTPReturnIndexPendingRows: indexDescription.PendingIndexRows,
HTTPReturnIndexIndexedRows: indexDescription.IndexedRows,
HTTPReturnIndexState: indexDescription.State.String(),
HTTPReturnIndexFailReason: indexDescription.IndexStateFailReason,
HTTPReturnMinIndexVersion: indexDescription.MinIndexVersion,
HTTPReturnMaxIndexVersion: indexDescription.MaxIndexVersion,
HTTPReturnIndexParams: indexDescription.Params,
}
indexInfos = append(indexInfos, indexInfo)
}
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: indexInfos})
}
return resp, err
}
func (h *HandlersV2) createIndex(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*IndexParamReq)
for _, indexParam := range httpReq.IndexParams {
req := &milvuspb.CreateIndexRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
FieldName: indexParam.FieldName,
IndexName: indexParam.IndexName,
ExtraParams: []*commonpb.KeyValuePair{
{Key: common.MetricTypeKey, Value: indexParam.MetricType},
},
}
c.Set(ContextRequest, req)
var err error
req.ExtraParams, err = convertToExtraParams(indexParam)
if err != nil {
// will not happen
mlog.Warn(ctx, "high level restful api, convertToExtraParams fail", mlog.Err(err), mlog.Any("request", anyReq))
HTTPAbortReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(err),
HTTPReturnMessage: err.Error(),
})
return nil, err
}
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/CreateIndex", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.CreateIndex(reqCtx, req.(*milvuspb.CreateIndexRequest))
})
if err != nil {
return resp, err
}
}
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
return httpReq.IndexParams, nil
}
func (h *HandlersV2) dropIndex(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
collGetter, _ := anyReq.(requestutil.CollectionNameGetter)
indexGetter, _ := anyReq.(IndexNameGetter)
req := &milvuspb.DropIndexRequest{
DbName: dbName,
CollectionName: collGetter.GetCollectionName(),
IndexName: indexGetter.GetIndexName(),
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/DropIndex", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.DropIndex(reqCtx, req.(*milvuspb.DropIndexRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) alterIndexProperties(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*IndexReqWithProperties)
req := &milvuspb.AlterIndexRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
IndexName: httpReq.IndexName,
}
extraParams := make([]*commonpb.KeyValuePair, 0, len(httpReq.Properties))
for key, value := range httpReq.Properties {
extraParams = append(extraParams, &commonpb.KeyValuePair{Key: key, Value: fmt.Sprintf("%v", value)})
}
req.ExtraParams = extraParams
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/AlterIndex", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.AlterIndex(reqCtx, req.(*milvuspb.AlterIndexRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) dropIndexProperties(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*DropIndexPropertiesReq)
req := &milvuspb.AlterIndexRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
DeleteKeys: httpReq.PropertyKeys,
IndexName: httpReq.IndexName,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/AlterIndex", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.AlterIndex(reqCtx, req.(*milvuspb.AlterIndexRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) listAlias(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
collectionGetter, _ := anyReq.(requestutil.CollectionNameGetter)
req := &milvuspb.ListAliasesRequest{
DbName: dbName,
CollectionName: collectionGetter.GetCollectionName(),
}
c.Set(ContextRequest, req)
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/ListAliases", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.ListAliases(reqCtx, req.(*milvuspb.ListAliasesRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnList(resp.(*milvuspb.ListAliasesResponse).Aliases))
}
return resp, err
}
func (h *HandlersV2) describeAlias(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
getter, _ := anyReq.(AliasNameGetter)
req := &milvuspb.DescribeAliasRequest{
DbName: dbName,
Alias: getter.GetAliasName(),
}
c.Set(ContextRequest, req)
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/DescribeAlias", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.DescribeAlias(reqCtx, req.(*milvuspb.DescribeAliasRequest))
})
if err == nil {
response := resp.(*milvuspb.DescribeAliasResponse)
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: gin.H{
HTTPDbName: response.DbName,
HTTPCollectionName: response.Collection,
HTTPAliasName: response.Alias,
}})
}
return resp, err
}
func (h *HandlersV2) createAlias(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
collectionGetter, _ := anyReq.(requestutil.CollectionNameGetter)
aliasGetter, _ := anyReq.(AliasNameGetter)
req := &milvuspb.CreateAliasRequest{
DbName: dbName,
CollectionName: collectionGetter.GetCollectionName(),
Alias: aliasGetter.GetAliasName(),
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/CreateAlias", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.CreateAlias(reqCtx, req.(*milvuspb.CreateAliasRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) dropAlias(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
getter, _ := anyReq.(AliasNameGetter)
req := &milvuspb.DropAliasRequest{
DbName: dbName,
Alias: getter.GetAliasName(),
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/DropAlias", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.DropAlias(reqCtx, req.(*milvuspb.DropAliasRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) alterAlias(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
collectionGetter, _ := anyReq.(requestutil.CollectionNameGetter)
aliasGetter, _ := anyReq.(AliasNameGetter)
req := &milvuspb.AlterAliasRequest{
DbName: dbName,
CollectionName: collectionGetter.GetCollectionName(),
Alias: aliasGetter.GetAliasName(),
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/AlterAlias", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.AlterAlias(reqCtx, req.(*milvuspb.AlterAliasRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
}
return resp, err
}
func (h *HandlersV2) listImportJob(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
var collectionName string
if collectionGetter, ok := anyReq.(requestutil.CollectionNameGetter); ok {
collectionName = collectionGetter.GetCollectionName()
}
req := &internalpb.ListImportsRequest{
DbName: dbName,
CollectionName: collectionName,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxy(ctx, c, req, false, false, "/milvus.proto.milvus.MilvusService/ListImports", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.ListImports(reqCtx, req.(*internalpb.ListImportsRequest))
})
if err == nil {
returnData := make(map[string]interface{})
records := make([]map[string]interface{}, 0)
response := resp.(*internalpb.ListImportsResponse)
jobIDs := response.GetJobIDs()
states := response.GetStates()
progresses := response.GetProgresses()
reasons := response.GetReasons()
collections := response.GetCollectionNames()
for i, jobID := range jobIDs {
collection := collections[i]
if h.checkAuth {
err = checkAuthorizationHelper(ctx, c, &milvuspb.ImportAuthPlaceholder{
DbName: dbName,
CollectionName: collection,
})
if err != nil {
continue
}
}
jobDetail := make(map[string]interface{})
jobDetail["jobId"] = jobID
jobDetail["collectionName"] = collection
jobDetail["state"] = states[i].String()
jobDetail["progress"] = progresses[i]
reason := reasons[i]
if reason != "" {
jobDetail["reason"] = reason
}
records = append(records, jobDetail)
}
returnData["records"] = records
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: returnData})
}
return resp, err
}
func (h *HandlersV2) createImportJob(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
var (
collectionGetter = anyReq.(requestutil.CollectionNameGetter)
partitionGetter = anyReq.(requestutil.PartitionNameGetter)
filesGetter = anyReq.(FilesGetter)
optionsGetter = anyReq.(OptionsGetter)
)
req := &internalpb.ImportRequest{
DbName: dbName,
CollectionName: collectionGetter.GetCollectionName(),
PartitionName: partitionGetter.GetPartitionName(),
Files: lo.Map(filesGetter.GetFiles(), func(paths []string, _ int) *internalpb.ImportFile {
return &internalpb.ImportFile{Paths: paths}
}),
Options: funcutil.Map2KeyValuePair(optionsGetter.GetOptions()),
}
c.Set(ContextRequest, req)
if h.checkAuth {
err := checkAuthorizationV2(ctx, c, false, &milvuspb.ImportAuthPlaceholder{
DbName: dbName,
CollectionName: collectionGetter.GetCollectionName(),
PartitionName: partitionGetter.GetPartitionName(),
})
if err != nil {
return nil, err
}
ctx = c.Request.Context()
}
resp, err := wrapperProxy(ctx, c, req, false, false, "/milvus.proto.milvus.MilvusService/Import", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.ImportV2(reqCtx, req.(*internalpb.ImportRequest))
})
if err == nil {
returnData := make(map[string]interface{})
returnData["jobId"] = resp.(*internalpb.ImportResponse).GetJobID()
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: returnData})
}
return resp, err
}
func (h *HandlersV2) getImportJobProcess(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
jobIDGetter := anyReq.(JobIDGetter)
response, err := h.getImportProgress(ctx, c, dbName, jobIDGetter.GetJobID())
if err != nil {
return response, err
}
if err := h.checkImportPrivilege(ctx, c, dbName, response.GetCollectionName()); err != nil {
return nil, err
}
returnData := make(map[string]interface{})
returnData["jobId"] = jobIDGetter.GetJobID()
returnData["createTime"] = response.GetCreateTime()
returnData["collectionName"] = response.GetCollectionName()
returnData["completeTime"] = response.GetCompleteTime()
returnData["state"] = response.GetState().String()
returnData["progress"] = response.GetProgress()
returnData["importedRows"] = response.GetImportedRows()
returnData["totalRows"] = response.GetTotalRows()
reason := response.GetReason()
if reason != "" {
returnData["reason"] = reason
}
details := make([]map[string]interface{}, 0)
totalFileSize := int64(0)
for _, taskProgress := range response.GetTaskProgresses() {
detail := make(map[string]interface{})
detail["fileName"] = taskProgress.GetFileName()
detail["fileSize"] = taskProgress.GetFileSize()
detail["progress"] = taskProgress.GetProgress()
detail["completeTime"] = taskProgress.GetCompleteTime()
detail["state"] = taskProgress.GetState()
detail["importedRows"] = taskProgress.GetImportedRows()
detail["totalRows"] = taskProgress.GetTotalRows()
reason = taskProgress.GetReason()
if reason != "" {
detail["reason"] = reason
}
details = append(details, detail)
totalFileSize += taskProgress.GetFileSize()
}
returnData["fileSize"] = totalFileSize
returnData["details"] = details
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: returnData})
return response, nil
}
func (h *HandlersV2) getImportProgress(ctx context.Context, c *gin.Context, dbName string, jobID string) (*internalpb.GetImportProgressResponse, error) {
req := &internalpb.GetImportProgressRequest{
DbName: dbName,
JobID: jobID,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxy(ctx, c, req, false, false, "/milvus.proto.milvus.MilvusService/GetImportProgress", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.GetImportProgress(reqCtx, req.(*internalpb.GetImportProgressRequest))
})
if err != nil {
return nil, err
}
return resp.(*internalpb.GetImportProgressResponse), nil
}
func (h *HandlersV2) checkImportPrivilege(ctx context.Context, c *gin.Context, dbName string, collectionName string) error {
if !h.checkAuth {
return nil
}
authErr := checkAuthorizationHelper(ctx, c, &milvuspb.ImportAuthPlaceholder{
DbName: dbName,
CollectionName: collectionName,
})
if authErr != nil {
HTTPReturn(c, http.StatusForbidden, gin.H{
HTTPReturnCode: merr.Code(authErr),
HTTPReturnMessage: authErr.Error(),
})
return authErr
}
return nil
}
func (h *HandlersV2) checkImportJobAuth(ctx context.Context, c *gin.Context, dbName string, jobID string) error {
if !h.checkAuth {
return nil
}
// Commit/abort requests only carry jobID. Import privilege is collection-scoped,
// so resolve the job's collection before checking PrivilegeImport.
response, err := h.getImportProgress(ctx, c, dbName, jobID)
if err != nil {
return err
}
return h.checkImportPrivilege(ctx, c, dbName, response.GetCollectionName())
}
func (h *HandlersV2) restoreExternalSnapshot(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*RestoreExternalSnapshotReq)
req := &milvuspb.RestoreExternalSnapshotRequest{
DbName: dbName,
TargetCollectionName: httpReq.TargetCollectionName,
SnapshotMetadataUri: httpReq.SnapshotMetadataURI,
ExternalSpec: httpReq.ExternalSpec,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/RestoreExternalSnapshot", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.RestoreExternalSnapshot(reqCtx, req.(*milvuspb.RestoreExternalSnapshotRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: gin.H{"jobId": resp.(*milvuspb.RestoreExternalSnapshotResponse).GetJobId()},
})
}
return resp, err
}
func (h *HandlersV2) exportSnapshot(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*ExportSnapshotReq)
req := &milvuspb.ExportSnapshotRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
Name: httpReq.Name,
TargetS3Path: httpReq.TargetS3Path,
ExternalSpec: httpReq.ExternalSpec,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/ExportSnapshot", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.ExportSnapshot(reqCtx, req.(*milvuspb.ExportSnapshotRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: gin.H{"snapshotMetadataURI": resp.(*milvuspb.ExportSnapshotResponse).GetSnapshotMetadataUri()},
})
}
return resp, err
}
func restoreSnapshotJobToREST(info *milvuspb.RestoreSnapshotInfo) gin.H {
if info == nil {
return gin.H{}
}
return gin.H{
"jobId": info.GetJobId(),
"snapshotName": info.GetSnapshotName(),
"dbName": info.GetDbName(),
"collectionName": info.GetCollectionName(),
"state": info.GetState().String(),
"progress": info.GetProgress(),
"reason": info.GetReason(),
"startTime": info.GetStartTime(),
"timeCost": info.GetTimeCost(),
}
}
func (h *HandlersV2) getRestoreSnapshotState(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*JobIDReq)
jobID, err := strconv.ParseInt(httpReq.GetJobID(), 10, 64)
if err != nil {
paramErr := merr.WrapErrParameterInvalid("int64 jobId", httpReq.GetJobID(), err.Error())
HTTPAbortReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(paramErr), HTTPReturnMessage: paramErr.Error()})
return nil, paramErr
}
req := &milvuspb.GetRestoreSnapshotStateRequest{
Base: commonpbutil.NewMsgBase(),
JobId: jobID,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/GetRestoreSnapshotState", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.GetRestoreSnapshotState(reqCtx, req.(*milvuspb.GetRestoreSnapshotStateRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: restoreSnapshotJobToREST(resp.(*milvuspb.GetRestoreSnapshotStateResponse).GetInfo()),
})
}
return resp, err
}
func (h *HandlersV2) listRestoreSnapshotJobs(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*OptionalCollectionNameReq)
req := &milvuspb.ListRestoreSnapshotJobsRequest{
Base: commonpbutil.NewMsgBase(),
DbName: dbName,
CollectionName: httpReq.GetCollectionName(),
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/ListRestoreSnapshotJobs", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.ListRestoreSnapshotJobs(reqCtx, req.(*milvuspb.ListRestoreSnapshotJobsRequest))
})
if err == nil {
jobs := resp.(*milvuspb.ListRestoreSnapshotJobsResponse).GetJobs()
records := make([]gin.H, 0, len(jobs))
for _, info := range jobs {
records = append(records, restoreSnapshotJobToREST(info))
}
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: gin.H{"records": records},
})
}
return resp, err
}
func (h *HandlersV2) refreshExternalCollection(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*RefreshExternalCollectionReq)
req := &milvuspb.RefreshExternalCollectionRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
ExternalSource: httpReq.ExternalSource,
ExternalSpec: httpReq.ExternalSpec,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/RefreshExternalCollection", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.RefreshExternalCollection(reqCtx, req.(*milvuspb.RefreshExternalCollectionRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: gin.H{"jobId": resp.(*milvuspb.RefreshExternalCollectionResponse).GetJobId()},
})
}
return resp, err
}
func (h *HandlersV2) commitImportJob(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
jobIDGetter := anyReq.(JobIDGetter)
jobID, err := strconv.ParseInt(jobIDGetter.GetJobID(), 10, 64)
if err != nil {
paramErr := merr.WrapErrParameterInvalid("int64 jobId", jobIDGetter.GetJobID(), err.Error())
HTTPAbortReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(paramErr), HTTPReturnMessage: paramErr.Error()})
return nil, paramErr
}
if err := h.checkImportJobAuth(ctx, c, dbName, jobIDGetter.GetJobID()); err != nil {
return nil, err
}
ctx = c.Request.Context()
req := &datapb.CommitImportRequest{
Base: commonpbutil.NewMsgBase(),
JobId: jobID,
}
c.Set(ContextRequest, req)
// Internal DataCoord-only RPC; not exposed on MilvusService. Use the real
// proto package in the trace/metric label so it reflects the actual method.
resp, err := wrapperProxy(ctx, c, req, false, false,
"/milvus.proto.data.DataCoord/CommitImport",
func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.CommitImport(reqCtx, req.(*datapb.CommitImportRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: gin.H{}})
}
return resp, err
}
func (h *HandlersV2) getRefreshExternalCollectionProgress(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*RefreshExternalCollectionProgressReq)
req := &milvuspb.GetRefreshExternalCollectionProgressRequest{
JobId: httpReq.JobID,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxy(ctx, c, req, false, false, "/milvus.proto.milvus.MilvusService/GetRefreshExternalCollectionProgress", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.GetRefreshExternalCollectionProgress(reqCtx, req.(*milvuspb.GetRefreshExternalCollectionProgressRequest))
})
if err == nil {
jobInfo := resp.(*milvuspb.GetRefreshExternalCollectionProgressResponse).GetJobInfo()
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: refreshExternalCollectionJobInfoToMap(jobInfo)})
}
return resp, err
}
func (h *HandlersV2) abortImportJob(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
jobIDGetter := anyReq.(JobIDGetter)
jobID, err := strconv.ParseInt(jobIDGetter.GetJobID(), 10, 64)
if err != nil {
paramErr := merr.WrapErrParameterInvalid("int64 jobId", jobIDGetter.GetJobID(), err.Error())
HTTPAbortReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(paramErr), HTTPReturnMessage: paramErr.Error()})
return nil, paramErr
}
if err := h.checkImportJobAuth(ctx, c, dbName, jobIDGetter.GetJobID()); err != nil {
return nil, err
}
ctx = c.Request.Context()
req := &datapb.AbortImportRequest{
Base: commonpbutil.NewMsgBase(),
JobId: jobID,
}
c.Set(ContextRequest, req)
// Internal DataCoord-only RPC; not exposed on MilvusService.
resp, err := wrapperProxy(ctx, c, req, false, false,
"/milvus.proto.data.DataCoord/AbortImport",
func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.AbortImport(reqCtx, req.(*datapb.AbortImportRequest))
})
if err == nil {
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: gin.H{}})
}
return resp, err
}
func (h *HandlersV2) listRefreshExternalCollectionJobs(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*OptionalCollectionNameReq)
req := &milvuspb.ListRefreshExternalCollectionJobsRequest{
DbName: dbName,
CollectionName: httpReq.CollectionName,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxy(ctx, c, req, false, false, "/milvus.proto.milvus.MilvusService/ListRefreshExternalCollectionJobs", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.ListRefreshExternalCollectionJobs(reqCtx, req.(*milvuspb.ListRefreshExternalCollectionJobsRequest))
})
if err == nil {
response := resp.(*milvuspb.ListRefreshExternalCollectionJobsResponse)
records := make([]map[string]interface{}, 0, len(response.GetJobs()))
for _, jobInfo := range response.GetJobs() {
records = append(records, refreshExternalCollectionJobInfoToMap(jobInfo))
}
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: gin.H{"records": records}})
}
return resp, err
}
func refreshExternalCollectionJobInfoToMap(jobInfo *milvuspb.RefreshExternalCollectionJobInfo) map[string]interface{} {
detail := make(map[string]interface{})
if jobInfo == nil {
return detail
}
detail["jobId"] = jobInfo.GetJobId()
detail["collectionName"] = jobInfo.GetCollectionName()
detail["state"] = jobInfo.GetState().String()
detail["progress"] = jobInfo.GetProgress()
detail["externalSource"] = jobInfo.GetExternalSource()
detail["externalSpec"] = jobInfo.GetExternalSpec()
detail["startTime"] = jobInfo.GetStartTime()
detail["endTime"] = jobInfo.GetEndTime()
if jobInfo.GetReason() != "" {
detail["reason"] = jobInfo.GetReason()
}
return detail
}
func (h *HandlersV2) GetCollectionSchema(ctx context.Context, c *gin.Context, dbName, collectionName string) (*schemapb.CollectionSchema, error) {
collSchema, err := proxy.GetCachedCollectionSchema(ctx, dbName, collectionName)
if err == nil {
return collSchema.CollectionSchema, nil
}
descReq := &milvuspb.DescribeCollectionRequest{
DbName: dbName,
CollectionName: collectionName,
}
descResp, err := wrapperProxy(ctx, c, descReq, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/DescribeCollection", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.DescribeCollection(reqCtx, req.(*milvuspb.DescribeCollectionRequest))
})
if err != nil {
return nil, err
}
response, _ := descResp.(*milvuspb.DescribeCollectionResponse)
return response.Schema, nil
}
func (h *HandlersV2) getSegmentsInfo(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*GetSegmentsInfoReq)
req := &internalpb.GetSegmentsInfoRequest{
DbName: httpReq.GetDbName(),
CollectionID: httpReq.GetCollectionID(),
SegmentIDs: httpReq.GetSegmentIDs(),
}
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/GetSegmentsInfo", func(reqCtx context.Context, req any) (any, error) {
return h.proxy.GetSegmentsInfo(reqCtx, req.(*internalpb.GetSegmentsInfoRequest))
})
getLogs := func(binlogs []*internalpb.FieldBinlog) []interface{} {
logIDs := make([]interface{}, 0, len(binlogs))
for _, binlog := range binlogs {
details := make(map[string]interface{})
details["fieldID"] = binlog.GetFieldID()
details["logIDs"] = binlog.GetLogIDs()
logIDs = append(logIDs, details)
}
return logIDs
}
if err == nil {
response := resp.(*internalpb.GetSegmentsInfoResponse)
returnData := make(map[string]interface{})
infos := make([]map[string]interface{}, 0)
for _, segInfo := range response.GetSegmentInfos() {
info := make(map[string]interface{})
info["segmentID"] = segInfo.GetSegmentID()
info["collectionID"] = segInfo.GetCollectionID()
info["partitionID"] = segInfo.GetPartitionID()
info["vChannel"] = segInfo.GetVChannel()
info["numRows"] = segInfo.GetNumRows()
info["state"] = segInfo.GetState()
info["level"] = segInfo.GetLevel()
info["isSorted"] = segInfo.GetIsSorted()
info["insertLogs"] = getLogs(segInfo.GetInsertLogs())
info["deltaLogs"] = getLogs(segInfo.GetDeltaLogs())
info["statsLogs"] = getLogs(segInfo.GetStatsLogs())
infos = append(infos, info)
}
returnData["segmentInfos"] = infos
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: returnData})
}
return resp, err
}
func (h *HandlersV2) getQuotaMetrics(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
req := &internalpb.GetQuotaMetricsRequest{}
resp, err := wrapperProxy(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/GetQuotaMetrics", func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.GetQuotaMetrics(reqCtx, req.(*internalpb.GetQuotaMetricsRequest))
})
if err == nil {
response := resp.(*internalpb.GetQuotaMetricsResponse)
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: response.GetMetricsInfo()})
}
return resp, err
}
func (h *HandlersV2) runAnalyzer(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
httpReq := anyReq.(*RunAnalyzerReq)
// Convert text strings to byte slices
placeholder := make([][]byte, len(httpReq.Text))
for i, text := range httpReq.Text {
placeholder[i] = []byte(text)
}
req := &milvuspb.RunAnalyzerRequest{
DbName: dbName,
AnalyzerParams: httpReq.AnalyzerParams,
Placeholder: placeholder,
WithDetail: httpReq.WithDetail,
WithHash: httpReq.WithHash,
CollectionName: httpReq.CollectionName,
FieldName: httpReq.FieldName,
AnalyzerNames: httpReq.AnalyzerNames,
}
c.Set(ContextRequest, req)
resp, err := wrapperProxyWithLimit(ctx, c, req, h.checkAuth, false, "/milvus.proto.milvus.MilvusService/RunAnalyzer", true, h.proxy, func(reqCtx context.Context, req any) (interface{}, error) {
return h.proxy.RunAnalyzer(reqCtx, req.(*milvuspb.RunAnalyzerRequest))
})
if err == nil {
analyzerResp := resp.(*milvuspb.RunAnalyzerResponse)
// Convert response to HTTP-friendly format
results := make([]gin.H, len(analyzerResp.Results))
for i, result := range analyzerResp.Results {
tokens := make([]gin.H, len(result.Tokens))
for j, token := range result.Tokens {
tokenData := gin.H{
"token": token.Token,
}
if httpReq.WithDetail {
tokenData["startOffset"] = token.StartOffset
tokenData["endOffset"] = token.EndOffset
tokenData["position"] = token.Position
tokenData["positionLength"] = token.PositionLength
}
if httpReq.WithHash {
tokenData["hash"] = token.Hash
}
tokens[j] = tokenData
}
results[i] = gin.H{
"tokens": tokens,
}
}
HTTPReturn(c, http.StatusOK, gin.H{
HTTPReturnCode: merr.Code(nil),
HTTPReturnData: gin.H{
"results": results,
},
})
}
return resp, err
}