mirror of https://github.com/milvus-io/milvus.git
2615 lines
115 KiB
Go
2615 lines
115 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"
|
|
|
|
"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"
|
|
"go.opentelemetry.io/otel/trace"
|
|
"go.uber.org/zap"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
"github.com/milvus-io/milvus-proto/go-api/v2/commonpb"
|
|
"github.com/milvus-io/milvus-proto/go-api/v2/hook"
|
|
"github.com/milvus-io/milvus-proto/go-api/v2/milvuspb"
|
|
"github.com/milvus-io/milvus-proto/go-api/v2/schemapb"
|
|
"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/pkg/v2/common"
|
|
"github.com/milvus-io/milvus/pkg/v2/log"
|
|
"github.com/milvus-io/milvus/pkg/v2/metrics"
|
|
"github.com/milvus-io/milvus/pkg/v2/proto/internalpb"
|
|
"github.com/milvus-io/milvus/pkg/v2/util/crypto"
|
|
"github.com/milvus-io/milvus/pkg/v2/util/funcutil"
|
|
"github.com/milvus-io/milvus/pkg/v2/util/merr"
|
|
"github.com/milvus-io/milvus/pkg/v2/util/requestutil"
|
|
"github.com/milvus-io/milvus/pkg/v2/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(),
|
|
}
|
|
}
|
|
|
|
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+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+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))))
|
|
|
|
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+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))))
|
|
|
|
// 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))))
|
|
}
|
|
|
|
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 {
|
|
log.Warn("high level restful api, read parameters from request body fail", zap.Error(err),
|
|
zap.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 := ""
|
|
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
|
|
}
|
|
}
|
|
}
|
|
innerCtx := gCtx.Request.Context()
|
|
ctx, span := otel.Tracer(typeutil.ProxyRole).Start(innerCtx, gCtx.Request.URL.Path)
|
|
defer span.End()
|
|
username, _ := gCtx.Get(ContextUsername)
|
|
ctx = proxy.NewContextWithMetadata(ctx, username.(string), dbName)
|
|
traceID := span.SpanContext().TraceID().String()
|
|
ctx = log.WithTraceID(ctx, traceID)
|
|
gCtx.Keys["traceID"] = traceID
|
|
log.Ctx(ctx).Debug("high level restful api, read parameters from request body, then start to handle.",
|
|
zap.Any("url", gCtx.Request.URL.Path))
|
|
v2(ctx, gCtx, req, dbName)
|
|
}
|
|
}
|
|
|
|
// 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, ctx.Request.ContentLength)
|
|
ctx.Request = ctx.Request.WithContext(h)
|
|
handler(ctx)
|
|
metrics.RecordRestfulMetrics(h, int64(ctx.Writer.Size()), observeOutbound)
|
|
}
|
|
}
|
|
|
|
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)
|
|
log.Ctx(ctx).Info("trace info: simple", fields...)
|
|
case 2: // detail info
|
|
fields := proxy.GetRequestBaseInfo(ctx, req, &grpc.UnaryServerInfo{
|
|
FullMethod: c.Request.URL.Path,
|
|
}, true)
|
|
fields = append(fields, proxy.GetRequestFieldWithoutSensitiveInfo(req))
|
|
log.Ctx(ctx).Info("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, proxy.GetRequestFieldWithoutSensitiveInfo(req))
|
|
log.Ctx(ctx).Info("trace info: all request", fields...)
|
|
}
|
|
resp, err := v2(ctx, c, req, dbName)
|
|
if proxy.Params.CommonCfg.TraceLogMode.GetAsInt() > 2 {
|
|
if err != nil {
|
|
log.Ctx(ctx).Info("trace info: all, error", zap.Error(err))
|
|
} else {
|
|
log.Ctx(ctx).Info("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().ReportRefused(ctx, req, WrapErrorToResponse(merr.ErrNeedAuthenticate), nil, c.FullPath())
|
|
return merr.ErrNeedAuthenticate
|
|
}
|
|
_, 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().ReportRefused(ctx, req, WrapErrorToResponse(authErr), nil, c.FullPath())
|
|
return authErr
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
if checkLimit {
|
|
_, err := CheckLimiter(ctx, req, pxy)
|
|
if err != nil {
|
|
log.Warn("high level restful api, fail to check limiter", zap.Error(err), zap.String("method", fullMethod))
|
|
hookutil.GetExtension().ReportRefused(ctx, req, WrapErrorToResponse(merr.ErrHTTPRateLimit), nil, ginCtx.FullPath())
|
|
HTTPAbortReturn(ginCtx, http.StatusOK, gin.H{
|
|
HTTPReturnCode: merr.Code(merr.ErrHTTPRateLimit),
|
|
HTTPReturnMessage: merr.ErrHTTPRateLimit.Error() + ", error: " + err.Error(),
|
|
})
|
|
return nil, RestRequestInterceptorErr
|
|
}
|
|
}
|
|
log.Ctx(ctx).Debug("high level restful api, try to do a grpc call")
|
|
username, ok := ginCtx.Get(ContextUsername)
|
|
if !ok {
|
|
username = ""
|
|
}
|
|
|
|
response, err := proxy.HookInterceptor(context.WithValue(ctx, hook.GinParamsKey, ginCtx.Keys), req, username.(string), fullMethod, handler)
|
|
if err == nil {
|
|
status, ok := requestutil.GetStatusFromResponse(response)
|
|
if ok {
|
|
err = merr.Error(status)
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
log.Ctx(ctx).Warn("high level restful api, grpc call failed", zap.Error(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,
|
|
}
|
|
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)
|
|
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 {
|
|
log.Ctx(ctx).Warn("high level restful api, get primary field from collection schema fail", zap.Any("collection schema", coll.Schema), zap.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),
|
|
"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,
|
|
"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
|
|
}
|
|
if resp.(*milvuspb.GetLoadStateResponse).State == commonpb.LoadState_LoadStateNotExist {
|
|
err = merr.WrapErrCollectionNotFound(req.CollectionName)
|
|
HTTPReturn(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(err), HTTPReturnMessage: err.Error()})
|
|
return resp, err
|
|
} else if resp.(*milvuspb.GetLoadStateResponse).State == 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) 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) 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 {
|
|
HTTPReturn(c, http.StatusOK, wrapperReturnDefault())
|
|
}
|
|
return resp, err
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// 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{},
|
|
UseDefaultConsistency: true,
|
|
}
|
|
req.ExprTemplateValues = generateExpressionTemplate(httpReq.ExprParams)
|
|
c.Set(ContextRequest, req)
|
|
if httpReq.Offset > 0 {
|
|
req.QueryParams = append(req.QueryParams, &commonpb.KeyValuePair{Key: ParamOffset, Value: strconv.FormatInt(int64(httpReq.Offset), 10)})
|
|
}
|
|
if httpReq.Limit > 0 && !matchCountRule(httpReq.OutputFields) {
|
|
req.QueryParams = append(req.QueryParams, &commonpb.KeyValuePair{Key: ParamLimit, Value: strconv.FormatInt(int64(httpReq.Limit), 10)})
|
|
}
|
|
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)
|
|
if err != nil {
|
|
log.Ctx(ctx).Warn("high level restful api, fail to deal with query result", zap.Any("response", resp), zap.Error(err))
|
|
HTTPReturn(c, http.StatusOK, gin.H{
|
|
HTTPReturnCode: merr.Code(merr.ErrInvalidSearchResult),
|
|
HTTPReturnMessage: merr.ErrInvalidSearchResult.Error() + ", error: " + err.Error(),
|
|
})
|
|
} 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,
|
|
UseDefaultConsistency: true,
|
|
}
|
|
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)
|
|
if err != nil {
|
|
log.Ctx(ctx).Warn("high level restful api, fail to deal with get result", zap.Any("response", resp), zap.Error(err))
|
|
HTTPReturn(c, http.StatusOK, gin.H{
|
|
HTTPReturnCode: merr.Code(merr.ErrInvalidSearchResult),
|
|
HTTPReturnMessage: merr.ErrInvalidSearchResult.Error() + ", error: " + err.Error(),
|
|
})
|
|
} 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
|
|
err, httpReq.Data, validDataMap = checkAndSetData(string(body.([]byte)), collSchema)
|
|
if err != nil {
|
|
log.Ctx(ctx).Warn("high level restful api, fail to deal with insert data", zap.Error(err), zap.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)
|
|
if err != nil {
|
|
log.Ctx(ctx).Warn("high level restful api, fail to deal with insert data", zap.Any("data", httpReq.Data), zap.Error(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,
|
|
// 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
|
|
err, httpReq.Data, validDataMap = checkAndSetData(string(body.([]byte)), collSchema)
|
|
if err != nil {
|
|
log.Ctx(ctx).Warn("high level restful api, fail to deal with upsert data", zap.Any("body", body), zap.Error(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)
|
|
if err != nil {
|
|
log.Ctx(ctx).Warn("high level restful api, fail to deal with upsert data", zap.Any("data", httpReq.Data), zap.Error(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())
|
|
switch upsertResp.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{"upsertCount": upsertResp.UpsertCnt, "upsertIds": upsertResp.IDs.IdField.(*schemapb.IDs_IntId).IntId.Data},
|
|
HTTPReturnCost: cost,
|
|
})
|
|
} 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:
|
|
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, errors.New("search without annsField, but already found multiple vector fields: [" + fieldName + ", " + field.Name + ",,,]")
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
for _, field := range collSchema.Fields {
|
|
if field.Name == fieldName && typeutil.IsVectorType(field.DataType) {
|
|
vectorField = field
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if vectorField == nil {
|
|
return nil, errors.New("cannot find a vector field named: " + fieldName)
|
|
}
|
|
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 {
|
|
// TODO: currently only BM25 & text embedding function is supported, thus guarantees one input field to one output field
|
|
if function.OutputFieldNames[0] == vectorField.Name {
|
|
dataType = schemapb.DataType_VarChar
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 {
|
|
log.Ctx(ctx).Warn("high level restful api, search with consistency_level invalid", zap.Error(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
|
|
}
|
|
|
|
searchParams, err := generateSearchParams(httpReq.SearchParams)
|
|
if err != nil {
|
|
log.Ctx(ctx).Warn("high level restful api, generate SearchParams failed", zap.Error(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: ParamOffset, 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)})
|
|
}
|
|
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 {
|
|
log.Ctx(ctx).Warn("high level restful api, search with vector invalid", zap.Error(err))
|
|
HTTPAbortReturn(c, http.StatusOK, gin.H{
|
|
HTTPReturnCode: merr.Code(merr.ErrIncorrectParameterFormat),
|
|
HTTPReturnMessage: merr.ErrIncorrectParameterFormat.Error() + ", error: " + err.Error(),
|
|
})
|
|
return nil, err
|
|
}
|
|
req.SearchParams = searchParams
|
|
req.PlaceholderGroup = placeholderGroup
|
|
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())
|
|
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)
|
|
if err != nil {
|
|
log.Ctx(ctx).Warn("high level restful api, fail to deal with search result", zap.Any("result", searchResp.Results), zap.Error(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 {
|
|
HTTPReturnStream(c, http.StatusOK, gin.H{HTTPReturnCode: merr.Code(nil), HTTPReturnData: outputData, HTTPReturnCost: cost, HTTPReturnRecalls: searchResp.Results.Recalls, HTTPReturnTopks: searchResp.Results.Topks})
|
|
} 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,
|
|
Requests: []*milvuspb.SearchRequest{},
|
|
OutputFields: httpReq.OutputFields,
|
|
}
|
|
var err error
|
|
req.ConsistencyLevel, req.UseDefaultConsistency, err = convertConsistencyLevel(httpReq.ConsistencyLevel)
|
|
if err != nil {
|
|
log.Ctx(ctx).Warn("high level restful api, search with consistency_level invalid", zap.Error(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
|
|
}
|
|
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 {
|
|
log.Ctx(ctx).Warn("high level restful api, generate SearchParams failed", zap.Error(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: ParamOffset, 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 {
|
|
log.Ctx(ctx).Warn("high level restful api, search with vector invalid", zap.Error(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,
|
|
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)
|
|
req.RankParams = []*commonpb.KeyValuePair{
|
|
{Key: proxy.RankTypeKey, Value: httpReq.Rerank.Strategy},
|
|
{Key: proxy.RankParamsKey, Value: string(bs)},
|
|
{Key: ParamLimit, Value: strconv.FormatInt(int64(httpReq.Limit), 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)})
|
|
}
|
|
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())
|
|
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)
|
|
if err != nil {
|
|
log.Ctx(ctx).Warn("high level restful api, fail to deal with search result", zap.Any("result", searchResp.Results), zap.Error(err))
|
|
HTTPReturn(c, http.StatusOK, gin.H{
|
|
HTTPReturnCode: merr.Code(merr.ErrInvalidSearchResult),
|
|
HTTPReturnMessage: merr.ErrInvalidSearchResult.Error() + ", error: " + err.Error(),
|
|
})
|
|
} 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) 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)
|
|
|
|
var schema []byte
|
|
var err error
|
|
fieldNames := map[string]bool{}
|
|
partitionsNum := int64(-1)
|
|
if len(httpReq.Schema.Fields) == 0 {
|
|
if len(httpReq.Schema.Functions) > 0 {
|
|
err := merr.WrapErrParameterInvalid("schema", "functions",
|
|
"functions are not supported for quickly create collection")
|
|
log.Ctx(ctx).Warn("high level restful api, quickly create collection fail", zap.Error(err), zap.Any("request", anyReq))
|
|
HTTPAbortReturn(c, http.StatusOK, gin.H{
|
|
HTTPReturnCode: merr.Code(err),
|
|
HTTPReturnMessage: err.Error(),
|
|
})
|
|
return nil, err
|
|
}
|
|
|
|
if httpReq.Dimension == 0 {
|
|
err := merr.WrapErrParameterInvalid("collectionName & dimension", "collectionName",
|
|
"dimension is required for quickly create collection(default metric type: "+DefaultMetricType+")")
|
|
log.Ctx(ctx).Warn("high level restful api, quickly create collection fail", zap.Error(err), zap.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")
|
|
log.Ctx(ctx).Warn("high level restful api, quickly create collection fail", zap.Error(err), zap.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 {
|
|
log.Ctx(ctx).Warn("high level restful api, parse enableDynamicField fail", zap.Error(err), zap.Any("request", anyReq))
|
|
HTTPAbortReturn(c, http.StatusOK, gin.H{
|
|
HTTPReturnCode: merr.Code(err),
|
|
HTTPReturnMessage: "parse enableDynamicField fail, err:" + err.Error(),
|
|
})
|
|
return nil, err
|
|
}
|
|
}
|
|
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: schemapb.DataType_FloatVector,
|
|
TypeParams: []*commonpb.KeyValuePair{
|
|
{
|
|
Key: Dim,
|
|
Value: strconv.FormatInt(int64(httpReq.Dimension), 10),
|
|
},
|
|
},
|
|
AutoID: DisableAutoID,
|
|
},
|
|
},
|
|
EnableDynamicField: enableDynamic,
|
|
Description: httpReq.Description,
|
|
})
|
|
} else {
|
|
collSchema := schemapb.CollectionSchema{
|
|
Name: httpReq.CollectionName,
|
|
AutoID: httpReq.Schema.AutoId,
|
|
Fields: []*schemapb.FieldSchema{},
|
|
Functions: []*schemapb.FunctionSchema{},
|
|
EnableDynamicField: httpReq.Schema.EnableDynamicField,
|
|
Description: httpReq.Description,
|
|
}
|
|
|
|
allOutputFields := []string{}
|
|
|
|
for _, function := range httpReq.Schema.Functions {
|
|
functionTypeValue, ok := schemapb.FunctionType_value[function.FunctionType]
|
|
if !ok {
|
|
log.Ctx(ctx).Warn("function's data type is invalid(case sensitive).", zap.Any("function.DataType", function.FunctionType), zap.Any("function", function))
|
|
err := merr.WrapErrParameterInvalid("FunctionType", function.FunctionType, "function data type is invalid(case sensitive)")
|
|
HTTPAbortReturn(c, http.StatusOK, gin.H{
|
|
HTTPReturnCode: merr.Code(merr.ErrParameterInvalid),
|
|
HTTPReturnMessage: err.Error(),
|
|
})
|
|
return nil, err
|
|
}
|
|
functionType := schemapb.FunctionType(functionTypeValue)
|
|
description := function.Description
|
|
params := []*commonpb.KeyValuePair{}
|
|
for key, value := range function.Params {
|
|
params = append(params, &commonpb.KeyValuePair{Key: key, Value: fmt.Sprintf("%v", value)})
|
|
}
|
|
collSchema.Functions = append(collSchema.Functions, &schemapb.FunctionSchema{
|
|
Name: function.FunctionName,
|
|
Description: description,
|
|
Type: functionType,
|
|
InputFieldNames: function.InputFieldNames,
|
|
OutputFieldNames: function.OutputFieldNames,
|
|
Params: params,
|
|
})
|
|
allOutputFields = append(allOutputFields, function.OutputFieldNames...)
|
|
}
|
|
|
|
for _, field := range httpReq.Schema.Fields {
|
|
fieldDataType, ok := schemapb.DataType_value[field.DataType]
|
|
if !ok {
|
|
log.Ctx(ctx).Warn("field's data type is invalid(case sensitive).", zap.Any("fieldDataType", field.DataType), zap.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,
|
|
}
|
|
|
|
fieldSchema.DefaultValue, err = convertDefaultValue(field.DefaultValue, dataType)
|
|
if err != nil {
|
|
log.Ctx(ctx).Warn("convert defaultValue fail", zap.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 {
|
|
log.Ctx(ctx).Warn("element's data type is invalid(case sensitive).", zap.Any("elementDataType", field.ElementDataType), zap.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
|
|
}
|
|
schema, err = proto.Marshal(&collSchema)
|
|
}
|
|
if err != nil {
|
|
log.Ctx(ctx).Warn("high level restful api, marshal collection schema fail", zap.Error(err), zap.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 := commonpb.ConsistencyLevel_Bounded
|
|
if _, ok := httpReq.Params["consistencyLevel"]; ok {
|
|
if level, ok := commonpb.ConsistencyLevel_value[fmt.Sprintf("%s", httpReq.Params["consistencyLevel"])]; ok {
|
|
consistencyLevel = commonpb.ConsistencyLevel(level)
|
|
} else {
|
|
err := merr.WrapErrParameterInvalid("Strong, Session, Bounded, Eventually, Customized", httpReq.Params["consistencyLevel"],
|
|
"consistencyLevel can only be [Strong, Session, Bounded, Eventually, Customized], default: Bounded")
|
|
log.Ctx(ctx).Warn("high level restful api, create collection fail", zap.Error(err), zap.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
|
|
}
|
|
if _, ok := httpReq.Params["ttlSeconds"]; ok {
|
|
req.Properties = append(req.Properties, &commonpb.KeyValuePair{
|
|
Key: common.CollectionTTLConfigKey,
|
|
Value: fmt.Sprintf("%v", httpReq.Params["ttlSeconds"]),
|
|
})
|
|
}
|
|
if _, ok := httpReq.Params["partitionKeyIsolation"]; ok {
|
|
req.Properties = append(req.Properties, &commonpb.KeyValuePair{
|
|
Key: common.PartitionKeyIsolationKey,
|
|
Value: fmt.Sprintf("%v", httpReq.Params["partitionKeyIsolation"]),
|
|
})
|
|
}
|
|
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 {
|
|
if len(httpReq.MetricType) == 0 {
|
|
httpReq.MetricType = DefaultMetricType
|
|
}
|
|
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
|
|
log.Ctx(ctx).Warn("high level restful api, convertToExtraParams fail", zap.Error(err), zap.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)
|
|
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{}
|
|
for _, userRole := range resp.(*milvuspb.SelectUserResponse).Results {
|
|
if userRole.User.Name == userName {
|
|
for _, role := range userRole.Roles {
|
|
roleNames = append(roleNames, role.Name)
|
|
}
|
|
}
|
|
}
|
|
HTTPReturn(c, http.StatusOK, wrapperReturnList(roleNames))
|
|
}
|
|
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),
|
|
}
|
|
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,
|
|
OldPassword: crypto.Base64Encode(httpReq.Password),
|
|
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)
|
|
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})
|
|
}
|
|
return resp, err
|
|
}
|
|
|
|
func (h *HandlersV2) createRole(ctx context.Context, c *gin.Context, anyReq any, dbName string) (interface{}, error) {
|
|
getter, _ := anyReq.(RoleNameGetter)
|
|
req := &milvuspb.CreateRoleRequest{
|
|
Entity: &milvuspb.RoleEntity{Name: getter.GetRoleName()},
|
|
}
|
|
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) 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/OperatePrivilege", 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)
|
|
req := &milvuspb.DescribeIndexRequest{
|
|
DbName: dbName,
|
|
CollectionName: collectionGetter.GetCollectionName(),
|
|
IndexName: indexGetter.GetIndexName(),
|
|
}
|
|
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 := ""
|
|
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
|
|
}
|
|
}
|
|
indexInfo := map[string]any{
|
|
HTTPIndexName: indexDescription.IndexName,
|
|
HTTPIndexField: indexDescription.FieldName,
|
|
HTTPReturnIndexType: indexType,
|
|
HTTPReturnIndexMetricType: metricType,
|
|
HTTPMmapEnabledKey: mmapEnabled,
|
|
HTTPIndexOffsetCacheEnabledKey: indexOffsetCacheEnabled,
|
|
HTTPReturnIndexTotalRows: indexDescription.TotalRows,
|
|
HTTPReturnIndexPendingRows: indexDescription.PendingIndexRows,
|
|
HTTPReturnIndexIndexedRows: indexDescription.IndexedRows,
|
|
HTTPReturnIndexState: indexDescription.State.String(),
|
|
HTTPReturnIndexFailReason: indexDescription.IndexStateFailReason,
|
|
}
|
|
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
|
|
log.Ctx(ctx).Warn("high level restful api, convertToExtraParams fail", zap.Error(err), zap.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)
|
|
|
|
if h.checkAuth {
|
|
err := checkAuthorizationV2(ctx, c, false, &milvuspb.ListImportsAuthPlaceholder{
|
|
DbName: dbName,
|
|
CollectionName: collectionName,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
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)
|
|
for i, jobID := range response.GetJobIDs() {
|
|
jobDetail := make(map[string]interface{})
|
|
jobDetail["jobId"] = jobID
|
|
jobDetail["collectionName"] = response.GetCollectionNames()[i]
|
|
jobDetail["state"] = response.GetStates()[i].String()
|
|
jobDetail["progress"] = response.GetProgresses()[i]
|
|
reason := response.GetReasons()[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
|
|
}
|
|
}
|
|
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)
|
|
req := &internalpb.GetImportProgressRequest{
|
|
DbName: dbName,
|
|
JobID: jobIDGetter.GetJobID(),
|
|
}
|
|
c.Set(ContextRequest, req)
|
|
|
|
if h.checkAuth {
|
|
err := checkAuthorizationV2(ctx, c, false, &milvuspb.GetImportProgressAuthPlaceholder{
|
|
DbName: dbName,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
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 {
|
|
response := resp.(*internalpb.GetImportProgressResponse)
|
|
returnData := make(map[string]interface{})
|
|
returnData["jobId"] = jobIDGetter.GetJobID()
|
|
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 resp, err
|
|
}
|
|
|
|
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
|
|
}
|