498b235461
Build and test / Build and test AMD64 Ubuntu 22.04 (push) Failing after 0s
Publish Builder / amazonlinux2023 (push) Failing after 1s
Build and test / UT for Cpp (push) Waiting to run
Build and test / UT for Go (push) Has been skipped
Publish KRTE Images / KRTE (push) Failing after 1s
Build and test / Integration Test (push) Has been skipped
Build and test / Upload Code Coverage (push) Has been skipped
Publish Builder / rockylinux9 (push) Failing after 1s
Publish Builder / ubuntu22.04 (push) Failing after 0s
Publish Builder / ubuntu24.04 (push) Failing after 0s
Publish Gpu Builder / publish-gpu-builder (push) Failing after 1s
Publish Test Images / PyTest (push) Failing after 0s
970 lines
30 KiB
Go
970 lines
30 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 common
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"math/bits"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/samber/lo"
|
|
"github.com/twpayne/go-geom/encoding/wkb"
|
|
"github.com/twpayne/go-geom/encoding/wkbcommon"
|
|
"github.com/twpayne/go-geom/encoding/wkt"
|
|
|
|
"github.com/milvus-io/milvus-proto/go-api/v3/commonpb"
|
|
"github.com/milvus-io/milvus-proto/go-api/v3/schemapb"
|
|
"github.com/milvus-io/milvus/pkg/v3/util/merr"
|
|
)
|
|
|
|
// system field id:
|
|
// 0: unique row id
|
|
// 1: timestamp
|
|
// 100: first user field id
|
|
// 101: second user field id
|
|
// 102: ...
|
|
|
|
const (
|
|
// StartOfUserFieldID represents the starting ID of the user-defined field
|
|
StartOfUserFieldID = 100
|
|
|
|
// StartOfUserFunctionID represents the starting ID of the user-defined function
|
|
StartOfUserFunctionID = 100
|
|
// RowIDField is the ID of the RowID field reserved by the system
|
|
RowIDField = 0
|
|
|
|
// TimeStampField is the ID of the Timestamp field reserved by the system
|
|
TimeStampField = 1
|
|
|
|
// RowIDFieldName defines the name of the RowID field
|
|
RowIDFieldName = "RowID"
|
|
|
|
// TimeStampFieldName defines the name of the Timestamp field
|
|
TimeStampFieldName = "Timestamp"
|
|
|
|
// NamespaceFieldName defines the name of the Namespace field
|
|
NamespaceFieldName = "$namespace_id"
|
|
|
|
NamespaceModeKey = "namespace.mode"
|
|
NamespaceModePartitionKey = "partition_key"
|
|
NamespaceModePartition = "partition"
|
|
ValidNamespaceModes = NamespaceModePartitionKey + ", " + NamespaceModePartition
|
|
|
|
// MetaFieldName is the field name of dynamic schema
|
|
MetaFieldName = "$meta"
|
|
|
|
// VirtualPKFieldName is the field name of virtual primary key for external collections
|
|
// Virtual PK format: (segmentID << 32) | offset
|
|
VirtualPKFieldName = "__virtual_pk__"
|
|
|
|
// DefaultShardsNum defines the default number of shards when creating a collection
|
|
DefaultShardsNum = int32(1)
|
|
|
|
// DefaultPartitionsWithPartitionKey defines the default number of partitions when use partition key
|
|
DefaultPartitionsWithPartitionKey = int64(16)
|
|
|
|
// InvalidPartitionID indicates that the partition is not specified. It will be set when the partitionName is empty
|
|
InvalidPartitionID = int64(-1)
|
|
|
|
// AllPartitionsID indicates data applies to all partitions.
|
|
AllPartitionsID = int64(-1)
|
|
|
|
// PkFilter values for SearchRequest/RetrieveRequest.PkFilter field.
|
|
// Proxy sets this to let delegator skip plan unmarshal when no PK predicate exists.
|
|
PkFilterNotChecked = int32(0) // old proxy or not checked (backward compat)
|
|
PkFilterHasPkFilter = int32(1) // plan contains optimizable PK predicate
|
|
PkFilterNoPkFilter = int32(2) // plan has no PK predicate, skip segment filter
|
|
|
|
// InvalidFieldID indicates that the field does not exist . It will be set when the field is not found.
|
|
InvalidFieldID = int64(-1)
|
|
|
|
// NotRegisteredID means node is not registered into etcd.
|
|
NotRegisteredID = int64(-1)
|
|
|
|
// InvalidNodeID indicates that node is not valid in querycoord replica or shard cluster.
|
|
InvalidNodeID = int64(-1)
|
|
|
|
SystemFieldsNum = int64(2)
|
|
)
|
|
|
|
const (
|
|
// Scalar index engine version tracks the *capability* of a Milvus node
|
|
// (which index types / features it supports). It is reported by QueryNodes
|
|
// in their session and aggregated by datacoord so that newly built indexes
|
|
// are clamped to a version every node in the cluster can load — this is
|
|
// what makes rolling upgrades safe.
|
|
//
|
|
// Engine version is distinct from the on-disk file format version
|
|
// (see MILVUS_V3_FORMAT_VERSION in IndexEntryWriter.h). Multiple engine
|
|
// versions can share the same file format; bumping the engine version
|
|
// does not necessarily imply a format change.
|
|
//
|
|
// Scalar index engine version 3:
|
|
// - Packed single-file index layout (file format v3) becomes the default
|
|
// - HYBRID/AUTOINDEX high-cardinality scalar indexes switched from
|
|
// INVERTED to STL_SORT
|
|
//
|
|
// Scalar index engine version 4:
|
|
// - JSON path index supports STL_SORT / BITMAP / HYBRID (in addition to
|
|
// the existing INVERTED / NGRAM)
|
|
// - On-disk file format is unchanged from v3
|
|
MinimalScalarIndexEngineVersion = int32(0)
|
|
CurrentScalarIndexEngineVersion = int32(4)
|
|
MaximumScalarIndexEngineVersion = int32(4)
|
|
|
|
// MinScalarIndexVersionForJsonPathMultiType is the minimum scalar index
|
|
// engine version that supports STL_SORT / BITMAP / HYBRID on JSON fields.
|
|
// Below this version, only INVERTED (and NGRAM for VARCHAR) are allowed.
|
|
MinScalarIndexVersionForJsonPathMultiType = int32(4) //nolint:revive // intentionally "Json" not "JSON" to match JsonCastType / JsonPathKey naming
|
|
)
|
|
|
|
// ClampScalarIndexVersion clamps the given scalar index version to MaximumScalarIndexEngineVersion.
|
|
// Used by DataNode to ensure the version written back to metadata does not exceed
|
|
// what the cluster can handle.
|
|
func ClampScalarIndexVersion(v int32) int32 {
|
|
if v > MaximumScalarIndexEngineVersion {
|
|
return MaximumScalarIndexEngineVersion
|
|
}
|
|
return v
|
|
}
|
|
|
|
const DefaultTimezone = "UTC"
|
|
|
|
// Endian is type alias of binary.LittleEndian.
|
|
// Milvus uses little endian by default.
|
|
var Endian = binary.LittleEndian
|
|
|
|
const (
|
|
// SegmentInsertLogPath storage path const for segment insert binlog.
|
|
SegmentInsertLogPath = `insert_log`
|
|
|
|
// SegmentDeltaLogPath storage path const for segment delta log.
|
|
SegmentDeltaLogPath = `delta_log`
|
|
|
|
// SegmentStatslogPath storage path const for segment stats log.
|
|
SegmentStatslogPath = `stats_log`
|
|
|
|
// SegmentIndexV0Path storage path const for legacy build-rooted segment index files.
|
|
SegmentIndexV0Path = `index_files`
|
|
|
|
// SegmentIndexV1Path storage path const for collection-rooted segment index files.
|
|
SegmentIndexV1Path = `index_v1`
|
|
|
|
// SegmentBm25LogPath storage path const for bm25 statistic
|
|
SegmentBm25LogPath = `bm25_stats`
|
|
|
|
// PartitionStatsPath storage path const for partition stats files
|
|
PartitionStatsPath = `part_stats`
|
|
|
|
// AnalyzeStatsPath storage path const for analyze.
|
|
AnalyzeStatsPath = `analyze_stats`
|
|
OffsetMapping = `offset_mapping`
|
|
Centroids = "centroids"
|
|
|
|
// TextIndexPath storage path const for text index
|
|
TextIndexPath = "text_log"
|
|
|
|
// JSONIndexPath storage path const for json index
|
|
JSONIndexPath = "json_key_index_log"
|
|
|
|
// JSONStatsPath storage path const for json stats
|
|
JSONStatsPath = "json_stats"
|
|
|
|
DefaultResourceGroupName = "__default_resource_group"
|
|
)
|
|
|
|
const (
|
|
// Version 3: metadata moved to separate meta.json file (instead of parquet metadata)
|
|
JSONStatsDataFormatVersion = 3
|
|
)
|
|
|
|
// Search, Index parameter keys
|
|
const (
|
|
TopKKey = "topk"
|
|
SearchParamKey = "search_param"
|
|
SegmentNumKey = "segment_num"
|
|
WithFilterKey = "with_filter"
|
|
DataTypeKey = "data_type"
|
|
ChannelNumKey = "channel_num"
|
|
WithOptimizeKey = "with_optimize"
|
|
CollectionKey = "collection"
|
|
RecallEvalKey = "recall_eval"
|
|
|
|
GlobalRefineKey = "global_refine"
|
|
SearchTopkRatioKey = "search_topk_ratio"
|
|
RefineTopkRatioKey = "refine_topk_ratio"
|
|
|
|
ParamsKey = "params"
|
|
IndexTypeKey = "index_type"
|
|
MetricTypeKey = "metric_type"
|
|
DimKey = "dim"
|
|
MaxLengthKey = "max_length"
|
|
MaxCapacityKey = "max_capacity"
|
|
|
|
DropRatioBuildKey = "drop_ratio_build"
|
|
|
|
IsSparseKey = "is_sparse"
|
|
AutoIndexName = "AUTOINDEX"
|
|
BitmapCardinalityLimitKey = "bitmap_cardinality_limit"
|
|
HybridLowCardinalityIndexTypeKey = "hybrid_low_cardinality_index_type"
|
|
HybridHighCardinalityIndexTypeKey = "hybrid_high_cardinality_index_type"
|
|
IgnoreGrowing = "ignore_growing"
|
|
ConsistencyLevel = "consistency_level"
|
|
HintsKey = "hints"
|
|
|
|
JSONCastTypeKey = "json_cast_type"
|
|
JSONPathKey = "json_path"
|
|
JSONCastFunctionKey = "json_cast_function"
|
|
|
|
SchemaVersionConsistencyProportionKey = "schema_version_consistency_proportion"
|
|
// SchemaVersionConsistentSegmentsKey and SchemaVersionTotalSegmentsKey are emitted by DataCoord
|
|
// GetCollectionStatistics to surface per-segment schema-version consistency progress.
|
|
SchemaVersionConsistentSegmentsKey = "schema_version_consistent_segments"
|
|
SchemaVersionTotalSegmentsKey = "schema_version_total_segments"
|
|
)
|
|
|
|
// expr query params
|
|
const (
|
|
ExprUseJSONStatsKey = "expr_use_json_stats"
|
|
)
|
|
|
|
// Doc-in-doc-out
|
|
const (
|
|
EnableAnalyzerKey = `enable_analyzer`
|
|
AnalyzerParamKey = `analyzer_params`
|
|
)
|
|
|
|
// Collection properties key
|
|
|
|
const (
|
|
CollectionTTLConfigKey = "collection.ttl.seconds"
|
|
CollectionAutoCompactionKey = "collection.autocompaction.enabled"
|
|
CollectionDescription = "collection.description"
|
|
CollectionExternalSource = "collection.external_source"
|
|
CollectionExternalSpec = "collection.external_spec"
|
|
CollectionTTLFieldKey = "ttl_field"
|
|
MaxTTLSeconds = 3155760000 // 100 years
|
|
|
|
// Deprecated: will be removed in the 3.0 after implementing ack sync up semantic.
|
|
CollectionOnTruncatingKey = "collection.on.truncating" // when collection is on truncating, forbid the compaction of current collection.
|
|
|
|
// Note:
|
|
// Function output fields cannot be included in inserted data.
|
|
// In particular, the `bm25` function output field is always disallowed
|
|
// and is not controlled by this option.
|
|
CollectionAllowInsertNonBM25FunctionOutputs = "collection.function.allowInsertNonBM25FunctionOutputs"
|
|
|
|
// rate limit
|
|
CollectionInsertRateMaxKey = "collection.insertRate.max.mb"
|
|
CollectionInsertRateMinKey = "collection.insertRate.min.mb"
|
|
CollectionDeleteRateMaxKey = "collection.deleteRate.max.mb"
|
|
CollectionDeleteRateMinKey = "collection.deleteRate.min.mb"
|
|
CollectionBulkLoadRateMaxKey = "collection.bulkLoadRate.max.mb"
|
|
CollectionBulkLoadRateMinKey = "collection.bulkLoadRate.min.mb"
|
|
CollectionQueryRateMaxKey = "collection.queryRate.max.qps"
|
|
CollectionQueryRateMinKey = "collection.queryRate.min.qps"
|
|
CollectionSearchRateMaxKey = "collection.searchRate.max.vps"
|
|
CollectionSearchRateMinKey = "collection.searchRate.min.vps"
|
|
CollectionDiskQuotaKey = "collection.diskProtection.diskQuota.mb"
|
|
|
|
PartitionDiskQuotaKey = "partition.diskProtection.diskQuota.mb"
|
|
|
|
// database level properties
|
|
DatabaseReplicaNumber = "database.replica.number"
|
|
DatabaseResourceGroups = "database.resource_groups"
|
|
DatabaseDiskQuotaKey = "database.diskQuota.mb"
|
|
DatabaseMaxCollectionsKey = "database.max.collections"
|
|
DatabaseForceDenyWritingKey = "database.force.deny.writing"
|
|
DatabaseForceDenyReadingKey = "database.force.deny.reading"
|
|
|
|
DatabaseForceDenyDDLKey = "database.force.deny.ddl" // all ddl
|
|
DatabaseForceDenyCollectionDDLKey = "database.force.deny.collectionDDL"
|
|
DatabaseForceDenyPartitionDDLKey = "database.force.deny.partitionDDL"
|
|
DatabaseForceDenyIndexDDLKey = "database.force.deny.index"
|
|
DatabaseForceDenyFlushDDLKey = "database.force.deny.flush"
|
|
DatabaseForceDenyCompactionDDLKey = "database.force.deny.compaction"
|
|
|
|
// collection level load properties
|
|
CollectionReplicaNumber = "collection.replica.number"
|
|
CollectionResourceGroups = "collection.resource_groups"
|
|
|
|
// CMEK related property keys, used in db and collection properties
|
|
EncryptionEnabledKey = "cipher.enabled"
|
|
EncryptionRootKeyKey = "cipher.key"
|
|
EncryptionEzIDKey = "cipher.ezID"
|
|
)
|
|
|
|
// Field properties key
|
|
const (
|
|
FieldDescriptionKey = "field.description"
|
|
)
|
|
|
|
// common properties
|
|
const (
|
|
MmapEnabledKey = "mmap.enabled"
|
|
LoadPriorityKey = "load_priority"
|
|
PartitionKeyIsolationKey = "partitionkey.isolation"
|
|
FieldSkipLoadKey = "field.skipLoad"
|
|
IndexOffsetCacheEnabledKey = "indexoffsetcache.enabled"
|
|
IndexNonEncoding = "index.nonEncoding"
|
|
EnableDynamicSchemaKey = `dynamicfield.enabled`
|
|
|
|
// timezone releated
|
|
TimezoneKey = "timezone"
|
|
AllowInsertAutoIDKey = "allow_insert_auto_id"
|
|
DisableFuncRuntimeCheck = "disable_func_runtime_check"
|
|
MaxFieldIDKey = "max_field_id"
|
|
|
|
// query mode
|
|
QueryModeKey = "query_mode"
|
|
QueryModeLargeTopK = "large_topk"
|
|
ValidQueryModes = QueryModeLargeTopK // comma-separated if more modes added later
|
|
|
|
// namespace sharding
|
|
NamespaceShardingEnabledKey = "namespace.sharding.enabled"
|
|
|
|
// warmup related
|
|
WarmupKey = "warmup"
|
|
WarmupScalarFieldKey = "warmup.scalarField"
|
|
WarmupScalarIndexKey = "warmup.scalarIndex"
|
|
WarmupVectorFieldKey = "warmup.vectorField"
|
|
WarmupVectorIndexKey = "warmup.vectorIndex"
|
|
WarmupDisable = "disable"
|
|
WarmupSync = "sync"
|
|
WarmupAsync = "async"
|
|
)
|
|
|
|
const (
|
|
PropertiesKey string = "properties"
|
|
TraceIDKey string = "uber-trace-id"
|
|
ClientRequestMsecKey string = "client-request-unixmsec"
|
|
)
|
|
|
|
// Timestamptz field
|
|
const (
|
|
TszYear string = "year"
|
|
TszMonth string = "month"
|
|
TszDay string = "day"
|
|
TszHour string = "hour"
|
|
TszMinute string = "minute"
|
|
TszSecond string = "second"
|
|
TszMicrosecond string = "microsecond"
|
|
)
|
|
|
|
func IsSystemField(fieldID int64) bool {
|
|
return fieldID < StartOfUserFieldID
|
|
}
|
|
|
|
func IsMmapDataEnabled(kvs ...*commonpb.KeyValuePair) (bool, bool) {
|
|
for _, kv := range kvs {
|
|
if kv.Key == MmapEnabledKey {
|
|
enable, _ := strconv.ParseBool(kv.Value)
|
|
return enable, true
|
|
}
|
|
}
|
|
return false, false
|
|
}
|
|
|
|
func IsMmapIndexEnabled(kvs ...*commonpb.KeyValuePair) (bool, bool) {
|
|
for _, kv := range kvs {
|
|
if kv.Key == MmapEnabledKey {
|
|
enable, _ := strconv.ParseBool(kv.Value)
|
|
return enable, true
|
|
}
|
|
}
|
|
return false, false
|
|
}
|
|
|
|
// GetWarmupPolicy returns the warmup policy value and whether it exists from key-value pairs
|
|
func GetWarmupPolicy(kvs ...*commonpb.KeyValuePair) (string, bool) {
|
|
for _, kv := range kvs {
|
|
if kv.Key == WarmupKey {
|
|
return kv.Value, true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// GetWarmupPolicyByKey returns the warmup policy for a specific key from key-value pairs
|
|
func GetWarmupPolicyByKey(key string, kvs ...*commonpb.KeyValuePair) (string, bool) {
|
|
for _, kv := range kvs {
|
|
if kv.Key == key {
|
|
return kv.Value, true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// IsWarmupKey checks if a key is any of the warmup-related keys
|
|
func IsWarmupKey(key string) bool {
|
|
return IsFieldWarmupKey(key) || IsCollectionWarmupKey(key)
|
|
}
|
|
|
|
// IsFieldWarmupKey checks if a key is the field-level warmup key
|
|
func IsFieldWarmupKey(key string) bool {
|
|
return key == WarmupKey
|
|
}
|
|
|
|
// IsCollectionWarmupKey checks if a key is a collection/table-level warmup key
|
|
func IsCollectionWarmupKey(key string) bool {
|
|
return key == WarmupScalarFieldKey ||
|
|
key == WarmupScalarIndexKey ||
|
|
key == WarmupVectorFieldKey ||
|
|
key == WarmupVectorIndexKey
|
|
}
|
|
|
|
// ValidateWarmupPolicy validates that the warmup policy value is valid
|
|
func ValidateWarmupPolicy(value string) error {
|
|
if value != WarmupDisable && value != WarmupSync && value != WarmupAsync {
|
|
return merr.WrapErrParameterInvalidMsg("invalid warmup policy: %s, must be '%s', '%s' or '%s'", value, WarmupDisable, WarmupSync, WarmupAsync)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FieldHasWarmupKey checks if a field has warmup key set in its TypeParams
|
|
func FieldHasWarmupKey(schema *schemapb.CollectionSchema, fieldID int64) bool {
|
|
for _, field := range schema.GetFields() {
|
|
if field.GetFieldID() == fieldID {
|
|
for _, kv := range field.GetTypeParams() {
|
|
if kv.Key == WarmupKey {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
// Check struct array fields
|
|
for _, structField := range schema.GetStructArrayFields() {
|
|
if structField.GetFieldID() == fieldID {
|
|
for _, kv := range structField.GetTypeParams() {
|
|
if kv.Key == WarmupKey {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
// Check fields inside struct
|
|
for _, field := range structField.GetFields() {
|
|
if field.GetFieldID() == fieldID {
|
|
for _, kv := range field.GetTypeParams() {
|
|
if kv.Key == WarmupKey {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func GetIndexType(indexParams []*commonpb.KeyValuePair) string {
|
|
for _, param := range indexParams {
|
|
if param.Key == IndexTypeKey {
|
|
return param.Value
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func FieldHasMmapKey(schema *schemapb.CollectionSchema, fieldID int64) bool {
|
|
for _, field := range schema.GetFields() {
|
|
if field.GetFieldID() == fieldID {
|
|
for _, kv := range field.GetTypeParams() {
|
|
if kv.Key == MmapEnabledKey {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
// Check struct array fields
|
|
for _, structField := range schema.GetStructArrayFields() {
|
|
if structField.GetFieldID() == fieldID {
|
|
for _, kv := range structField.GetTypeParams() {
|
|
if kv.Key == MmapEnabledKey {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
// Check fields inside struct
|
|
for _, field := range structField.GetFields() {
|
|
if field.GetFieldID() == fieldID {
|
|
for _, kv := range field.GetTypeParams() {
|
|
if kv.Key == MmapEnabledKey {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func IsPartitionKeyIsolationKvEnabled(kvs ...*commonpb.KeyValuePair) (bool, error) {
|
|
for _, kv := range kvs {
|
|
if kv.Key == PartitionKeyIsolationKey {
|
|
val, err := strconv.ParseBool(strings.ToLower(kv.Value))
|
|
if err != nil {
|
|
return false, merr.WrapErrParameterInvalidMsg("failed to parse partition key isolation: %v", err)
|
|
}
|
|
return val, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// IsQueryModeKeyExists checks if the query_mode key exists in the key-value pairs.
|
|
func IsQueryModeKeyExists(kvs ...*commonpb.KeyValuePair) bool {
|
|
for _, kv := range kvs {
|
|
if kv.Key == QueryModeKey {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GetQueryMode extracts the query_mode value from properties.
|
|
// Returns empty string if not set.
|
|
func GetQueryMode(kvs ...*commonpb.KeyValuePair) string {
|
|
for _, kv := range kvs {
|
|
if kv.Key == QueryModeKey {
|
|
return kv.Value
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// ValidateQueryMode validates the query_mode value. Returns nil if the value
|
|
// is valid or if query_mode is not set. Also rejects case-variant keys
|
|
// (e.g. "QUERY_MODE", "Query_Mode") that would be silently ignored.
|
|
func ValidateQueryMode(kvs ...*commonpb.KeyValuePair) error {
|
|
for _, kv := range kvs {
|
|
if kv.Key == QueryModeKey {
|
|
mode := kv.Value
|
|
if mode != QueryModeLargeTopK {
|
|
return merr.WrapErrParameterInvalidMsg("invalid query_mode value %q, valid values: [%s]", kv.Value, ValidQueryModes)
|
|
}
|
|
return nil
|
|
}
|
|
if strings.EqualFold(kv.Key, QueryModeKey) {
|
|
return merr.WrapErrParameterInvalidMsg("invalid property key %q, did you mean %q?", kv.Key, QueryModeKey)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// IsQueryModeLargeTopK checks if query_mode is set to "large_topk".
|
|
func IsQueryModeLargeTopK(kvs ...*commonpb.KeyValuePair) bool {
|
|
return GetQueryMode(kvs...) == QueryModeLargeTopK
|
|
}
|
|
|
|
// IsNamespaceShardingEnabledKeyExists checks if namespace.sharding.enabled exists in the key-value pairs.
|
|
func IsNamespaceShardingEnabledKeyExists(kvs ...*commonpb.KeyValuePair) bool {
|
|
for _, kv := range kvs {
|
|
if kv.Key == NamespaceShardingEnabledKey {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsNamespaceShardingEnabled extracts namespace.sharding.enabled from properties.
|
|
// Returns false if not set.
|
|
func IsNamespaceShardingEnabled(kvs ...*commonpb.KeyValuePair) (bool, error) {
|
|
for _, kv := range kvs {
|
|
if kv.Key == NamespaceShardingEnabledKey {
|
|
switch kv.Value {
|
|
case "true":
|
|
return true, nil
|
|
case "false":
|
|
return false, nil
|
|
default:
|
|
return false, merr.WrapErrParameterInvalidMsg("invalid namespace.sharding.enabled value %q, valid values: [true,false]", kv.Value)
|
|
}
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// ValidateNamespaceShardingEnabled validates namespace.sharding.enabled. Returns nil if
|
|
// the value is valid or if namespace.sharding.enabled is not set. Also rejects
|
|
// case-variant keys that would be silently ignored.
|
|
func ValidateNamespaceShardingEnabled(kvs ...*commonpb.KeyValuePair) error {
|
|
for _, kv := range kvs {
|
|
if kv.Key == NamespaceShardingEnabledKey {
|
|
_, err := IsNamespaceShardingEnabled(kv)
|
|
return err
|
|
}
|
|
if strings.EqualFold(kv.Key, NamespaceShardingEnabledKey) {
|
|
return merr.WrapErrParameterInvalidMsg("invalid property key %q, did you mean %q?", kv.Key, NamespaceShardingEnabledKey)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateNamespaceShardingEnabledNotAltered rejects attempts to update or
|
|
// delete namespace.sharding.enabled after collection creation.
|
|
func ValidateNamespaceShardingEnabledNotAltered(properties []*commonpb.KeyValuePair, deleteKeys []string) error {
|
|
for _, property := range properties {
|
|
if property.GetKey() == NamespaceShardingEnabledKey {
|
|
return merr.WrapErrParameterInvalidMsg("cannot alter %s after collection creation", NamespaceShardingEnabledKey)
|
|
}
|
|
if strings.EqualFold(property.GetKey(), NamespaceShardingEnabledKey) {
|
|
return merr.WrapErrParameterInvalidMsg("invalid property key %q, did you mean %q?", property.GetKey(), NamespaceShardingEnabledKey)
|
|
}
|
|
}
|
|
for _, key := range deleteKeys {
|
|
if key == NamespaceShardingEnabledKey {
|
|
return merr.WrapErrParameterInvalidMsg("cannot delete %s after collection creation", NamespaceShardingEnabledKey)
|
|
}
|
|
if strings.EqualFold(key, NamespaceShardingEnabledKey) {
|
|
return merr.WrapErrParameterInvalidMsg("invalid property key %q, did you mean %q?", key, NamespaceShardingEnabledKey)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func IsDisableFuncRuntimeCheck(kvs ...*commonpb.KeyValuePair) (bool, error) {
|
|
for _, kv := range kvs {
|
|
if kv.Key == DisableFuncRuntimeCheck {
|
|
val, err := strconv.ParseBool(strings.ToLower(kv.Value))
|
|
if err != nil {
|
|
return false, merr.WrapErrParameterInvalidMsg("failed to parse disable_func_runtime_check param: %v", err)
|
|
}
|
|
return val, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func IsPartitionKeyIsolationPropEnabled(props map[string]string) (bool, error) {
|
|
val, ok := props[PartitionKeyIsolationKey]
|
|
if !ok {
|
|
return false, nil
|
|
}
|
|
iso, parseErr := strconv.ParseBool(val)
|
|
if parseErr != nil {
|
|
return false, merr.WrapErrParameterInvalidMsg("failed to parse partition key isolation property: %v", parseErr)
|
|
}
|
|
return iso, nil
|
|
}
|
|
|
|
func GetNamespaceMode(kvs ...*commonpb.KeyValuePair) string {
|
|
for _, kv := range kvs {
|
|
if kv.GetKey() == NamespaceModeKey {
|
|
mode, ok := normalizeNamespaceMode(kv.GetValue())
|
|
if ok {
|
|
return mode
|
|
}
|
|
return kv.GetValue()
|
|
}
|
|
}
|
|
return NamespaceModePartitionKey
|
|
}
|
|
|
|
func IsNamespaceModePartitionKey(kvs ...*commonpb.KeyValuePair) bool {
|
|
return GetNamespaceMode(kvs...) == NamespaceModePartitionKey
|
|
}
|
|
|
|
func IsNamespaceModePartition(kvs ...*commonpb.KeyValuePair) bool {
|
|
return GetNamespaceMode(kvs...) == NamespaceModePartition
|
|
}
|
|
|
|
func ValidateNamespaceMode(kvs ...*commonpb.KeyValuePair) error {
|
|
for _, kv := range kvs {
|
|
if kv.GetKey() == NamespaceModeKey {
|
|
if _, ok := normalizeNamespaceMode(kv.GetValue()); !ok {
|
|
return merr.WrapErrParameterInvalidMsg("invalid namespace.mode value %q, valid values: [%s]", kv.GetValue(), ValidNamespaceModes)
|
|
}
|
|
return nil
|
|
}
|
|
if strings.EqualFold(kv.GetKey(), NamespaceModeKey) {
|
|
return merr.WrapErrParameterInvalidMsg("invalid property key %q, did you mean %q?", kv.GetKey(), NamespaceModeKey)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func normalizeNamespaceMode(mode string) (string, bool) {
|
|
switch mode {
|
|
case "", NamespaceModePartitionKey:
|
|
return NamespaceModePartitionKey, true
|
|
case NamespaceModePartition:
|
|
return NamespaceModePartition, true
|
|
default:
|
|
return "", false
|
|
}
|
|
}
|
|
|
|
const (
|
|
// LatestVerision is the magic number for watch latest revision
|
|
LatestRevision = int64(-1)
|
|
)
|
|
|
|
func DatabaseLevelReplicaNumber(kvs []*commonpb.KeyValuePair) (int64, error) {
|
|
for _, kv := range kvs {
|
|
if kv.Key == DatabaseReplicaNumber {
|
|
replicaNum, err := strconv.ParseInt(kv.Value, 10, 64)
|
|
if err != nil {
|
|
return 0, merr.WrapErrParameterInvalidMsg("invalid database property: [key=%s] [value=%s]", kv.Key, kv.Value)
|
|
}
|
|
|
|
return replicaNum, nil
|
|
}
|
|
}
|
|
|
|
return 0, merr.WrapErrParameterInvalidMsg("database property not found: %s", DatabaseReplicaNumber)
|
|
}
|
|
|
|
func DatabaseLevelResourceGroups(kvs []*commonpb.KeyValuePair) ([]string, error) {
|
|
for _, kv := range kvs {
|
|
if kv.Key == DatabaseResourceGroups {
|
|
invalidPropValue := merr.WrapErrParameterInvalidMsg("invalid database property: [key=%s] [value=%s]", kv.Key, kv.Value)
|
|
if len(kv.Value) == 0 {
|
|
return nil, invalidPropValue
|
|
}
|
|
|
|
rgs := strings.Split(kv.Value, ",")
|
|
if len(rgs) == 0 {
|
|
return nil, invalidPropValue
|
|
}
|
|
|
|
return lo.Map(rgs, func(rg string, _ int) string { return strings.TrimSpace(rg) }), nil
|
|
}
|
|
}
|
|
|
|
return nil, merr.WrapErrParameterInvalidMsg("database property not found: %s", DatabaseResourceGroups)
|
|
}
|
|
|
|
func CollectionLevelReplicaNumber(kvs []*commonpb.KeyValuePair) (int64, error) {
|
|
for _, kv := range kvs {
|
|
if kv.Key == CollectionReplicaNumber {
|
|
replicaNum, err := strconv.ParseInt(kv.Value, 10, 64)
|
|
if err != nil {
|
|
return 0, merr.WrapErrParameterInvalidMsg("invalid collection property: [key=%s] [value=%s]", kv.Key, kv.Value)
|
|
}
|
|
|
|
return replicaNum, nil
|
|
}
|
|
}
|
|
|
|
return 0, merr.WrapErrParameterInvalidMsg("collection property not found: %s", CollectionReplicaNumber)
|
|
}
|
|
|
|
func CollectionLevelResourceGroups(kvs []*commonpb.KeyValuePair) ([]string, error) {
|
|
for _, kv := range kvs {
|
|
if kv.Key == CollectionResourceGroups {
|
|
invalidPropValue := merr.WrapErrParameterInvalidMsg("invalid collection property: [key=%s] [value=%s]", kv.Key, kv.Value)
|
|
if len(kv.Value) == 0 {
|
|
return nil, invalidPropValue
|
|
}
|
|
|
|
rgs := strings.Split(kv.Value, ",")
|
|
if len(rgs) == 0 {
|
|
return nil, invalidPropValue
|
|
}
|
|
|
|
return lo.Map(rgs, func(rg string, _ int) string { return strings.TrimSpace(rg) }), nil
|
|
}
|
|
}
|
|
|
|
return nil, merr.WrapErrParameterInvalidMsg("collection property not found: %s", CollectionReplicaNumber)
|
|
}
|
|
|
|
// GetCollectionLoadFields returns the load field ids according to the type params.
|
|
func GetCollectionLoadFields(schema *schemapb.CollectionSchema, skipDynamicField bool) []int64 {
|
|
filter := func(field *schemapb.FieldSchema, _ int) (int64, bool) {
|
|
// skip system field
|
|
if IsSystemField(field.GetFieldID()) {
|
|
return field.GetFieldID(), false
|
|
}
|
|
// skip dynamic field if specified
|
|
if field.IsDynamic && skipDynamicField {
|
|
return field.GetFieldID(), false
|
|
}
|
|
|
|
v, err := ShouldFieldBeLoaded(field.GetTypeParams())
|
|
if err != nil {
|
|
// if configuration cannot be parsed, ignore it and load field
|
|
return field.GetFieldID(), true
|
|
}
|
|
return field.GetFieldID(), v
|
|
}
|
|
fields := lo.FilterMap(schema.GetFields(), filter)
|
|
|
|
fieldsNum := len(schema.GetFields())
|
|
for _, structField := range schema.GetStructArrayFields() {
|
|
fields = append(fields, lo.FilterMap(structField.GetFields(), filter)...)
|
|
fieldsNum += len(structField.GetFields())
|
|
}
|
|
|
|
// empty fields list means all fields will be loaded
|
|
if len(fields) == fieldsNum-int(SystemFieldsNum) {
|
|
return []int64{}
|
|
}
|
|
return fields
|
|
}
|
|
|
|
func ShouldFieldBeLoaded(kvs []*commonpb.KeyValuePair) (bool, error) {
|
|
for _, kv := range kvs {
|
|
if kv.GetKey() == FieldSkipLoadKey {
|
|
val, err := strconv.ParseBool(kv.GetValue())
|
|
return !val, err
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func IsEnableDynamicSchema(kvs []*commonpb.KeyValuePair) (found bool, value bool, err error) {
|
|
for _, kv := range kvs {
|
|
if kv.GetKey() == EnableDynamicSchemaKey {
|
|
value, err = strconv.ParseBool(kv.GetValue())
|
|
return true, value, err
|
|
}
|
|
}
|
|
return false, false, nil
|
|
}
|
|
|
|
func ValidateAutoIndexMmapConfig(autoIndexConfigEnable, isVectorField bool, indexParams map[string]string) error {
|
|
if !autoIndexConfigEnable {
|
|
return nil
|
|
}
|
|
|
|
_, ok := indexParams[MmapEnabledKey]
|
|
if ok && isVectorField {
|
|
return merr.WrapErrParameterInvalidMsg("mmap index is not supported to config for the collection in auto index mode")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func AllocAutoID(allocFunc func(uint32) (int64, int64, error), rowNum uint32, clusterID uint64) (int64, int64, error) {
|
|
idStart, idEnd, err := allocFunc(rowNum)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
reversed := bits.Reverse64(clusterID)
|
|
// right shift by 1 to preserve sign bit
|
|
reversed = reversed >> 1
|
|
|
|
return idStart | int64(reversed), idEnd | int64(reversed), nil
|
|
}
|
|
|
|
func GetCollectionAllowInsertNonBM25FunctionOutputs(kvs []*commonpb.KeyValuePair) bool {
|
|
for _, kv := range kvs {
|
|
if kv.Key == CollectionAllowInsertNonBM25FunctionOutputs {
|
|
enable, _ := strconv.ParseBool(kv.Value)
|
|
return enable
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func IsAllowInsertAutoID(kvs ...*commonpb.KeyValuePair) (bool, bool) {
|
|
for _, kv := range kvs {
|
|
if kv.Key == AllowInsertAutoIDKey {
|
|
enable, _ := strconv.ParseBool(kv.Value)
|
|
return enable, true
|
|
}
|
|
}
|
|
return false, false
|
|
}
|
|
|
|
func GetInt64Value(kvs []*commonpb.KeyValuePair, key string) (result int64, parseErr error, exist bool) {
|
|
kv := lo.FindOrElse(kvs, nil, func(kv *commonpb.KeyValuePair) bool {
|
|
return kv.GetKey() == key
|
|
})
|
|
if kv == nil {
|
|
return 0, nil, false
|
|
}
|
|
|
|
result, err := strconv.ParseInt(kv.GetValue(), 10, 64)
|
|
if err != nil {
|
|
return 0, err, true
|
|
}
|
|
return result, nil, true
|
|
}
|
|
|
|
func GetStringValue(kvs []*commonpb.KeyValuePair, key string) (result string, exist bool) {
|
|
kv := lo.FindOrElse(kvs, nil, func(kv *commonpb.KeyValuePair) bool {
|
|
return kv.GetKey() == key
|
|
})
|
|
if kv == nil {
|
|
return "", false
|
|
}
|
|
return kv.GetValue(), true
|
|
}
|
|
|
|
func GetCollectionTTL(kvs []*commonpb.KeyValuePair) (time.Duration, error) {
|
|
value, parseErr, exist := GetInt64Value(kvs, CollectionTTLConfigKey)
|
|
if parseErr != nil {
|
|
return 0, parseErr
|
|
}
|
|
|
|
if !exist {
|
|
return -1, nil
|
|
}
|
|
|
|
return time.Duration(value) * time.Second, nil
|
|
}
|
|
|
|
func GetCollectionTTLFromMap(kvs map[string]string) (time.Duration, error) {
|
|
value, exist := kvs[CollectionTTLConfigKey]
|
|
if !exist {
|
|
return -1, nil
|
|
}
|
|
|
|
ttlSeconds, err := strconv.ParseInt(value, 10, 64)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return time.Duration(ttlSeconds) * time.Second, nil
|
|
}
|
|
|
|
func CheckNamespace(schema *schemapb.CollectionSchema, namespace *string) error {
|
|
enabled := schema.GetEnableNamespace()
|
|
namespaceIsSet := namespace != nil
|
|
if enabled != namespaceIsSet {
|
|
if namespaceIsSet {
|
|
return merr.WrapErrParameterInvalidMsg("namespace data is set but namespace disabled")
|
|
}
|
|
return merr.WrapErrParameterInvalidMsg("namespace data is not set but namespace enabled")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ConvertWKTToWKB(wktStr string) ([]byte, error) {
|
|
geomT, err := wkt.Unmarshal(wktStr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return wkb.Marshal(geomT, wkb.NDR, wkbcommon.WKBOptionEmptyPointHandling(wkbcommon.EmptyPointHandlingNaN))
|
|
}
|
|
|
|
func ConvertWKBToWKT(wkbData []byte) (string, error) {
|
|
geomT, err := wkb.Unmarshal(wkbData, wkbcommon.WKBOptionEmptyPointHandling(wkbcommon.EmptyPointHandlingNaN))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return wkt.Marshal(geomT)
|
|
}
|