From e2787d39818b169085020f886b408a993fe0ae3f Mon Sep 17 00:00:00 2001 From: "zhenshan.cao" Date: Fri, 12 Jun 2026 15:04:51 -0700 Subject: [PATCH] enhance: standardize error handling on merr + Sys/Input classification (#50221) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit issue: #47420 ## What this PR does Project-wide migration of raw `fmt.Errorf` / `errors.New` in function bodies onto the `merr` framework, plus the Sys-vs-Input error classification and the machinery it drives (retriability, fine-grained metrics, segcore unification), plus the convention docs and a linter that keeps it from regressing. Scope: storage, proxy, coordinators (root/data/query), query node, data node, `pkg/util` & `internal/util`, expression parser, message queue, streaming, and misc packages. Bare raw-error usages went from ~3000 to a ~340 allowlist (package-level sentinels / build-tag / test sites). --- ## How to review this PR It is large but the vast majority is mechanical. Changes fall into three tiers; spend review budget on Part 2 and Part 3. ### Part 1 — Mechanical standardization (low risk, verify by rule) Each converted call follows one of a small fixed set of rules. To review, check that each site obeys the matching rule rather than reading every line: | Pattern | Rule | |---|---| | `fmt.Errorf("...")` originating a new error | → `merr.WrapErrXxxMsg("...")` with a code matching the failure's meaning | | Adding context to an existing typed error | → `merr.Wrap(err, "...")` / `merr.Wrapf(...)` — **preserves** the inner code (never `WrapErr*Err`, which overwrites it) | | Errors inside the streaming subsystem | → `status.New*` factories (StreamingError), **not** merr — this is the component-internal dialect (see `docs/dev/error_handling_guide.md`) | | Low-level / control-flow signal caught by `errors.Is` | → kept as a package-level `errors.New` sentinel (lowercase, same-package) | Conventions are documented in `docs/dev/error_handling_guide.md` (how-to) and `docs/dev/error_sentinel_convention.md` (rules + audit). A `gocritic`/`ruleguard` rule (`rawmerrerror`, in `rules.go`) enforces "no raw `return errors.New/fmt.Errorf`" under `make verifiers`. ### Part 2 — Behavior changes (review these closely) These are the sites where the wire contract or runtime behavior changes, not just the source text. Listed by category; representative locations given, full set in the diff. **A. gRPC wire-code shifts: `UnexpectedError(1)/Code 65535` → typed code.** Where a handler previously returned a raw error (collapsed to `Code=65535` on the wire), it now returns a typed merr, so the client sees a real code. The most common shift is to `IllegalArgument(5)/Code 1100` (ParameterInvalid). Touch points include datanode task handlers (CreateTask/Query/Drop), proxy Upsert, querynode GetMetrics, datacoord CreateIndex, httpserver query-response builder, and typeutil schema validation. One code refinement: an index-param validation moved `1100` → `1101` (ParameterMissing). **Client/SDK assertions and any code that switched on `Code=65535` for these paths must be re-checked** (the go_client e2e assertions were already aligned in this PR). **B. Prometheus `status` label contract change (externally visible).** The proxy metric's coarse `fail` / `rejected` values are split into `fail_input` / `fail_system` and `rejected_user` / `rejected_system` (in `requestutil.ParseMetricLabel`; auth/privilege rejections count as `rejected_user`), so dashboards can attribute a failure to caller vs operator. **Dashboards/alerts querying `status="fail"` must migrate to `status=~"fail_.*"`, and `status="rejected"` to `status=~"rejected_.*"`.** The in-repo Grafana dashboard is already migrated; external dashboards built on the old values silently go empty after upgrade. This is the one change that requires an ops-side migration. **C. Retriability semantics.** - C1: `merr.Status(err)` now forces `Retriable=false` when the error is an `InputError` — a malformed request can never succeed on blind retry, so clients never get the self-contradictory "your input is wrong but you may retry". - C2: `retry.Do` short-circuits an `InputError` (non-retriable) — **but only when the caller did not pass a `RetryErr` predicate**. The check is an `if c.isRetryErr != nil { ... } else if InputError { ... }` *mutually exclusive* branch (`pkg/util/retry/retry.go`): an explicit `RetryErr` takes precedence and bypasses the InputError abort. `retry.Handle` deliberately does **not** apply the InputError abort (its callers signal abort via `shouldRetry=false`). Four flusher startup callsites that must retry through transient "not ready" errors were given explicit `RetryErr` escape hatches. **D. segcore (C++→Go) error classification.** A single shared Go-side table (`pkg/util/merr/segcore.go`) maps each segcore code to a merr sentinel + InputError/signal category, replacing scattered hand-written `if errorCode == ...` switches in the cgo wrappers. **Wire `Code` values change for every segcore pass-through error, not just the remapped ones.** Named sentinels remap (C++ `2003` → merr `2001`, `2033` → `2002`, Folly/Knowhere codes likewise); **all remaining pass-through codes (`2004`–`2043`, previously surfaced to clients as raw C++ enum values) now serialize as `2000`** (`ErrSegcore`), with the original C++ code preserved in the `Reason` text (`segcoreCode=...`); unknown/future codes collapse to `2000` as well (pinned by the `wire_code_projection` test). Transient segcore classes (object storage / file IO / OOM / mmap / FieldNotLoaded — 11 codes) now report `Retriable=true`. **Any client switching on raw segcore codes in the `2004`–`2043` range must be re-checked**; the in-Reason code remains available for diagnostics. Signal codes (PretendFinished / FollyCancel) are recognized centrally. `errors.Is`-based control flow on these (e.g. scheduler skip/retry) is preserved. **E. InputError classification (25 sentinels + dynamic marks).** 25 sentinels in `errors.go` carry `WithErrorType(InputError)` (the Collection / ResourceGroup / Database families, `ErrIndexDuplicate`, `ErrParameterInvalid`, `ErrPrivilegeNotAuthenticated`, `ErrImportFailed`, `ErrQueryPlan`, ...), plus dynamic marks for the 8 segcore input codes (ExprInvalid, DimNotMatch, MetricTypeInvalid, FieldIDInvalid, ...) and `WrapErrAsInputError`. The widest blast radius is `ErrParameterInvalid` (1100): ~2335 `WrapErrParameterInvalid*` callsites now classify as input / non-retriable. Because of C1/C2 this changes retriability for any path that returns these. **The audit to confirm no transient path was mis-marked is the single most important review item** (see Part 3). One reverse correction: storage field-stats parsing moved from `ErrParameterInvalid` (input) to `ErrDataIntegrity` — a corrupted stored stat is data corruption, not user input. ### Part 3 — Known risks & traps (called out proactively) 1. **`merr.Wrap` vs `WrapErr*Err` (code-masking).** `WrapErr*Err` builds a `wrappedMilvusError{sentinel: ErrServiceInternal}` whose `code()` returns the *outer* sentinel — it overwrites the inner typed code and hides the `errors.Is` chain. This is intentional (use it to *deliberately* downgrade), but it was a recurring conversion defect; the rule "add context with `merr.Wrap`, downgrade with `WrapErr*Err`" is enforced by convention and reviewed across the diff. 2. **InputError × `retry.Do` blast radius.** Marking a sentinel `InputError` makes any `retry.Do(...)` without a `RetryErr` predicate stop retrying it. Reviewers should sanity-check that no transient use of the 19 newly-marked sentinels (especially `ErrParameterInvalid`) sits inside a retry loop that needed to keep spinning. The known flusher cases were handled (see C2). 3. **The ~340 raw-error allowlist.** What remains as bare `errors.New` is, by design: package-level sentinels (caught by `errors.Is`), `//go:build test` sites, and out-of-band trees (`cmd/`, `tests/`, codegen, walimpls). The linter only bans the *direct-return* form; assignment-then-return escapes and the full no-exceptions ban are deferred to an AST-based linter (Tier 2, documented). 4. **segcore C++ second step deferred.** This PR unifies classification on the Go side; splitting the dual-semantic C++ codes at the source is a follow-up. --- ## Validation - `make verifiers`: Go side clean (gofmt + static-check across modules, including the new `rawmerrerror` rule with a 0-hit baseline repo-wide). - `make test-go`: passing; the one real regression introduced (a datanode `invalid_task_type` assertion shifting `1` → `5` from a ParameterInvalid conversion) was fixed in-tree. - go_client e2e CreateIndex assertions aligned to the new merr messages. --------- Signed-off-by: zhenshan.cao Co-authored-by: Claude Opus 4.7 (1M context) --- configs/milvus.yaml | 8 +- .../monitor/grafana/milvus-dashboard.json | 2 +- docs/dev/error_handling_guide.md | 314 ++++++++++++ docs/dev/error_sentinel_convention.md | 324 ++++++++++++ .../developer_guides/appendix_d_error_code.md | 9 + examples/telemetry_e2e_test/go.mod | 4 +- examples/telemetry_e2e_test/go.sum | 2 + internal/agg/aggregate.go | 21 +- internal/agg/aggregate_reducer.go | 47 +- internal/agg/aggregate_util.go | 39 +- internal/agg/operators.go | 37 +- internal/agg/type_check.go | 5 +- internal/allocator/cached_allocator.go | 11 +- internal/allocator/id_allocator.go | 8 +- internal/allocator/local_allocator.go | 7 +- internal/cdc/cluster/milvus_client.go | 6 +- internal/cdc/meta/replicate_meta.go | 4 +- .../replicatemanager/channel_replicator.go | 3 +- .../replication/replicatestream/msg_queue.go | 2 +- .../coordinator/file_resource_observer.go | 4 +- internal/coordinator/restful_mgr_routes.go | 16 +- .../snmanager/streaming_node_manager.go | 3 +- internal/coordinator/wal_callbacks.go | 8 +- .../src/analyzer/dict/lindera/fetch.rs | 11 +- internal/datacoord/analyze_meta.go | 10 +- internal/datacoord/backfill_result.go | 25 +- .../datacoord/compaction_policy_clustering.go | 9 +- .../datacoord/compaction_policy_forcemerge.go | 5 +- internal/datacoord/compaction_queue.go | 11 +- .../datacoord/compaction_task_clustering.go | 3 +- internal/datacoord/compaction_task_l0.go | 4 +- internal/datacoord/compaction_task_mix.go | 4 +- internal/datacoord/compaction_trigger.go | 7 +- internal/datacoord/copy_segment_task.go | 2 +- internal/datacoord/ddl_callbacks.go | 17 +- internal/datacoord/ddl_callbacks_import.go | 8 +- .../datacoord/ddl_callbacks_import_test.go | 12 +- .../datacoord/ddl_callbacks_snapshot_test.go | 6 +- internal/datacoord/errors.go | 5 +- .../external_collection_refresh_manager.go | 20 +- .../external_collection_refresh_meta.go | 16 +- internal/datacoord/garbage_collector.go | 4 +- internal/datacoord/garbage_collector_lob.go | 5 +- internal/datacoord/import_meta.go | 3 +- internal/datacoord/import_services_test.go | 20 +- internal/datacoord/import_util.go | 14 +- internal/datacoord/index_meta.go | 14 +- internal/datacoord/index_service.go | 2 +- internal/datacoord/meta.go | 32 +- internal/datacoord/meta_test.go | 4 +- internal/datacoord/meta_util.go | 11 +- internal/datacoord/metrics_info.go | 6 +- internal/datacoord/partition_stats_meta.go | 5 +- .../datacoord/segment_allocation_policy.go | 6 +- internal/datacoord/segment_manager.go | 6 +- internal/datacoord/server.go | 9 +- internal/datacoord/services.go | 43 +- .../datacoord/services_commit_backfill.go | 2 +- internal/datacoord/services_test.go | 14 +- internal/datacoord/session/session.go | 4 +- internal/datacoord/snapshot.go | 59 +-- internal/datacoord/snapshot_manager.go | 44 +- internal/datacoord/snapshot_meta.go | 17 +- internal/datacoord/stats_task_meta.go | 10 +- internal/datacoord/task_index.go | 9 +- .../task_refresh_external_collection.go | 63 +-- internal/datacoord/task_stats.go | 15 +- .../bump_schema_version_compactor.go | 61 +-- .../compactor/clustering_compactor.go | 5 +- .../datanode/compactor/compactor_common.go | 5 +- internal/datanode/compactor/l0_compactor.go | 9 +- internal/datanode/compactor/mix_compactor.go | 10 +- .../datanode/compactor/record_materializer.go | 59 +-- internal/datanode/compactor/segment_writer.go | 8 +- .../datanode/compactor/sort_compaction.go | 5 +- internal/datanode/data_node.go | 6 +- .../datanode/external/function_executor.go | 49 +- internal/datanode/external/manager.go | 4 +- internal/datanode/external/task_update.go | 38 +- .../datanode/importv2/copy_segment_utils.go | 56 +- .../datanode/importv2/task_copy_segment.go | 4 +- internal/datanode/importv2/task_preimport.go | 5 +- internal/datanode/importv2/util.go | 8 +- internal/datanode/index/scheduler.go | 2 +- internal/datanode/index/task_stats.go | 15 +- internal/datanode/index_services.go | 17 +- internal/datanode/services.go | 20 +- internal/datanode/services_test.go | 15 +- .../distributed/datanode/client/client.go | 6 +- .../distributed/mixcoord/client/client.go | 8 +- internal/distributed/proxy/client/client.go | 6 +- .../distributed/proxy/httpserver/handler.go | 101 ++-- .../proxy/httpserver/handler_v1_test.go | 2 +- .../proxy/httpserver/handler_v2.go | 38 +- .../proxy/httpserver/handler_v2_test.go | 4 +- .../proxy/httpserver/timeout_middleware.go | 9 +- .../distributed/proxy/httpserver/utils.go | 149 +++--- .../proxy/httpserver/utils_test.go | 5 +- .../proxy/httpserver/wrap_request.go | 24 +- .../distributed/proxy/httpserver/wrapper.go | 8 + .../distributed/proxy/listener_manager.go | 7 +- .../distributed/proxy/request_interceptor.go | 33 ++ .../proxy/request_interceptor_test.go | 5 +- internal/distributed/proxy/service.go | 2 +- internal/distributed/proxy/service_test.go | 18 +- .../distributed/querynode/client/client.go | 6 +- internal/distributed/streaming/balancer.go | 10 +- .../producer/producer_rate_limiter.go | 4 +- .../producer/producer_rate_limiter_test.go | 3 +- internal/distributed/streamingnode/service.go | 3 +- internal/distributed/utils/util.go | 4 +- .../pipeline/flow_graph_time_tick_node.go | 3 +- .../flushcommon/syncmgr/growing_source.go | 10 +- internal/flushcommon/syncmgr/pack_writer.go | 3 +- .../flushcommon/syncmgr/pack_writer_v3.go | 13 +- internal/flushcommon/syncmgr/sync_manager.go | 6 +- .../flushcommon/writebuffer/insert_buffer.go | 4 +- .../flushcommon/writebuffer/write_buffer.go | 18 +- internal/http/server.go | 9 +- internal/kv/etcd/embed_etcd_kv.go | 72 +-- internal/kv/etcd/etcd_kv.go | 16 +- internal/kv/tikv/txn_tikv.go | 80 +-- internal/metastore/kv/binlog/binlog.go | 3 +- internal/metastore/kv/datacoord/kv_catalog.go | 17 +- internal/metastore/kv/datacoord/util.go | 25 +- internal/metastore/kv/rootcoord/kv_catalog.go | 38 +- .../kv/rootcoord/legacy_snapshot_gc.go | 3 +- .../metastore/kv/streamingcoord/kv_catalog.go | 2 +- .../convert_field_data_to_generic_value.go | 10 +- .../parser/planparserv2/error_listener.go | 6 +- .../planparserv2/fill_expression_value.go | 41 +- internal/parser/planparserv2/operators.go | 47 +- .../parser/planparserv2/parser_visitor.go | 67 ++- .../parser/planparserv2/plan_parser_v2.go | 32 +- internal/parser/planparserv2/utils.go | 56 +- .../proxy/accesslog/info/grpc_info_test.go | 6 +- internal/proxy/accesslog/info/restful_info.go | 22 +- internal/proxy/accesslog/info/util.go | 11 +- internal/proxy/accesslog/minio_handler.go | 4 +- internal/proxy/accesslog/util.go | 6 +- internal/proxy/accesslog/writer.go | 19 +- internal/proxy/agg_reducer.go | 7 +- internal/proxy/channels_mgr.go | 7 +- internal/proxy/connection/util.go | 9 +- internal/proxy/function_task.go | 17 +- internal/proxy/highlighter.go | 23 +- internal/proxy/hook_interceptor.go | 10 +- internal/proxy/hook_interceptor_test.go | 7 +- internal/proxy/impl.go | 37 +- internal/proxy/meta_cache.go | 36 +- internal/proxy/meta_cache_testonly.go | 5 +- internal/proxy/privilege/cache.go | 10 +- internal/proxy/proxy.go | 6 +- internal/proxy/proxy_test.go | 2 +- internal/proxy/query_pipeline.go | 5 +- internal/proxy/repack_func.go | 11 +- internal/proxy/search_agg/computer.go | 56 +- internal/proxy/search_agg/context_builder.go | 78 +-- .../proxy/search_agg/context_builder_test.go | 53 ++ internal/proxy/search_agg/order.go | 5 +- internal/proxy/search_pipeline.go | 138 ++--- internal/proxy/search_reduce_util.go | 32 +- internal/proxy/search_util.go | 79 ++- internal/proxy/service_provider.go | 4 +- internal/proxy/shardclient/lb_policy.go | 12 +- internal/proxy/shardclient/lb_policy_test.go | 41 +- internal/proxy/shardclient/shard_client.go | 7 +- internal/proxy/simple_rate_limiter.go | 3 +- internal/proxy/snapshot_impl.go | 18 +- internal/proxy/struct_hybrid_search.go | 8 +- internal/proxy/task.go | 90 ++-- internal/proxy/task_alias.go | 13 +- internal/proxy/task_delete.go | 11 +- internal/proxy/task_delete_streaming.go | 2 +- internal/proxy/task_flush_all_streaming.go | 3 +- internal/proxy/task_flush_streaming.go | 5 +- internal/proxy/task_import.go | 9 +- internal/proxy/task_index.go | 41 +- internal/proxy/task_insert.go | 2 +- internal/proxy/task_query.go | 82 +-- internal/proxy/task_search.go | 26 +- internal/proxy/task_search_test.go | 2 +- internal/proxy/task_snapshot.go | 14 +- internal/proxy/task_statistic.go | 18 +- internal/proxy/task_test.go | 4 +- internal/proxy/task_upsert.go | 13 +- internal/proxy/task_upsert_partial_op.go | 4 +- internal/proxy/task_validator.go | 7 +- internal/proxy/timestamp.go | 10 +- internal/proxy/util.go | 311 +++++------ internal/proxy/util_test.go | 14 + internal/proxy/validate_util.go | 12 +- internal/proxy/vector_type_convert.go | 10 +- internal/querycoordv2/ddl_callbacks.go | 3 +- ...llbacks_alter_load_info_load_collection.go | 6 + ...llbacks_alter_load_info_load_partitions.go | 10 + ...acks_alter_load_info_release_partitions.go | 6 +- ...lbacks_alter_load_info_transfer_replica.go | 12 + .../ddl_callbacks_alter_resource_group.go | 12 +- .../ddl_callbacks_drop_resource_group.go | 17 +- internal/querycoordv2/handlers.go | 8 +- internal/querycoordv2/job/job_load.go | 4 +- internal/querycoordv2/job/job_release.go | 8 +- internal/querycoordv2/job/job_sync.go | 4 +- internal/querycoordv2/job/load_config.go | 9 +- internal/querycoordv2/job/utils.go | 18 +- .../querycoordv2/meta/coordinator_broker.go | 2 +- internal/querycoordv2/meta/replica_manager.go | 17 +- internal/querycoordv2/meta/resource_group.go | 6 +- .../querycoordv2/meta/resource_manager.go | 71 +-- .../meta/resource_manager_test.go | 25 +- internal/querycoordv2/ops_services.go | 22 +- internal/querycoordv2/params/params.go | 3 + internal/querycoordv2/server.go | 15 +- internal/querycoordv2/services.go | 99 ++-- internal/querycoordv2/services_test.go | 14 +- internal/querycoordv2/session/cluster.go | 11 +- internal/querycoordv2/session/cluster_test.go | 2 +- internal/querycoordv2/session/node_manager.go | 4 +- internal/querycoordv2/task/executor.go | 9 +- internal/querycoordv2/utils/util.go | 2 +- internal/querynodev2/cluster/manager.go | 4 +- internal/querynodev2/delegator/delegator.go | 20 +- .../querynodev2/delegator/delegator_data.go | 32 +- .../delegator/delegator_twostage.go | 3 +- .../delegator/function_runtime_state.go | 8 +- .../delegator/growing_flush_source.go | 2 +- internal/querynodev2/delegator/idf_oracle.go | 22 +- internal/querynodev2/delegator/types.go | 2 - internal/querynodev2/delegator/util.go | 5 +- internal/querynodev2/handlers.go | 4 +- internal/querynodev2/pipeline/insert_node.go | 9 +- .../querynodev2/segments/ignore_non_pk_ops.go | 10 +- .../querynodev2/segments/index_attr_cache.go | 5 +- .../querynodev2/segments/query_pipeline.go | 14 +- .../querynodev2/segments/search_reduce.go | 17 +- internal/querynodev2/segments/segment.go | 13 +- .../querynodev2/segments/segment_loader.go | 48 +- .../segments/state/load_state_lock.go | 4 +- internal/querynodev2/segments/utils.go | 16 +- internal/querynodev2/server.go | 10 +- internal/querynodev2/services.go | 8 +- internal/querynodev2/services_test.go | 2 +- internal/querynodev2/tasks/arrow_import.go | 9 +- .../querynodev2/tasks/heap_merge_reduce.go | 4 +- internal/querynodev2/tasks/search_task.go | 2 +- internal/rootcoord/broker.go | 14 +- internal/rootcoord/create_collection_task.go | 48 +- internal/rootcoord/ddl_callbacks.go | 11 +- ...dl_callbacks_alter_collection_add_field.go | 7 +- .../ddl_callbacks_alter_collection_name.go | 7 +- ...l_callbacks_alter_collection_properties.go | 8 +- .../ddl_callbacks_alter_collection_schema.go | 5 +- .../rootcoord/ddl_callbacks_alter_database.go | 10 +- .../ddl_callbacks_collection_function.go | 23 +- .../ddl_callbacks_create_collection.go | 8 +- .../ddl_callbacks_create_database.go | 10 +- .../ddl_callbacks_create_partition.go | 10 +- .../rootcoord/ddl_callbacks_drop_alias.go | 5 +- .../ddl_callbacks_drop_collection.go | 9 +- .../rootcoord/ddl_callbacks_drop_database.go | 9 +- .../rootcoord/ddl_callbacks_drop_partition.go | 5 +- .../ddl_callbacks_rbac_credential.go | 19 +- .../rootcoord/ddl_callbacks_rbac_privilege.go | 15 +- .../rootcoord/ddl_callbacks_rbac_restore.go | 9 +- internal/rootcoord/ddl_callbacks_rbac_role.go | 25 +- .../ddl_callbacks_truncate_collection.go | 13 +- internal/rootcoord/dml_channels.go | 6 +- internal/rootcoord/drop_collection_task.go | 5 +- internal/rootcoord/key_manager.go | 6 +- internal/rootcoord/meta_rbac.go | 4 - internal/rootcoord/meta_table.go | 106 ++-- internal/rootcoord/name_db.go | 5 +- internal/rootcoord/quota_center.go | 4 +- internal/rootcoord/rbac_task.go | 14 +- internal/rootcoord/root_coord.go | 109 ++-- internal/rootcoord/root_coord_test.go | 6 +- internal/rootcoord/telemetry/command_store.go | 3 +- internal/rootcoord/timeticksync.go | 6 +- internal/rootcoord/util.go | 12 +- internal/storage/arrow_util.go | 35 +- internal/storage/binlog_reader.go | 7 +- internal/storage/binlog_record_writer.go | 26 +- internal/storage/binlog_util.go | 8 +- internal/storage/binlog_writer.go | 12 +- internal/storage/data_codec.go | 31 +- internal/storage/delta_data.go | 22 +- internal/storage/event_data.go | 49 +- internal/storage/event_reader.go | 10 +- internal/storage/event_writer.go | 11 +- internal/storage/factory.go | 5 +- internal/storage/field_stats.go | 5 +- internal/storage/field_stats_test.go | 2 +- internal/storage/field_value.go | 20 +- internal/storage/index_data_codec.go | 10 +- internal/storage/insert_data.go | 21 +- internal/storage/local_chunk_manager.go | 6 +- internal/storage/minio_object_storage_test.go | 28 +- internal/storage/payload_reader.go | 202 ++++---- internal/storage/payload_writer.go | 133 +++-- internal/storage/pk_statistics.go | 7 +- internal/storage/primary_key.go | 14 +- internal/storage/print_binlog.go | 28 +- internal/storage/record_reader.go | 11 +- internal/storage/record_to_insert.go | 21 +- internal/storage/record_writer.go | 15 +- internal/storage/remote_chunk_manager.go | 10 +- internal/storage/rw.go | 11 +- internal/storage/serde.go | 134 ++--- internal/storage/serde_delta.go | 12 +- internal/storage/serde_events.go | 5 +- internal/storage/sort.go | 4 +- internal/storage/stats.go | 13 +- internal/storage/stats_collector.go | 4 +- internal/storage/stats_test.go | 2 +- internal/storage/utils.go | 37 +- internal/storagev2/filesystem_metrics.go | 12 +- internal/storagev2/packed/explore_ffi.go | 28 +- internal/storagev2/packed/ffi_common.go | 36 +- internal/storagev2/packed/ffi_filesystem.go | 8 +- internal/storagev2/packed/ffi_sample.go | 12 +- internal/storagev2/packed/ffi_stats.go | 4 +- internal/storagev2/packed/manifest_commit.go | 14 +- internal/storagev2/packed/manifest_ffi.go | 53 +- internal/storagev2/packed/packed_reader.go | 4 +- .../storagev2/packed/packed_reader_ffi.go | 13 +- internal/storagev2/packed/packed_writer.go | 10 +- .../storagev2/packed/packed_writer_ffi.go | 10 +- .../storagev2/packed/segment_writer_ffi.go | 10 +- internal/storagev2/packed/stats_resolver.go | 3 +- internal/storagev2/packed/transaction.go | 30 +- internal/storagev2/packed/utils.go | 17 +- .../client/assignment/assignment_impl.go | 3 +- .../server/balancer/balancer_impl.go | 19 +- .../server/balancer/channel/manager.go | 7 +- .../balancer/policy/vchannelfair/builder.go | 9 +- .../vchannelfair/vchannel_fair_policy.go | 6 +- .../broadcaster/ack_callback_scheduler.go | 4 +- .../server/broadcaster/broadcast/singleton.go | 7 +- .../server/broadcaster/broadcast_manager.go | 6 +- .../server/broadcaster/broadcast_task.go | 22 +- .../registry/ack_message_callback.go | 4 +- .../registry/ack_once_message_callback.go | 4 +- .../server/broadcaster/resource_key_locker.go | 3 +- .../server/service/assignment.go | 29 +- .../client/handler/consumer/consumer_impl.go | 4 +- .../client/handler/handler_client_impl.go | 6 +- .../client/handler/producer/producer_impl.go | 2 +- .../flusher/flusherimpl/flusher_components.go | 4 +- .../flusher/flusherimpl/msg_handler_impl.go | 2 +- .../server/flusher/flusherimpl/util.go | 2 +- .../handler/consumer/consume_server.go | 6 +- .../handler/producer/produce_server.go | 6 +- .../service/release_manual_flush_preparer.go | 4 +- .../server/wal/adaptor/opener.go | 4 +- .../shard/shards/segment_flush_worker.go | 2 +- .../shard/shards/shard_manager_collection.go | 4 +- .../wal/interceptors/shard/stats/config.go | 5 +- .../wal/interceptors/txn/txn_manager.go | 2 +- .../server/wal/recovery/config.go | 9 +- .../wal/recovery/recovery_storage_impl.go | 6 +- .../wal/recovery/vchannel_recovery_info.go | 4 +- .../server/wal/utility/reorder_buffer.go | 3 +- internal/tso/global_allocator.go | 6 +- internal/tso/tso.go | 5 +- internal/util/analyzecgowrapper/helper.go | 10 +- internal/util/analyzer/canalyzer/helper.go | 10 +- internal/util/bloomfilter/bloom_filter.go | 8 +- internal/util/cgo/futures.go | 7 +- internal/util/cgo/futures_test.go | 13 +- internal/util/componentutil/componentutil.go | 8 +- internal/util/credentials/credentials.go | 13 +- internal/util/dependency/factory.go | 7 +- internal/util/exprutil/expr_checker.go | 18 +- internal/util/flowgraph/flow_graph.go | 11 +- internal/util/function/bm25_function.go | 18 +- internal/util/function/chain/chain.go | 18 +- internal/util/function/chain/converter.go | 75 ++- internal/util/function/chain/dataframe.go | 16 +- .../util/function/chain/expr/base_expr.go | 4 +- .../util/function/chain/expr/decay_expr.go | 5 +- .../function/chain/expr/rerank_model_expr.go | 11 +- .../function/chain/expr/round_decimal_expr.go | 5 +- .../function/chain/expr/score_combine_expr.go | 9 +- internal/util/function/chain/operator_base.go | 7 +- .../util/function/chain/operator_filter.go | 20 +- .../util/function/chain/operator_group_by.go | 22 +- .../util/function/chain/operator_limit.go | 6 +- internal/util/function/chain/operator_map.go | 12 +- .../util/function/chain/operator_merge.go | 32 +- .../util/function/chain/operator_registry.go | 6 +- .../util/function/chain/operator_select.go | 6 +- internal/util/function/chain/operator_sort.go | 8 +- internal/util/function/chain/repr.go | 9 +- .../util/function/chain/rerank_builder.go | 10 +- .../util/function/chain/types/registry.go | 6 +- .../embedding/ali_embedding_provider.go | 8 +- .../embedding/bedrock_embedding_provider.go | 13 +- .../embedding/cohere_embedding_provider.go | 14 +- .../util/function/embedding/function_base.go | 6 +- .../function/embedding/function_executor.go | 22 +- .../embedding/gemini_embedding_provider.go | 9 +- .../embedding/openai_embedding_provider.go | 11 +- internal/util/function/embedding/runner.go | 33 +- .../siliconflow_embedding_provider.go | 6 +- .../embedding/tei_embedding_provider.go | 12 +- .../embedding/text_embedding_function.go | 50 +- .../embedding/vertexai_embedding_provider.go | 13 +- .../embedding/voyageai_embedding_provider.go | 14 +- .../embedding/yc_embedding_provider.go | 9 +- internal/util/function/function.go | 5 +- .../function/highlight/semantic_highlight.go | 18 +- internal/util/function/manager.go | 63 +-- internal/util/function/minhash_function.go | 64 ++- .../models/ali/ali_dashscope_client.go | 7 +- .../function/models/cohere/cohere_client.go | 3 +- internal/util/function/models/common.go | 25 +- .../function/models/gemini/gemini_client.go | 4 +- .../function/models/openai/openai_client.go | 7 +- .../models/siliconflow/siliconflow_client.go | 3 +- .../models/vertexai/vertexai_client.go | 12 +- .../util/function/models/vllm/vllm_client.go | 5 +- .../models/voyageai/voyageai_client.go | 11 +- .../function/models/zilliz/zilliz_client.go | 11 +- .../function/multi_analyzer_bm25_function.go | 35 +- .../function/rerank/ali_rerank_provider.go | 4 +- .../function/rerank/cohere_rerank_provider.go | 6 +- .../util/function/rerank/model_function.go | 12 +- .../rerank/siliconflow_rerank_provider.go | 8 +- .../function/rerank/tei_rerank_provider.go | 6 +- .../function/rerank/vllm_rerank_provider.go | 6 +- .../rerank/voyageai_rerank_provider.go | 6 +- internal/util/funcutil/count_util.go | 15 +- internal/util/hookutil/cipher.go | 17 +- internal/util/hookutil/cipher_test.go | 6 +- internal/util/hookutil/default.go | 5 +- internal/util/hookutil/ez.go | 19 +- internal/util/hookutil/hook.go | 3 +- internal/util/hookutil/plugin.go | 10 +- internal/util/idalloc/basic_allocator.go | 18 +- .../util/importutilv2/binlog/l0_reader.go | 2 +- internal/util/importutilv2/binlog/reader.go | 7 +- internal/util/importutilv2/binlog/util.go | 29 +- .../util/importutilv2/binlog/util_test.go | 2 +- internal/util/importutilv2/common/util.go | 9 +- internal/util/importutilv2/csv/reader.go | 9 +- internal/util/importutilv2/csv/row_parser.go | 38 +- internal/util/importutilv2/json/reader.go | 31 +- internal/util/importutilv2/json/row_parser.go | 30 +- .../util/importutilv2/numpy/field_reader.go | 10 +- internal/util/importutilv2/numpy/util.go | 28 +- internal/util/importutilv2/option.go | 10 +- .../util/importutilv2/parquet/field_reader.go | 55 +- .../util/importutilv2/parquet/list_like.go | 5 +- internal/util/importutilv2/parquet/reader.go | 5 +- .../parquet/struct_field_reader.go | 25 +- internal/util/importutilv2/parquet/util.go | 46 +- internal/util/importutilv2/util.go | 2 +- internal/util/indexcgowrapper/helper.go | 17 +- internal/util/indexcgowrapper/index.go | 10 +- internal/util/indexparamcheck/base_checker.go | 5 +- .../indexparamcheck/bitmap_index_checker.go | 7 +- .../util/indexparamcheck/conf_adapter_mgr.go | 5 +- .../indexparamcheck/hybrid_index_checker.go | 11 +- internal/util/indexparamcheck/index_type.go | 10 +- .../util/indexparamcheck/inverted_checker.go | 4 +- .../indexparamcheck/ngram_index_checker.go | 7 +- .../util/indexparamcheck/rtree_checker.go | 7 +- .../util/indexparamcheck/stl_sort_checker.go | 7 +- internal/util/indexparamcheck/trie_checker.go | 5 +- internal/util/indexparamcheck/utils.go | 11 +- .../indexparamcheck/vector_index_checker.go | 37 +- internal/util/initcore/init_core.go | 35 +- internal/util/mock/grpcclient.go | 6 +- internal/util/pipeline/errors.go | 4 +- .../util/proxyutil/proxy_client_manager.go | 27 +- internal/util/proxyutil/proxy_watcher.go | 4 +- internal/util/queryutil/dedup_pk_op.go | 6 +- internal/util/queryutil/helpers.go | 35 +- internal/util/queryutil/order_op.go | 4 +- internal/util/queryutil/pipeline.go | 10 +- internal/util/queryutil/pipeline_builders.go | 5 +- internal/util/queryutil/reduce_by_pk_op.go | 6 +- internal/util/queryutil/remap_op.go | 4 +- internal/util/reduce/field_data.go | 9 +- internal/util/reduce/orderby/types.go | 11 +- internal/util/segcore/collection.go | 5 +- internal/util/segcore/plan.go | 16 +- internal/util/segcore/requests.go | 13 +- internal/util/segcore/segment.go | 23 +- internal/util/sessionutil/session_util.go | 17 +- .../service/balancer/balancer.go | 15 +- .../service/contextutil/cluster_id.go | 9 +- .../service/contextutil/create_consumer.go | 5 +- .../service/contextutil/create_producer.go | 5 +- .../service/discoverer/session_discoverer.go | 3 +- .../streamingutil/service/resolver/builder.go | 3 +- .../service/resolver/resolver.go | 6 + .../resolver/watch_based_grpc_resolver.go | 3 +- .../streamingutil/status/streaming_error.go | 5 +- .../util/streamingutil/util/wal_selector.go | 15 +- internal/util/testutil/test_util.go | 14 +- internal/util/textmatch/phrase_match.go | 16 +- internal/util/typeutil/hash.go | 9 +- internal/util/typeutil/json_util.go | 4 +- pkg/common/common.go | 37 +- pkg/common/error.go | 19 +- pkg/config/config.go | 13 + pkg/config/file_source.go | 5 +- pkg/config/manager.go | 20 +- pkg/kv/rocksdb/rocksdb_kv.go | 56 +- pkg/log/log.go | 8 +- pkg/metrics/metrics.go | 16 + pkg/mq/common/message.go | 10 +- pkg/mq/mqimpl/rocksmq/client/client_impl.go | 5 +- pkg/mq/mqimpl/rocksmq/client/util.go | 6 +- pkg/mq/mqimpl/rocksmq/server/global_rmq.go | 4 +- pkg/mq/mqimpl/rocksmq/server/rocksmq_impl.go | 68 ++- .../rocksmq/server/rocksmq_retention.go | 6 +- pkg/mq/msgdispatcher/dispatcher.go | 3 +- pkg/mq/msgdispatcher/manager.go | 3 +- pkg/mq/msgdispatcher/target.go | 4 +- pkg/mq/msgstream/mq_factory.go | 8 +- pkg/mq/msgstream/mq_msgstream.go | 29 +- .../msgstream/mqwrapper/kafka/kafka_client.go | 4 +- .../mqwrapper/kafka/kafka_consumer.go | 3 +- .../mqwrapper/kafka/kafka_producer.go | 5 +- .../mqwrapper/pulsar/pulsar_client.go | 8 +- pkg/mq/msgstream/mqwrapper/rmq/rmq_client.go | 4 +- pkg/mq/msgstream/msg.go | 15 +- pkg/mq/msgstream/msgstream.go | 16 +- pkg/mq/msgstream/repack_func.go | 17 +- pkg/mq/msgstream/unmarshal.go | 5 +- pkg/objectstorage/huawei/huawei.go | 5 +- pkg/objectstorage/util.go | 8 +- pkg/rules.go | 58 +++ .../util/message/adaptor/broadcast_message.go | 3 +- .../util/message/adaptor/message_id.go | 3 +- pkg/streaming/util/message/builder.go | 3 +- .../util/message/specialized_message.go | 11 +- .../walimpls/impls/pulsar/builder.go | 3 +- pkg/streaming/walimpls/impls/rmq/scanner.go | 5 +- pkg/streaming/walimpls/opener.go | 9 +- pkg/taskcommon/properties.go | 13 +- pkg/tracer/tracer.go | 7 +- pkg/util/conc/pool.go | 3 +- pkg/util/contextutil/context_util.go | 10 +- pkg/util/distance/calc_distance.go | 12 +- pkg/util/etcd/etcd_util.go | 11 +- pkg/util/etcd/etcd_util_test.go | 38 +- pkg/util/expr/expr.go | 12 +- pkg/util/externalspec/external_spec.go | 37 +- pkg/util/funcutil/func.go | 59 +-- pkg/util/funcutil/map.go | 4 +- pkg/util/funcutil/placeholdergroup.go | 16 +- pkg/util/funcutil/policy.go | 14 +- pkg/util/hardware/container_darwin.go | 6 +- pkg/util/hardware/container_linux.go | 7 +- pkg/util/hardware/container_windows.go | 6 +- pkg/util/hardware/gpu_mem_info.go | 4 +- pkg/util/hardware/gpu_mem_info_cuda.go | 4 +- pkg/util/indexparams/index_params.go | 18 +- pkg/util/lock/metric_mutex.go | 4 +- pkg/util/merr/error_classification_test.go | 212 ++++++++ pkg/util/merr/errors.go | 201 ++++++-- pkg/util/merr/errors_test.go | 113 +++- pkg/util/merr/segcore.go | 188 +++++++ pkg/util/merr/segcore_test.go | 205 ++++++++ pkg/util/merr/utils.go | 484 ++++++++++++++---- pkg/util/merr/utils_test.go | 20 + pkg/util/metricsinfo/cache.go | 3 +- pkg/util/metricsinfo/err.go | 4 - pkg/util/metricsinfo/metric_request.go | 12 +- pkg/util/paramtable/autoindex_param.go | 7 +- pkg/util/paramtable/grpc_param.go | 3 +- pkg/util/ratelimitutil/rate_collector.go | 12 +- pkg/util/replicateutil/config_helper.go | 9 +- pkg/util/replicateutil/config_validator.go | 55 +- pkg/util/requestutil/getter.go | 77 ++- pkg/util/requestutil/getter_test.go | 60 +++ pkg/util/resource/release_codec.go | 6 +- pkg/util/retry/retry.go | 42 +- pkg/util/retry/retry_test.go | 50 ++ pkg/util/timestamptz/timestamptz.go | 17 +- pkg/util/typeutil/convension.go | 8 +- pkg/util/typeutil/field_data.go | 5 +- pkg/util/typeutil/field_schema.go | 12 +- pkg/util/typeutil/float_util.go | 16 +- pkg/util/typeutil/gen_empty_field_data.go | 5 +- pkg/util/typeutil/hash.go | 11 +- pkg/util/typeutil/ids_checker.go | 27 +- pkg/util/typeutil/kv_pair_helper.go | 5 +- pkg/util/typeutil/schema.go | 199 ++++--- pkg/util/typeutil/schema_test.go | 19 +- pkg/util/typeutil/skip_list.go | 7 +- rules.go | 70 +++ scripts/run_go_unittest.sh | 6 + scripts/start_cluster_with_replicas.sh | 73 +++ tests/go_client/testcases/index_test.go | 6 +- tests/python_client/base/client_base.py | 2 + tests/python_client/base/client_v2_base.py | 6 + tests/python_client/check/func_check.py | 8 +- .../milvus_client/test_milvus_client_alias.py | 7 +- .../milvus_client/test_milvus_client_alter.py | 13 +- .../test_milvus_client_collection.py | 5 +- .../test_milvus_client_geometry.py | 2 +- .../test_milvus_client_highlighter.py | 3 +- .../milvus_client/test_milvus_client_index.py | 30 +- .../test_milvus_client_partition.py | 3 +- .../milvus_client/test_milvus_client_query.py | 36 +- .../milvus_client/test_milvus_client_rbac.py | 8 + .../test_milvus_client_search.py | 16 +- .../test_milvus_client_search_by_pk.py | 2 +- .../test_milvus_client_search_iterator.py | 3 +- .../test_milvus_client_search_json.py | 6 +- .../test_milvus_client_search_v2.py | 2 +- .../test_milvus_client_snapshot.py | 9 +- .../test_milvus_client_timestamptz.py | 6 +- .../async_milvus_client/test_index_async.py | 3 +- tests/python_client/testcases/test_delete.py | 4 +- tests/python_client/testcases/test_index.py | 10 +- .../test_text_embedding_function_e2e.py | 29 +- tests/python_client/testcases/test_utility.py | 4 +- .../testcases/test_collection_operations.py | 2 +- .../testcases/test_index_operation.py | 2 +- .../testcases/test_jobs_operation.py | 2 +- .../testcases/test_partition_operation.py | 2 +- .../testcases/test_user_operation.py | 2 +- .../testcases/test_vector_operations.py | 8 +- 629 files changed, 7465 insertions(+), 4797 deletions(-) create mode 100644 docs/dev/error_handling_guide.md create mode 100644 docs/dev/error_sentinel_convention.md create mode 100644 pkg/util/merr/error_classification_test.go create mode 100644 pkg/util/merr/segcore.go create mode 100644 pkg/util/merr/segcore_test.go create mode 100755 scripts/start_cluster_with_replicas.sh diff --git a/configs/milvus.yaml b/configs/milvus.yaml index b05d5a2a2d..c00c7349d8 100644 --- a/configs/milvus.yaml +++ b/configs/milvus.yaml @@ -359,15 +359,15 @@ proxy: remoteMaxTime: 0 # The time interval allowed for uploading access log files. If the upload time of a log file exceeds this interval, the file will be deleted. Setting the value to 0 disables this feature. formatters: base: - format: "[$time_now] [ACCESS] <$user_name: $user_addr> $method_name [status: $method_status] [code: $error_code] [sdk: $sdk_version] [msg: $error_msg] [traceID: $trace_id] [timeCost: $time_cost]" + format: "[$time_now] [ACCESS] <$user_name: $user_addr> $method_name [status: $method_status] [code: $error_code] [sdk: $sdk_version] [msg: $error_msg] [traceID: $trace_id] [timeCost: $time_cost] [error_type: $error_type]" query: - format: "[$time_now] [ACCESS] <$user_name: $user_addr> $method_name [status: $method_status] [code: $error_code] [sdk: $sdk_version] [msg: $error_msg] [traceID: $trace_id] [timeCost: $time_cost] [database: $database_name] [collection: $collection_name] [partitions: $partition_name] [expr: $method_expr] [params: $query_params]" + format: "[$time_now] [ACCESS] <$user_name: $user_addr> $method_name [status: $method_status] [code: $error_code] [sdk: $sdk_version] [msg: $error_msg] [traceID: $trace_id] [timeCost: $time_cost] [database: $database_name] [collection: $collection_name] [partitions: $partition_name] [expr: $method_expr] [params: $query_params] [error_type: $error_type]" methods: "Query, Delete, /query, /v2/vectordb/entities/query, /v2/vectordb/entities/get, /v2/vectordb/entities/delete" search: - format: "[$time_now] [ACCESS] <$user_name: $user_addr> $method_name [status: $method_status] [code: $error_code] [sdk: $sdk_version] [msg: $error_msg] [traceID: $trace_id] [timeCost: $time_cost] [database: $database_name] [collection: $collection_name] [partitions: $partition_name] [expr: $method_expr] [nq: $nq] [params: $search_params]" + format: "[$time_now] [ACCESS] <$user_name: $user_addr> $method_name [status: $method_status] [code: $error_code] [sdk: $sdk_version] [msg: $error_msg] [traceID: $trace_id] [timeCost: $time_cost] [database: $database_name] [collection: $collection_name] [partitions: $partition_name] [expr: $method_expr] [nq: $nq] [params: $search_params] [error_type: $error_type]" methods: "HybridSearch, Search, /search, /v2/vectordb/entities/search, /v2/vectordb/entities/advanced_search, /v2/vectordb/entities/hybrid_search" upsert: - format: "[$time_now] [ACCESS] <$user_name: $user_addr> $method_name [status: $method_status] [code: $error_code] [sdk: $sdk_version] [msg: $error_msg] [traceID: $trace_id] [timeCost: $time_cost] [database: $database_name] [collection: $collection_name] [partitions: $partition_name] [partial_update: $partial_update]" + format: "[$time_now] [ACCESS] <$user_name: $user_addr> $method_name [status: $method_status] [code: $error_code] [sdk: $sdk_version] [msg: $error_msg] [traceID: $trace_id] [timeCost: $time_cost] [database: $database_name] [collection: $collection_name] [partitions: $partition_name] [partial_update: $partial_update] [error_type: $error_type]" methods: Upsert cacheSize: 0 # Size of log of write cache, in byte. (Close write cache if size was 0) cacheFlushInterval: 3 # time interval of auto flush write cache, in seconds. (Close auto flush if interval was 0) diff --git a/deployments/monitor/grafana/milvus-dashboard.json b/deployments/monitor/grafana/milvus-dashboard.json index 34a33c1899..dbb352ff90 100644 --- a/deployments/monitor/grafana/milvus-dashboard.json +++ b/deployments/monitor/grafana/milvus-dashboard.json @@ -4794,7 +4794,7 @@ "uid": "${datasource}" }, "exemplar": true, - "expr": "sum(increase(milvus_proxy_req_count{app_kubernetes_io_instance=~\"$instance\", app_kubernetes_io_name=\"$app_name\", namespace=\"$namespace\", status=\"fail\"}[2m])/120) by(function_name, pod, node_id)", + "expr": "sum(increase(milvus_proxy_req_count{app_kubernetes_io_instance=~\"$instance\", app_kubernetes_io_name=\"$app_name\", namespace=\"$namespace\", status=~\"fail_.*\"}[2m])/120) by(function_name, pod, node_id)", "interval": "", "legendFormat": "{{function_name}}-{{pod}}-{{node_id}}", "queryType": "randomWalk", diff --git a/docs/dev/error_handling_guide.md b/docs/dev/error_handling_guide.md new file mode 100644 index 0000000000..e48b45bd1a --- /dev/null +++ b/docs/dev/error_handling_guide.md @@ -0,0 +1,314 @@ +# Error Handling Guide + +How to produce and return errors in Milvus server code — the day-to-day how-to. +For the underlying rules, the sentinel naming convention, and the enforcement +roadmap, see [error_sentinel_convention.md](./error_sentinel_convention.md). For +the canonical numeric code list, see the sentinel definitions in +[`pkg/util/merr/errors.go`](../../pkg/util/merr/errors.go). (The +[appendix_d_error_code.md](../developer_guides/appendix_d_error_code.md) +appendix predates merr and lists the **deprecated** `commonpb.ErrorCode` enum, +not the merr codes.) + +## TL;DR + +1. **Never** `return errors.New(...)` or `return fmt.Errorf(...)`. A linter + rejects it (see [The one rule](#the-one-rule-never-return-a-bare-error)). +2. An error that leaves your component must be a **typed** error carrying a code. + The lingua franca across Milvus components is **merr**. +3. Pick one of three forms: originate a typed merr, add context with `merr.Wrap`, + or — only if a caller branches on it by identity — a package-level sentinel. + +## The mental model: three kinds of error, one boundary rule + +Milvus has three legitimate kinds of error, distinguished by **how far they +travel**, not by syntax: + +| Kind | Scope | Carrier | Example | +|---|---|---|---| +| **① merr** | across Milvus components & to clients | numeric code on the main gRPC wire | `merr.ErrCollectionNotFound` | +| **② component-internal dialect** | between sub-modules of one big component | that component's own typed error + its own wire | `streamingutil/status.StreamingError` | +| **③ internal sentinel** | within a single Go process | `errors.New` pointer identity, caught by `errors.Is` | `errSessionVersionCheckFailure` | + +The single rule that ties them together: + +> **The error you must return follows the interface's promise** — it is decided +> by *who is on the other side of the boundary you are crossing*, not by where +> the error was born. + +- Crossing into **another Milvus component** (proxy → rootcoord), or returning to + a **client**: the promise is **merr**. Translate to a typed merr at that + boundary. +- Staying **inside one component** (e.g. the streaming sub-modules talking to + each other): the component may speak its own typed dialect (`StreamingError`). + It is **not** required to be merr — but it must still be *typed*, and it gets + translated to merr at the component's outer edge. +- On **no** wire, ever: a **bare** `errors.New` / `fmt.Errorf`. Internal + sentinels (kind ③) are bare, but they never reach a wire — they are caught by + `errors.Is` and translated first. + +### Why component-internal dialects are allowed (the StreamingError case) + +Streaming is one big component whose sub-modules (streamingnode, streamingcoord, +the streaming client) talk to each other constantly. They use +`streamingutil/status.StreamingError`, which has its **own** error codes and its +**own** gRPC encoding. That is deliberate: it is a *bounded context* with its own +vocabulary. There is intentionally **no** global "StreamingError → merr" +auto-converter — that would erase the dialect. Instead the conversion happens +**once, at the consumer's boundary**, and the consumer decides how, based on +what its own interface promises: + +```go +// rootcoord consumes a streaming service inside CreateCollection. +err := s.streamingService.DoSomething(ctx, ...) // may return *StreamingError +if err != nil { + // (a) You care about the code the client sees → translate explicitly: + if se := status.AsStreamingError(err); se != nil && se.IsRateLimitRejected() { + return merr.WrapErrServiceRateLimit("streaming backpressure") + } + // (b) You don't care about a precise code → let it fall back at the + // boundary (see "The safety net"); the client gets a generic + // internal-class error. Prefer being explicit, but this is allowed. + return err +} +``` + +## The one rule: never return a bare error + +```go +return errors.New("segment not loaded") // ❌ linter rejects +return fmt.Errorf("segment %d not loaded", id) // ❌ linter rejects +``` + +Why it is banned, even though the boundary would "fix it up" anyway: a bare error +that escapes to a gRPC boundary becomes `Code=65535 (Unexpected)` — visually +indistinguishable from "the server hit an unhandled bug". A wall of +`errors.New("reason 1")`, `errors.New("reason 2")` is a sign nobody planned the +error taxonomy: the caller cannot program against it, and it all collapses into +one opaque code on the wire. A typed error costs one extra word and makes the +failure *addressable*. The linter exists to build the habit: **the thing I +return is always typed.** + +## Decision tree: what should I return? + +``` +Am I crossing into another component / returning to a client? +│ +├─ No (staying inside my own component) +│ ├─ My component has its own typed dialect (e.g. streaming)? +│ │ → use that dialect's factory (status.New*), not merr, not errors.New +│ └─ Otherwise → a typed merr (rules below); or, only if a caller in THIS +│ process branches on the outcome by identity, a package-level sentinel (§3.3) +│ +└─ Yes → I must return a typed merr: + ├─ Brand-new failure, no underlying error worth carrying? + │ → merr.WrapErrXxxMsg("detail %s", v) (§3.1 originate) + │ and pick Input vs System deliberately (next section) + ├─ I hold an underlying error and want to KEEP its code, just add context? + │ → merr.Wrap(err, "while doing X") (§3.2 add context) + └─ I hold an underlying error and want to DOWNGRADE it to a generic class? + → merr.WrapErrServiceInternalErr(err, "...") (§3.2 — deliberate override) +``` + +## Input vs System: who is to blame? + +Every merr is classified as **InputError** (the request author's fault) or +**SystemError** (Milvus's fault, the default). Choosing a factory chooses the +classification, so when you originate an error, ask one question first: + +> **Would a correctly implemented Milvus ever hit this branch, given this +> request?** If the request content itself triggers it → InputError. If +> reaching this branch means a Milvus bug or internal failure → SystemError. + +Quick rules for the cases that get misclassified in practice: + +- A **plan / task type / request produced by a coordinator** is not user input. + An unrecognized task type or malformed compaction plan is an internal + protocol violation (think mixed-version rolling upgrade) → + `WrapErrServiceInternalMsg`, even though the check looks like validation. +- **Data produced by segcore or another internal component** is not user + input. A violated data-shape contract (ValidData length, truncated vectors) + is a Milvus bug → `WrapErrServiceInternalMsg`. +- A **TOCTOU race** (state was valid at check time, changed by execution time) + is not user input → keep it a system error. + +### How classification is attached + +Two mechanisms, used in different situations: + +1. **Baked-in sentinels.** ~25 sentinels are declared + `WithErrorType(InputError)` in `errors.go` (`ErrParameterInvalid/Missing/ + TooLarge`, `ErrPrivilegeNotPermitted`, `ErrDatabaseInvalidName`, ...). + Using their factory *is* the classification — which is exactly why reaching + for `WrapErrParameterInvalidMsg` to express an internal assertion is wrong. +2. **Boundary marking** for dual-use sentinels. `ErrCollectionNotFound` stays + SystemError (internal refresh/retry paths depend on that), and the proxy + boundary stamps it InputError only where the name came from the user: + + ```go + // proxy meta cache, the central chokepoint for user-supplied names: + return collection, merr.WrapErrAsInputErrorWhen(err, + merr.ErrCollectionNotFound, merr.ErrDatabaseNotFound) + ``` + + `WrapErrAsInputError(err)` marks unconditionally; + `WrapErrAsInputErrorWhen(err, targets...)` marks only if the error's code + matches a target. Both preserve `errors.Is` and the code — they relabel the + classification, nothing else. + +### What the classification drives + +| Surface | InputError behavior | +|---|---| +| `commonpb.Status` | `ExtraInfo["is_input_error"]="true"`, `Retriable` forced `false` | +| Prometheus | request counted as `fail_input` / `rejected_user` (vs `fail_system` / `rejected_system`) | +| Access log / failure log | `error_type` field set accordingly | +| proxy lb_policy | **no cross-replica failover** — retrying a bad request elsewhere can't help | +| `retry.Do` | aborts immediately instead of retrying | + +The last two rows are why misclassification is not cosmetic: marking an +internal failure as InputError disables the retry/failover machinery that +would have healed it, and a dashboard blames users for Milvus bugs. + +### Pitfalls (each of these happened) + +- **Don't mark a shared sentinel InputError globally** to fix one callsite — + every internal `retry.Do` loop waiting on that error stops retrying. Use + boundary marking instead. +- **Don't classify in a helper** what only the boundary can know. The same + not-found is the user's fault when the name came from a request, and a + system fault when it came from internal state — stamp at the chokepoint + where the origin is known. +- **"Looks like validation" is not the test.** Coordinator-to-node protocol + checks, segcore output checks, and cgo boundary checks all look like + validation; none of them are user input. + +## The three correct ways + +### 3.1 Originate a typed error — `WrapErrXxxMsg` / `WrapErrXxx` + +When the failure starts here and there is no inner error worth preserving: + +```go +return merr.WrapErrParameterInvalidMsg("nq (%d) exceeds the limit (%d)", nq, max) +return merr.WrapErrCollectionNotFound(collectionName) +``` + +Pick the sentinel whose **code** matches the failure's meaning (see the sentinel +definitions in [`pkg/util/merr/errors.go`](../../pkg/util/merr/errors.go)). This is the common +case: most of the time you only need to attach a message to a well-chosen code, +and the framework does the rest. + +### 3.2 Add context but keep the code — `merr.Wrap`, never `WrapErr*Err` + +When you already hold a typed error and only want to add a breadcrumb: + +```go +if err := s.loadSegment(ctx, id); err != nil { + return merr.Wrap(err, "while loading sealed segment") // ✅ keeps the inner code +} +``` + +`merr.Wrap` / `merr.Wrapf` is a thin wrapper (like `errors.Wrap`): it prepends +context and **preserves** the inner error's code and its `errors.Is` chain. + +⚠️ Do **not** reach for `merr.WrapErrServiceInternalErr(err, ...)` (or any +`merr.WrapErrXxxErr`) just to add context. Those build a +`wrappedMilvusError{sentinel: ErrServiceInternal}` whose `code()` returns the +**outer** sentinel's code — they **overwrite** the inner typed code with +ServiceInternal (5) and force it non-retriable (`As()` resolves to the outer +sentinel). The `errors.Is` chain itself survives via `Unwrap()`, but the typed +code and retriability are masked. `WrapErrXxxErr` is *only* for +when you **deliberately** want to relabel the inner error to a new code (e.g. +collapse a noisy internal failure into one ServiceInternal for the client). + +This split is intentional, and the framework deliberately does **not** try to be +clever: *keep the code* versus *downgrade the code* is a decision you state, not +one the framework guesses. `merr.Wrap` **always** keeps the inner code; +`WrapErrXxxErr` **always** relabels to the outer one. A helper that "smartly" +preserved the inner code whenever it recognized a typed merr would blur the +intent — a reader could no longer tell from the call site whether the author +meant to preserve or to downgrade. The choice of helper *is* the statement of +intent. + +### 3.2.1 The base-package case: pass through, wrap, or relabel? + +Low-level packages (`pkg/...`, `internal/util/...`) sit under many callers and +usually receive an error from something even lower — etcd, S3, a third-party +library, another util. For every such error you must consciously pick one of +three. Getting this wrong in a base package is expensive: it is multiplied across +every caller. + +| Choice | Use it when | How | +|---|---|---| +| **Pass through** the original `err` | the inner err is already a typed error meaningful to your caller, or your package has no business classifying it — let the caller decide | `return err` | +| **Wrap, keep the code** | you want to add a breadcrumb (which key/path/op failed) without changing what the error *means* | `merr.Wrapf(err, "etcd txn on key %s", k)` | +| **Relabel / downgrade** | the inner err is a leaky implementation / third-party detail your caller should not see; you translate it into the typed merr your package's interface promises | `merr.WrapErrXxxErr(err, "...")` | + +Deciding questions, in order: + +1. **Is the inner err already typed and meaningful to my caller?** → pass through. +2. **Does any caller `errors.Is` the inner err's identity?** → pass through or + `merr.Wrap` (both preserve the chain); **never relabel** — it hides the chain. +3. **Is the inner err a third-party / implementation detail I promise to hide?** + → relabel to the typed merr my interface promises. + +A base package that relabels too eagerly destroys codes the upper layers needed; +one that passes a raw third-party error straight through leaks an untyped error +toward the boundary. Neither is acceptable — the choice must be deliberate, and it +follows your package's interface promise, not convenience. + +### 3.3 Need identity branching or reuse — a package-level sentinel + +Define a sentinel when a caller **in the same process** branches on the outcome +by identity, via `errors.Is`: + +```go +// internal/util/sessionutil/session_util.go — caught by isNotSessionVersionCheckFailure +// and used as a retry.Do predicate; the identity must survive, so it stays a +// bare sentinel rather than a merr error. +var errSessionVersionCheckFailure = errors.New("session version check failure") +``` + +Rules for sentinels (full version in +[error_sentinel_convention.md](./error_sentinel_convention.md)): + +- **Package-level, never function-local.** A local `x := errors.New(...)` is a + refactor hazard — tomorrow it gets hoisted and silently crosses a boundary. + Lift it to a `var` at package scope. +- **Lowercase / unexported** when it lives in `internal/...`. If a + *cross-package* caller needs the signal, do **not** export the sentinel — + redesign the API to carry the signal in a return value (e.g. + `(ignored bool, err error)`). +- It must be `errors.Is`-caught and translated to a typed merr (or + `merr.Success()`) **before** crossing any gRPC boundary. A sentinel that + reaches the wire is just an opaque `Code=65535`. + +When unsure between 3.1 and 3.3: if nobody does `errors.Is` on it, you don't need +a sentinel — just originate a typed merr with a message (3.1). + +## The safety net: boundary fallback (and why not panic) + +If a non-typed error does reach a gRPC handler, `merr.Status(err)` falls back to +`Code=65535 (Unexpected)`. This is a **backstop, not a feature**: it keeps the +server from leaking internals or crashing, but the client gets an opaque code. +Treat any `Code=65535` in logs as "someone forgot to type their error". + +Why the boundary **falls back instead of panicking**: third-party libraries and +deep call chains produce errors on paths that tests cannot fully cover. Panicking +on "not a typed merr" would turn a stray untyped error into an outage. The +contract is therefore: **fall back to a generic code, never panic.** The goal of +the linter and this guide is to make that fallback path *empty in practice* — so +that every error a client sees was deliberately typed at its source. + +## Enforcement + +- A `gocritic`/`ruleguard` rule (`rawmerrerror` in `rules.go`) rejects + `return errors.New / fmt.Errorf / errors.Errorf` from function bodies. It runs + under `make verifiers` (via `static-check`), so no extra command is needed. + Exempt paths (run outside the request path): `*_test.go`, `cmd/`, `tests/`, + codegen, the walimpls harness, and `/mocks/` (generated mock helpers are + test infrastructure even though some lack a "Code generated" header). +- It catches the **direct-return** form — the one that lets a raw error escape to + a boundary. Assignment-then-return escapes (`e := errors.New(); return e`) and + the full no-exceptions ban require the AST-based linter described as "Tier 2" + in [error_sentinel_convention.md](./error_sentinel_convention.md). diff --git a/docs/dev/error_sentinel_convention.md b/docs/dev/error_sentinel_convention.md new file mode 100644 index 0000000000..6049a6b986 --- /dev/null +++ b/docs/dev/error_sentinel_convention.md @@ -0,0 +1,324 @@ +# Error Sentinel Convention + +Milvus error handling has two distinct layers. This document describes the rules +that separate them, the rationale, and an audit of where the codebase currently +violates them. + +## The two layers + +### 1. Typed merr (wire-protocol errors) + +Defined in `pkg/util/merr/errors.go` (`ErrCollectionNotFound`, +`ErrParameterInvalid`, etc.). These carry a numeric error code that is +serialized into `commonpb.Status{ErrorCode, Reason}` and shipped to the +client over gRPC. They are the only thing a client (or another Milvus +component on the receiving end of an RPC) sees. + +Creation: `merr.WrapErrXxxMsg(...)` / `merr.WrapErrXxxErr(cause, ...)` +(origination only — never to add context to a typed merr that already exists, +see `merr.Wrap`). + +### 2. Internal sentinels (single-process control flow) + +Created with `errors.New(...)` at package scope (e.g. `errIgnoredAlterAlias`, +`errReleaseCollectionNotLoaded`, `errNodeNotEnough`). These are signaling +vocabulary inside a single Go process: a callee tells its caller "this is an +idempotent no-op" or "the queue is empty" so the caller can branch / retry / +ignore. They are **not** part of the wire protocol. + +The catcher is always `errors.Is(err, sentinelX)` at some boundary in the +calling stack, and the boundary either: + +- translates to `merr.Success()` (idempotency: e.g. drop something that + doesn't exist → success), or +- translates to a typed merr `merr.WrapErrXxxMsg(...)` (e.g. user already + exists → `WrapErrParameterInvalid`). + +## The hard invariant + +> **Any internal sentinel must be `errors.Is`-caught and translated to a +> typed merr (or `Success`) before crossing any gRPC handler boundary — +> client-facing or component-to-component.** + +Why "any gRPC boundary, not just client-facing": gRPC serializes errors to +`commonpb.Status{ErrorCode, Reason}`. The Go-level pointer identity that +`errors.New` sentinels rely on does not survive the wire. The peer's +`merr.Error(status)` reconstructs a typed merr from the numeric code; the +sentinel chain is gone forever. So a sentinel that escapes an internal coord +RPC is just as broken as one that escapes a user-facing RPC — only quieter, +because no customer sees the resulting `Code=65535 (unexpected)`. + +### What breaks the invariant + +The `errors.Is` chain survives `return err`, `errors.Wrap(err, "...")` / +`merr.Wrap(err, "...")` (cockroachdb thin wrap), **and** +`merr.WrapErrServiceInternalErr(err, "...")` — `milvusError.Unwrap()` +returns the inner error, so `errors.Is(outer, innerSentinel)` stays true through any +of them. It is **destroyed** only by: + +- Putting the cause in a format argument instead of the chain: + `merr.WrapErrXxxMsg("...: %s", err)`, or the `%w` mistake (`WrapErr*Msg` + formats with `fmt.Sprintf`, which does **not** honor `%w` and renders + `%!w(...)`). The inner error reaches the message text but is unreachable via + `Unwrap()`, so `errors.Is(outer, innerSentinel)` returns false. +- Any custom wrapper that doesn't implement `Unwrap()`. + +Do **not** confuse this with the separate code/retriability rule. +`merr.Wrap(err, ...)` and `merr.WrapErrXxxErr(err, ...)` both keep `errors.Is` +intact, but they differ in what the result reports at the boundary: + +- `merr.Wrap(err, ...)` preserves the inner's `Code()` and `IsRetryable` — the + chain still resolves to the inner's `*milvusError`. +- `merr.WrapErrXxxErr(err, ...)` reports the **outer** sentinel's code and + retriability: `As()` resolves to `ErrServiceInternal` (Code 5, + non-retriable), **masking** the inner's classification. + +So there are two distinct rules, often conflated: + +1. To keep `errors.Is` working: never stuff the cause into a format string; + pass it as the error argument. +2. To add context **without changing the classification** (preserve the inner + code + retriability): use `merr.Wrap`, not `merr.WrapErr*Err`. Reserve + `merr.WrapErrXxxErr` for when you intend to *assert* a new classification + (e.g. this genuinely is a service-internal error). (See also + [feedback rule](#related-rules) on `merr.Wrap` vs `merr.WrapErr*Err`.) + +## Naming convention + +The convention has two layers, matched to the two error categories: + +### Wire-protocol layer (typed merr) — uppercase `Err*` in `pkg/util/merr` only + +All errors that may cross any gRPC boundary (client-facing or +component-to-component) must be `*merr.milvusError` defined in +`pkg/util/merr/errors.go`. They have: + +- A numeric code passed to `newMilvusError(...)`; uniqueness is enforced by + the init-time code registry (defining a second sentinel on an occupied code + panics at package init, since `milvusError.Is` matches by code alone) +- A `var ErrXxx = newMilvusError(...)` declaration in `pkg/util/merr/errors.go` +- An exported `WrapErrXxxMsg` / `WrapErrXxxErr` helper + +If an error needs to be visible to the wire, it lives here. No exceptions. + +Every sentinel also carries an Input-vs-System classification (who is to +blame) that drives `Status.Retriable`, the `fail_input`/`fail_system` metric +labels, lb_policy failover and `retry.Do`; see "Input vs System: who is to +blame?" in [error_handling_guide.md](error_handling_guide.md). + +### Internal-sentinel layer — lowercase `errXxx`, same-package only + +Internal sentinels live in `internal/...` packages, are created with +`errors.New(...)`, and are **lowercase / unexported**. The rule: + +> **A `var err* = errors.New(...)` declared in `internal/...` may only be +> referenced inside the same Go package. Cross-package consumers must not +> see it.** + +This makes Go visibility do the enforcement: if you need a signal across +package boundaries, you either (a) lift it into the wire layer as a typed +merr, or (b) redesign the API so the signal flows via a return value +(e.g. `(ignored bool, err error)`), not via the error type. + +Example (current code, after the 04-coord cleanup): + +```go +// errFull / errNoSuchElement are INTERNAL sentinels: caught by errors.Is +// inside the compaction inspector / scheduler loop and never serialized +// across any gRPC boundary. +var ( + errFull = errors.New("compaction queue is full") + errNoSuchElement = errors.New("compaction queue has no element") +) +``` + +### Cross-package idempotency: use a return-value flag, not an exported sentinel + +The previous code exported `meta.ErrResourceGroupOperationIgnored` so that +the parent package `querycoordv2` could catch it via `errors.Is` and +translate to `merr.Success()`. This was an exported `Err*` in +`internal/...` — visually indistinguishable from a `merr.ErrXxx` typed +wire error, easy to misuse. + +The current code instead encodes the signal in the return value: + +```go +// meta/resource_manager.go +func (rm *ResourceManager) CheckIfResourceGroupAddable(...) (ignored bool, err error) { + if proto.Equal(rm.groups[rgName].GetConfig(), cfg) { + return true, nil // idempotent no-op + } + ... +} + +// querycoordv2/ddl_callbacks_alter_resource_group.go (broadcaster) +func (s *Server) broadcastCreateResourceGroup(...) (ignored bool, err error) { + if ignored, err := s.meta.CheckIfResourceGroupAddable(...); err != nil || ignored { + return ignored, err + } + ... +} + +// querycoordv2/services.go (RPC handler) +ignored, err := s.broadcastCreateResourceGroup(ctx, req) +if err != nil { return merr.Status(err), nil } +if ignored { return merr.Success(), nil } +``` + +No sentinel crosses the package boundary; the signal travels via a +structured return value. This is the preferred pattern for any new +cross-package idempotency case. + +## Current state (audit done on err-std-04-coord branch, 2026-05-19) + +Across `internal/{datacoord,rootcoord,querycoordv2}` there are 28 +`errors.New(...)` sentinels. Their fates: + +| Kind | Count | Examples | Status | +|---|---|---|---| +| Idempotency: caught → `merr.Success()` | 13 catch sites, ~12 distinct sentinels | `errIgnoredAlterAlias`, `errIgnoredCreateCollection`, `errReleaseCollectionNotLoaded`, `errUserNotFound`, ... | ✅ compliant | +| Caught → translated to typed merr | 3 catch sites (`errUserAlreadyExists`, `errRoleAlreadyExists`, `errRoleNotExists`) | client gets `WrapErrParameterInvalidMsg(...)` or `WrapErrServiceInternalMsg(...)` | ✅ compliant (1100 / 5) | +| Background-only (never enter an RPC handler) | 5 (`errFull`, `errNoSuchElement`, `errNodeNotEnough`, `errDisposed`, `errTypeNotFound`) | compaction queue / resource observer / session lifecycle / checker registry | ✅ compliant | +| Cross-package idempotency via `(ignored bool, err error)` signature | 1 (resource group create/drop) | meta layer returns `ignored=true`; querycoordv2 RPC handler translates to `merr.Success()` — no sentinel crosses package | ✅ compliant (refactored from exported `ErrResourceGroupOperationIgnored` in this branch) | +| Dead code (function with 0 callers) | 3 (`errNilResponse`, `errNilStatusResponse`, `errUnknownResponseType` — all only used by `VerifyResponse` in `datacoord/util.go`) | safe to delete | 🪦 cleanup candidate | +| ~~**Violation**: escapes RPC handler with no catch~~ (resolved) | 3 (`errEmptyUsername`, `errEmptyRoleName`, `errEmptyPrivilegeGroupName`) | origin sites in `meta_table.go` now emit `WrapErrParameterInvalidMsg` directly; the bare sentinels are gone | ✅ resolved | +| ~~**Semantic miscategorization**: caught and wrapped with the wrong typed merr code~~ (resolved) | 1 (`errTypeNotFound` in `ops_services.go:87,101`) | invalid `CheckerID` from the client now wrapped as `WrapErrParameterInvalidMsg` (code 1100), was `WrapErrServiceInternal` (code 5) | ✅ resolved | + +### Cleanup status + +1. ✅ Done — `errEmptyUsername` / `errEmptyRoleName` / `errEmptyPrivilegeGroupName` + at the origin sites in `internal/rootcoord/meta_table.go` now emit + `merr.WrapErrParameterInvalidMsg("username is empty")` etc. directly; the bare + sentinels no longer exist. +2. ✅ Done — `errTypeNotFound` at the catcher in + `internal/querycoordv2/ops_services.go:87,101` is now wrapped as + `merr.WrapErrParameterInvalidMsg("invalid checker type %d: %v", req.CheckerID, err)`. +3. Not done — delete `VerifyResponse` and its three dead-code sentinels + (`errNilResponse`, `errNilStatusResponse`, `errUnknownResponseType`). + +## Future linter ideas + +Three candidates, in order of how cheap they are to implement and how +hard the enforcement is. **Tier 1.5's return form is now implemented (see +below); Tier 1 and Tier 2 remain a design queue.** + +### Tier 1 — exported-sentinel ban (1 hour to write) + +The simplest rule: **`internal/...` packages may not declare exported +`var Err\w+ = errors.New(...)`.** Scan all `internal/...` *.go files, +fail CI if any match. Two paths to fix a violation: + +1. Lowercase it (`var errXxx = errors.New(...)`) — only callable inside the + same package. If the lint fails because a cross-package caller needs the + signal, see fix 2. +2. Refactor the API so the signal travels via a return value + (e.g. add `ignored bool` to the return tuple) and delete the sentinel. + +This makes Go visibility itself the enforcement mechanism: anything that +needs to look like `merr.ErrXxx` to a reviewer can only exist in +`pkg/util/merr`. Internal sentinels stay quietly lowercase in their owning +package. + +Lowercase sentinels (`var errXxx = errors.New(...)`) inside `internal/...` +are still encouraged to carry an `INTERNAL: ...` doc comment for reviewer +context, but it's not enforced — the visibility rule already prevents the +worst-case (`Err*` collision with `merr.ErrXxx`). + +### Tier 1.5 — bare-usage ban (1 hour grep, half-day AST for 100% precision) + +> **Status — return form implemented.** The **return** form of this ban is now +> enforced by a `gocritic`/`ruleguard` rule (`rawmerrerror` in `rules.go`), run +> under `make verifiers`: it rejects `return errors.New / fmt.Errorf / +> errors.Errorf` from function bodies (package-level sentinels, `cmd/`, `tests/`, +> codegen and the walimpls harness exempt). The day-to-day guide is +> [error_handling_guide.md](./error_handling_guide.md). The **no-exceptions** +> form below (local `:=`, `panic(...)`, function argument) is *not* covered: +> ruleguard's DSL cannot match "a call anywhere in a function body but not in a +> `ValueSpec`", so the full ban still needs the AST-based Tier 2 linter. + +**Hardest enforcement, no exceptions.** `internal/...` packages may not +use `errors.New(...)` or `errors.Errorf(...)` **inline inside a function +body**. The only legal site for these calls is a package-level +`var = errors.New(...)` (sentinel declaration). + +Allow/deny matrix: + +| Form | Location | Verdict | +|---|---|---| +| `var errInvalid = errors.New("invalid")` | package-level (file top / `var` block) | ✅ allowed | +| `var ( errA = ...; errB = ... )` | package-level `var` block | ✅ allowed | +| `return errors.New(...)` | function body | ❌ banned | +| `x := errors.New(...)` | function body local | ❌ banned | +| `panic(errors.New(...))` | function body | ❌ banned | +| `foo(errors.New(...))` | function body argument | ❌ banned | + +**Why no exceptions** (even for "local break signal" / "log-only" / +"panic-bound" cases that look harmless today): + +- Today's local var can be hoisted to package-level by tomorrow's refactor + and silently start crossing boundaries. +- A linter with exceptions needs AST-level wire-reachability analysis + (expensive); a no-exception linter is one grep. +- Forces authors to use the right primitive instead of `errors.New` as + a universal escape hatch: + - **break signal from a callback** → define a `type doneSignal struct{}` + that implements `error` and use `errors.As`. Intent is now in the type, + not in a string-keyed sentinel. + - **"unreachable" assertion** → just `panic(...)`. If caller already + does `if err != nil { panic(err) }`, fold it into the callee. + - **input validation / config validation** → `merr.WrapErrParameterInvalidMsg(...)` + or `status.NewInvalidArgument(...)` depending on layer. + +Implementation: grep version covers ~95% true violations in ~1 hour. +AST version (go/analysis) covers the edge cases (e.g. `init()` body +assigning to a package var) but needs ~half a day. Start with grep, +upgrade if false-positive rate exceeds 5%. + +```bash +# grep skeleton +grep -rnE 'errors\.(New|Errorf)\(' internal/ --include='*.go' \ + | grep -v _test.go \ + | grep -vE ':[0-9]+:\s*(var\s+)?[A-Za-z_]+\s*=\s*errors\.(New|Errorf)' \ + | grep -vE ':[0-9]+:\s*[A-Za-z_]+\s+(\w+\s+)?=\s*errors\.(New|Errorf)' +# Any remaining line = violation +``` + +A `//nolint:err-bare` escape valve with a required justification comment +handles the genuine outliers (a few `init()` patterns, embedded `errors.Mark` +usage, etc.). + +### Tier 2 — escape-path linter (~1 day, go/analysis based) + +For every gRPC handler method (anything matching the +`internal/{rootcoord,datacoord,querycoordv2}/services.go,root_coord.go,*_handler.go` +pattern, return type `(*proto.XxxResponse, error)` or `(*commonpb.Status, +error)`), trace the err-return data-flow. Any error that: + +- transitively originates from an `INTERNAL:`-tagged sentinel, **and** +- reaches a `return Status{Code: merr.Status(err)}` or `return err` without + passing through an `errors.Is(err, internalSentinelX) { ... }` branch, + +is a violation. Report file:line of the leak. + +This catches the actual invariant violation (the 3 RBAC empty sentinels +would have been flagged), not just naming hygiene. Requires AST analysis; +worth doing if the cost of one more silent `Code=1` to a client is high. + +### Tier 3 (no longer necessary if Tier 2 is in place) — wrap-rule linter + +Scan `merr\.WrapErr[A-Za-z]+Err\(err, ` (note: first arg `err`, not a +fresh string-only origination) and require the cause `err` to not itself be +a typed merr. This is what +[`feedback_merr_wrap_rule`](../../) already enforces by convention; Tier 2 +would catch the symptom (sentinel escape) as a side effect. + +## Related rules + +- `feedback_merr_wrap_rule` (this repo's collaboration memory): "Add context + to an existing err with `merr.Wrap` / `merr.Wrapf`, never with + `merr.WrapErr*Err` — the latter masks the inner typed code and + retriability (the `errors.Is` chain itself is preserved via `Unwrap()`)." +- `project_errstd_autogen_defects`: three systematic defects in the + auto-generated `errors.Wrap → merr` conversion in this branch series; + defects #2 and #3 are direct consequences of violating the rules in + this document. diff --git a/docs/developer_guides/appendix_d_error_code.md b/docs/developer_guides/appendix_d_error_code.md index ecd0d895aa..c5732b850c 100644 --- a/docs/developer_guides/appendix_d_error_code.md +++ b/docs/developer_guides/appendix_d_error_code.md @@ -1,5 +1,14 @@ ## Appendix D. Error Code +> **⚠️ Deprecated.** This appendix documents the legacy `commonpb.ErrorCode` +> enum (Milvus 2.0-era), which is no longer the source of truth for error codes. +> Current error codes are the `merr` sentinel codes (e.g. `ParameterInvalid` +> = 1100, `ServiceInternal` = 5, `FunctionFailed` = 2400) carried in +> `commonpb.Status.Code`. The canonical list is the sentinel definitions in +> [`pkg/util/merr/errors.go`](../../pkg/util/merr/errors.go); see also +> [error_handling_guide.md](../dev/error_handling_guide.md) and +> [error_sentinel_convention.md](../dev/error_sentinel_convention.md). + **ErrorCode** ```protobuf diff --git a/examples/telemetry_e2e_test/go.mod b/examples/telemetry_e2e_test/go.mod index 2499f86935..0b082707e5 100644 --- a/examples/telemetry_e2e_test/go.mod +++ b/examples/telemetry_e2e_test/go.mod @@ -2,9 +2,9 @@ module github.com/milvus-io/milvus/examples/telemetry_e2e_test go 1.26.4 require ( - github.com/milvus-io/milvus-proto/go-api/v2 v2.6.6-0.20260304062516-e7e54d966ef2 + github.com/milvus-io/milvus-proto/go-api/v2 v2.6.6-0.20260323081523-53649783989c github.com/milvus-io/milvus/client/v2 v2.6.4-0.20251104142533-a2ce70d25256 - google.golang.org/grpc v1.71.0 + google.golang.org/grpc v1.79.3 ) replace ( diff --git a/examples/telemetry_e2e_test/go.sum b/examples/telemetry_e2e_test/go.sum index aba89031b5..b39b56c56f 100644 --- a/examples/telemetry_e2e_test/go.sum +++ b/examples/telemetry_e2e_test/go.sum @@ -1,2 +1,4 @@ github.com/milvus-io/milvus-proto/go-api/v2 v2.6.6-0.20260304062516-e7e54d966ef2/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs= +github.com/milvus-io/milvus-proto/go-api/v2 v2.6.6-0.20260323081523-53649783989c/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= diff --git a/internal/agg/aggregate.go b/internal/agg/aggregate.go index 1b912a5760..64c88eb8ed 100644 --- a/internal/agg/aggregate.go +++ b/internal/agg/aggregate.go @@ -8,6 +8,7 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/pkg/v3/proto/planpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) const ( @@ -57,7 +58,7 @@ func isSupportedAggregateName(aggregateName string) bool { func NewAggregate(aggregateName string, aggFieldID int64, originalName string, fieldType schemapb.DataType) ([]AggregateBase, error) { if !isSupportedAggregateName(aggregateName) { - return nil, fmt.Errorf("invalid Aggregation operator %s", aggregateName) + return nil, merr.WrapErrParameterInvalidMsg("invalid Aggregation operator %s", aggregateName) } if err := ValidateAggFieldType(aggregateName, fieldType); err != nil { @@ -78,7 +79,7 @@ func NewAggregate(aggregateName string, aggFieldID int64, originalName string, f return []AggregateBase{&MaxAggregate{fieldID: aggFieldID, originalName: originalName}}, nil default: // should never happen due to isSupportedAggregateName check - return nil, fmt.Errorf("invalid Aggregation operator %s", aggregateName) + return nil, merr.WrapErrParameterInvalidMsg("invalid Aggregation operator %s", aggregateName) } } @@ -93,13 +94,13 @@ func FromPB(pb *planpb.Aggregate) (AggregateBase, error) { case planpb.AggregateOp_max: return &MaxAggregate{fieldID: pb.GetFieldId()}, nil default: - return nil, fmt.Errorf("invalid Aggregation operator %d", pb.Op) + return nil, merr.WrapErrParameterInvalidMsg("invalid Aggregation operator %d", pb.Op) } } func AccumulateFieldValue(target *FieldValue, new *FieldValue) error { if target == nil || new == nil { - return fmt.Errorf("target or new field value is nil") + return merr.WrapErrServiceInternalMsg("target or new field value is nil") } // Skip null values during accumulation @@ -121,7 +122,7 @@ func AccumulateFieldValue(target *FieldValue, new *FieldValue) error { } // Verify types match if reflect.TypeOf(target.val) != reflect.TypeOf(new.val) { - return fmt.Errorf("type mismatch: target is %T, new is %T", target.val, new.val) + return merr.WrapErrParameterInvalidMsg("type mismatch: target is %T, new is %T", target.val, new.val) } switch target.val.(type) { @@ -136,7 +137,7 @@ func AccumulateFieldValue(target *FieldValue, new *FieldValue) error { case float64: target.val = target.val.(float64) + new.val.(float64) default: - return fmt.Errorf("unsupported type: %T", target.val) + return merr.WrapErrParameterInvalidMsg("unsupported type: %T", target.val) } return nil } @@ -255,17 +256,17 @@ func (bucket *Bucket) RowCount() int { func (bucket *Bucket) Accumulate(row *Row, idx int, keyCount int, aggs []AggregateBase) error { if idx >= len(bucket.rows) || idx < 0 { - return fmt.Errorf("wrong idx:%d for bucket", idx) + return merr.WrapErrParameterInvalidMsg("wrong idx:%d for bucket", idx) } targetRow := bucket.rows[idx] if targetRow == nil { - return fmt.Errorf("nil row at the target idx:%d, cannot accumulate the row", idx) + return merr.WrapErrServiceInternalMsg("nil row at the target idx:%d, cannot accumulate the row", idx) } if row.ColumnCount() != targetRow.ColumnCount() { - return fmt.Errorf("column count:%d in the row must be equal to the target row:%d", row.ColumnCount(), bucket.rows[idx].ColumnCount()) + return merr.WrapErrParameterInvalidMsg("column count:%d in the row must be equal to the target row:%d", row.ColumnCount(), bucket.rows[idx].ColumnCount()) } if row.ColumnCount() != keyCount+len(aggs) { - return fmt.Errorf("column count:%d in the row must be sum of keyCount:%d and the number of aggs:%d", row.ColumnCount(), keyCount, len(aggs)) + return merr.WrapErrParameterInvalidMsg("column count:%d in the row must be sum of keyCount:%d and the number of aggs:%d", row.ColumnCount(), keyCount, len(aggs)) } for col := keyCount; col < row.ColumnCount(); col++ { targetRow.UpdateFieldValue(row, col, aggs[col-keyCount]) diff --git a/internal/agg/aggregate_reducer.go b/internal/agg/aggregate_reducer.go index 60fce2dd1b..0ac5d2d94c 100644 --- a/internal/agg/aggregate_reducer.go +++ b/internal/agg/aggregate_reducer.go @@ -2,7 +2,6 @@ package agg import ( "context" - "fmt" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" typeutil2 "github.com/milvus-io/milvus/internal/util/typeutil" @@ -88,15 +87,15 @@ func (reducer *GroupAggReducer) EmptyAggResult() (*AggregationResult, error) { } else { field, err := helper.GetFieldFromID(agg.GetFieldId()) if err != nil { - return nil, fmt.Errorf("failed to get field schema for aggregate fieldID %d: %w", agg.GetFieldId(), err) + return nil, merr.Wrapf(err, "failed to get field schema for aggregate fieldID %d", agg.GetFieldId()) } resultType, err := getAggregateResultType(agg.GetOp(), field.GetDataType()) if err != nil { - return nil, fmt.Errorf("failed to get result type for aggregate fieldID %d: %w", agg.GetFieldId(), err) + return nil, merr.Wrapf(err, "failed to get result type for aggregate fieldID %d", agg.GetFieldId()) } emptyFieldData, err := genEmptyFieldDataByType(resultType) if err != nil { - return nil, fmt.Errorf("failed to generate empty field data for result type %s: %w", resultType.String(), err) + return nil, merr.Wrapf(err, "failed to generate empty field data for result type %s", resultType.String()) } ret.fieldDatas = append(ret.fieldDatas, emptyFieldData) } @@ -160,7 +159,7 @@ func genEmptyFieldDataByType(dataType schemapb.DataType) (*schemapb.FieldData, e return genEmptyLongFieldData(dataType, []int64{0}), nil default: // For other types, try to use the original field's GenEmptyFieldData - return nil, fmt.Errorf("unsupported data type for aggregate result: %s", dataType.String()) + return nil, merr.WrapErrParameterInvalidMsg("unsupported data type for aggregate result: %s", dataType.String()) } } @@ -187,10 +186,10 @@ func getAggregateResultType(op planpb.AggregateOp, inputType schemapb.DataType) case schemapb.DataType_Float, schemapb.DataType_Double: return schemapb.DataType_Double, nil default: - return schemapb.DataType_None, fmt.Errorf("unsupported input type %s for sum aggregation", inputType.String()) + return schemapb.DataType_None, merr.WrapErrParameterInvalidMsg("unsupported input type %s for sum aggregation", inputType.String()) } default: - return schemapb.DataType_None, fmt.Errorf("unknown aggregate operator: %d", op) + return schemapb.DataType_None, merr.WrapErrParameterInvalidMsg("unknown aggregate operator: %d", op) } } @@ -201,12 +200,12 @@ func getAggregateResultType(op planpb.AggregateOp, inputType schemapb.DataType) // 3. Each fieldData's Type matches the expected type from schema func (reducer *GroupAggReducer) validateAggregationResults(results []*AggregationResult) error { if reducer.schema == nil { - return fmt.Errorf("schema is nil, cannot validate field types") + return merr.WrapErrParameterInvalidMsg("schema is nil, cannot validate field types") } helper, err := typeutil.CreateSchemaHelper(reducer.schema) if err != nil { - return fmt.Errorf("failed to create schema helper: %w", err) + return merr.Wrap(err, "failed to create schema helper") } numGroupingKeys := len(reducer.groupByFieldIds) @@ -220,7 +219,7 @@ func (reducer *GroupAggReducer) validateAggregationResults(results []*Aggregatio for _, fieldID := range reducer.groupByFieldIds { field, err := helper.GetFieldFromID(fieldID) if err != nil { - return fmt.Errorf("failed to get field schema for groupBy fieldID %d: %w", fieldID, err) + return merr.Wrapf(err, "failed to get field schema for groupBy fieldID %d", fieldID) } expectedTypes = append(expectedTypes, field.GetDataType()) } @@ -234,11 +233,11 @@ func (reducer *GroupAggReducer) validateAggregationResults(results []*Aggregatio } else { field, err := helper.GetFieldFromID(agg.GetFieldId()) if err != nil { - return fmt.Errorf("failed to get field schema for aggregate fieldID %d: %w", agg.GetFieldId(), err) + return merr.Wrapf(err, "failed to get field schema for aggregate fieldID %d", agg.GetFieldId()) } expectedType, err = getAggregateResultType(agg.GetOp(), field.GetDataType()) if err != nil { - return fmt.Errorf("failed to get aggregate result type for aggregate fieldID %d: %w", agg.GetFieldId(), err) + return merr.Wrapf(err, "failed to get aggregate result type for aggregate fieldID %d", agg.GetFieldId()) } } expectedTypes = append(expectedTypes, expectedType) @@ -247,27 +246,27 @@ func (reducer *GroupAggReducer) validateAggregationResults(results []*Aggregatio // Validate each result for resultIdx, result := range results { if result == nil { - return fmt.Errorf("result at index %d is nil", resultIdx) + return merr.WrapErrServiceInternalMsg("result at index %d is nil", resultIdx) } fieldDatas := result.GetFieldDatas() // Check 1: fieldDatas length if len(fieldDatas) != expectedColumnCount { - return fmt.Errorf("result at index %d has fieldDatas length %d, expected %d (numGroupingKeys=%d, numAggs=%d)", + return merr.WrapErrServiceInternalMsg("result at index %d has fieldDatas length %d, expected %d (numGroupingKeys=%d, numAggs=%d)", resultIdx, len(fieldDatas), expectedColumnCount, numGroupingKeys, numAggs) } // Check 2: no nil fieldData and Check 3: type matching for colIdx, fieldData := range fieldDatas { if fieldData == nil { - return fmt.Errorf("result at index %d has nil fieldData at column %d", resultIdx, colIdx) + return merr.WrapErrServiceInternalMsg("result at index %d has nil fieldData at column %d", resultIdx, colIdx) } expectedType := expectedTypes[colIdx] actualType := fieldData.GetType() if actualType != expectedType { - return fmt.Errorf("result at index %d, column %d has type %s, expected %s", + return merr.WrapErrServiceInternalMsg("result at index %d, column %d has type %s, expected %s", resultIdx, colIdx, schemapb.DataType_name[int32(actualType)], schemapb.DataType_name[int32(expectedType)]) } } @@ -352,15 +351,15 @@ func (reducer *GroupAggReducer) Reduce(ctx context.Context, results []*Aggregati limitReached := false for _, result := range results { if result == nil { - return nil, fmt.Errorf("input result from any sources cannot be nil") + return nil, merr.WrapErrServiceInternalMsg("input result from any sources cannot be nil") } reducedResult.allRetrieveCount += result.GetAllRetrieveCount() fieldDatas := result.GetFieldDatas() if outputColumnCount != len(fieldDatas) { - return nil, fmt.Errorf("retrieved results from different segments have different size of columns") + return nil, merr.WrapErrServiceInternalMsg("retrieved results from different segments have different size of columns") } if outputColumnCount == 0 { - return nil, fmt.Errorf("retrieved results have no column data") + return nil, merr.WrapErrServiceInternalMsg("retrieved results have no column data") } rowCount := -1 for i := 0; i < outputColumnCount; i++ { @@ -374,11 +373,11 @@ func (reducer *GroupAggReducer) Reduce(ctx context.Context, results []*Aggregati rowCount = hashers[i].RowCount() } else if i < numGroupingKeys { if rowCount != hashers[i].RowCount() { - return nil, fmt.Errorf("field data:%d for different columns have different row count, %d vs %d, wrong state", + return nil, merr.WrapErrServiceInternalMsg("field data:%d for different columns have different row count, %d vs %d, wrong state", i, rowCount, hashers[i].RowCount()) } } else if rowCount != accumulators[i-numGroupingKeys].RowCount() { - return nil, fmt.Errorf("field data:%d for different columns have different row count, %d vs %d, wrong state", + return nil, merr.WrapErrServiceInternalMsg("field data:%d for different columns have different row count, %d vs %d, wrong state", i, rowCount, accumulators[i-numGroupingKeys].RowCount()) } } @@ -443,9 +442,9 @@ func (reducer *GroupAggReducer) Reduce(ctx context.Context, results []*Aggregati } } if totalGroupCount > maxGroupByGroups { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("GROUP BY produced too many groups (%d). "+ + return nil, merr.WrapErrParameterInvalidMsg("GROUP BY produced too many groups (%d). "+ "Add filters or increase common.groupBy.maxGroups (current: %d)", - totalGroupCount, maxGroupByGroups)) + totalGroupCount, maxGroupByGroups) } // Don't guarantee specific groups to be returned before milvus support order by } @@ -478,7 +477,7 @@ func SegcoreResults2AggResult(results []*segcorepb.RetrieveResults) ([]*Aggregat aggResults := make([]*AggregationResult, len(results)) for i := 0; i < len(results); i++ { if results[i] == nil { - return nil, fmt.Errorf("input segcore query results from any sources cannot be nil") + return nil, merr.WrapErrServiceInternalMsg("input segcore query results from any sources cannot be nil") } fieldsData := results[i].GetFieldsData() allRetrieveCount := results[i].GetAllRetrieveCount() diff --git a/internal/agg/aggregate_util.go b/internal/agg/aggregate_util.go index 843c580ff5..09b791c388 100644 --- a/internal/agg/aggregate_util.go +++ b/internal/agg/aggregate_util.go @@ -9,6 +9,7 @@ import ( "unsafe" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) func NewFieldAccessor(fieldType schemapb.DataType) (FieldAccessor, error) { @@ -28,7 +29,7 @@ func NewFieldAccessor(fieldType schemapb.DataType) (FieldAccessor, error) { case schemapb.DataType_Double: return newFloat64FieldAccessor(), nil default: - return nil, fmt.Errorf("unsupported data type for hasher") + return nil, merr.WrapErrParameterInvalidMsg("unsupported data type for hasher") } } @@ -410,7 +411,7 @@ func AssembleSingleValue(fv *FieldValue, fieldData *schemapb.FieldData) error { case schemapb.DataType_VarChar, schemapb.DataType_String: fieldData.GetScalars().GetStringData().Data = append(fieldData.GetScalars().GetStringData().GetData(), "") default: - return fmt.Errorf("unsupported DataType:%d", fieldData.GetType()) + return merr.WrapErrParameterInvalidMsg("unsupported DataType:%d", fieldData.GetType()) } return nil } @@ -421,47 +422,47 @@ func AssembleSingleValue(fv *FieldValue, fieldData *schemapb.FieldData) error { case schemapb.DataType_Bool: boolVal, ok := val.(bool) if !ok { - return fmt.Errorf("type assertion failed: expected bool, got %T", val) + return merr.WrapErrServiceInternalMsg("type assertion failed: expected bool, got %T", val) } fieldData.GetScalars().GetBoolData().Data = append(fieldData.GetScalars().GetBoolData().GetData(), boolVal) case schemapb.DataType_Int8, schemapb.DataType_Int16, schemapb.DataType_Int32: intVal, ok := val.(int32) if !ok { - return fmt.Errorf("type assertion failed: expected int32, got %T", val) + return merr.WrapErrServiceInternalMsg("type assertion failed: expected int32, got %T", val) } fieldData.GetScalars().GetIntData().Data = append(fieldData.GetScalars().GetIntData().GetData(), intVal) case schemapb.DataType_Int64: int64Val, ok := val.(int64) if !ok { - return fmt.Errorf("type assertion failed: expected int64, got %T", val) + return merr.WrapErrServiceInternalMsg("type assertion failed: expected int64, got %T", val) } fieldData.GetScalars().GetLongData().Data = append(fieldData.GetScalars().GetLongData().GetData(), int64Val) case schemapb.DataType_Timestamptz: timestampVal, ok := val.(int64) if !ok { - return fmt.Errorf("type assertion failed: expected int64 for Timestamptz, got %T", val) + return merr.WrapErrServiceInternalMsg("type assertion failed: expected int64 for Timestamptz, got %T", val) } fieldData.GetScalars().GetTimestamptzData().Data = append(fieldData.GetScalars().GetTimestamptzData().GetData(), timestampVal) case schemapb.DataType_Float: floatVal, ok := val.(float32) if !ok { - return fmt.Errorf("type assertion failed: expected float32, got %T", val) + return merr.WrapErrServiceInternalMsg("type assertion failed: expected float32, got %T", val) } fieldData.GetScalars().GetFloatData().Data = append(fieldData.GetScalars().GetFloatData().GetData(), floatVal) case schemapb.DataType_Double: doubleVal, ok := val.(float64) if !ok { - return fmt.Errorf("type assertion failed: expected float64, got %T", val) + return merr.WrapErrServiceInternalMsg("type assertion failed: expected float64, got %T", val) } fieldData.GetScalars().GetDoubleData().Data = append(fieldData.GetScalars().GetDoubleData().GetData(), doubleVal) case schemapb.DataType_VarChar, schemapb.DataType_String: stringVal, ok := val.(string) if !ok { - return fmt.Errorf("type assertion failed: expected string, got %T", val) + return merr.WrapErrServiceInternalMsg("type assertion failed: expected string, got %T", val) } fieldData.GetScalars().GetStringData().Data = append(fieldData.GetScalars().GetStringData().GetData(), stringVal) default: - return fmt.Errorf("unsupported DataType:%d", fieldData.GetType()) + return merr.WrapErrParameterInvalidMsg("unsupported DataType:%d", fieldData.GetType()) } return nil } @@ -544,13 +545,13 @@ func NewAggregationFieldMap(originalUserOutputFields []string, groupByFields []s // 2. Global aggregation (no GROUP BY): output_fields can only contain aggregation expressions // (e.g., "SELECT count(*), int64 FROM t" is invalid SQL — cannot mix aggregates with raw columns) if numGroupingKeys > 0 { - return nil, fmt.Errorf( + return nil, merr.WrapErrParameterInvalidMsg( "output field '%s' is not allowed: when using GROUP BY, output_fields can only contain "+ "group_by fields (%v) or aggregation expressions", outputField, groupByFields, ) } - return nil, fmt.Errorf( + return nil, merr.WrapErrParameterInvalidMsg( "output field '%s' is not allowed: when using aggregation functions (e.g., count(*)), "+ "output_fields can only contain aggregation expressions, not regular columns", outputField, @@ -566,14 +567,14 @@ func NewAggregationFieldMap(originalUserOutputFields []string, groupByFields []s // and returns a new Double FieldData containing the average values. func ComputeAvgFromSumAndCount(sumFieldData *schemapb.FieldData, countFieldData *schemapb.FieldData) (*schemapb.FieldData, error) { if sumFieldData == nil || countFieldData == nil { - return nil, fmt.Errorf("sumFieldData and countFieldData cannot be nil") + return nil, merr.WrapErrServiceInternalMsg("sumFieldData and countFieldData cannot be nil") } sumType := sumFieldData.GetType() countType := countFieldData.GetType() if countType != schemapb.DataType_Int64 { - return nil, fmt.Errorf("count field must be Int64 type, got %s", countType.String()) + return nil, merr.WrapErrParameterInvalidMsg("count field must be Int64 type, got %s", countType.String()) } countData := countFieldData.GetScalars().GetLongData().GetData() @@ -598,27 +599,27 @@ func ComputeAvgFromSumAndCount(sumFieldData *schemapb.FieldData, countFieldData case schemapb.DataType_Int64: sumData := sumFieldData.GetScalars().GetLongData().GetData() if len(sumData) != rowCount { - return nil, fmt.Errorf("sum and count field data must have the same length, got sum:%d, count:%d", len(sumData), rowCount) + return nil, merr.WrapErrParameterInvalidMsg("sum and count field data must have the same length, got sum:%d, count:%d", len(sumData), rowCount) } for i := 0; i < rowCount; i++ { if countData[i] == 0 { - return nil, fmt.Errorf("division by zero: count is 0 at row %d", i) + return nil, merr.WrapErrParameterInvalidMsg("division by zero: count is 0 at row %d", i) } resultData = append(resultData, float64(sumData[i])/float64(countData[i])) } case schemapb.DataType_Double: sumData := sumFieldData.GetScalars().GetDoubleData().GetData() if len(sumData) != rowCount { - return nil, fmt.Errorf("sum and count field data must have the same length, got sum:%d, count:%d", len(sumData), rowCount) + return nil, merr.WrapErrParameterInvalidMsg("sum and count field data must have the same length, got sum:%d, count:%d", len(sumData), rowCount) } for i := 0; i < rowCount; i++ { if countData[i] == 0 { - return nil, fmt.Errorf("division by zero: count is 0 at row %d", i) + return nil, merr.WrapErrParameterInvalidMsg("division by zero: count is 0 at row %d", i) } resultData = append(resultData, sumData[i]/float64(countData[i])) } default: - return nil, fmt.Errorf("unsupported sum field type for avg computation: %s", sumType.String()) + return nil, merr.WrapErrParameterInvalidMsg("unsupported sum field type for avg computation: %s", sumType.String()) } result.GetScalars().GetDoubleData().Data = resultData diff --git a/internal/agg/operators.go b/internal/agg/operators.go index a0ea4fef14..5437ad34df 100644 --- a/internal/agg/operators.go +++ b/internal/agg/operators.go @@ -1,14 +1,13 @@ package agg import ( - "fmt" - "github.com/milvus-io/milvus/pkg/v3/proto/planpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) func terminateSingleSlot(slots []*FieldValue) (any, error) { if len(slots) != 1 { - return nil, fmt.Errorf("aggregate expects 1 accumulator slot, got %d", len(slots)) + return nil, merr.WrapErrParameterInvalidMsg("aggregate expects 1 accumulator slot, got %d", len(slots)) } if slots[0] == nil || slots[0].IsNull() { return nil, nil @@ -18,7 +17,7 @@ func terminateSingleSlot(slots []*FieldValue) (any, error) { func terminateAvg(slots []*FieldValue) (any, error) { if len(slots) != 2 { - return nil, fmt.Errorf("avg expects 2 accumulator slots, got %d", len(slots)) + return nil, merr.WrapErrParameterInvalidMsg("avg expects 2 accumulator slots, got %d", len(slots)) } sum := slots[0] count := slots[1] @@ -52,7 +51,7 @@ func fieldValueToFloat64(v any) (float64, error) { case float64: return value, nil default: - return 0, fmt.Errorf("avg expects numeric accumulator, got %T", v) + return 0, merr.WrapErrParameterInvalidMsg("avg expects numeric accumulator, got %T", v) } } @@ -80,7 +79,7 @@ func (sum *SumAggregate) NewState() []*FieldValue { func (sum *SumAggregate) UpdateState(slots []*FieldValue, new *FieldValue) error { if len(slots) != 1 { - return fmt.Errorf("aggregate expects 1 accumulator slot, got %d", len(slots)) + return merr.WrapErrParameterInvalidMsg("aggregate expects 1 accumulator slot, got %d", len(slots)) } return sum.Update(slots[0], new) } @@ -121,7 +120,7 @@ func (count *CountAggregate) NewState() []*FieldValue { func (count *CountAggregate) UpdateState(slots []*FieldValue, new *FieldValue) error { if len(slots) != 1 { - return fmt.Errorf("aggregate expects 1 accumulator slot, got %d", len(slots)) + return merr.WrapErrParameterInvalidMsg("aggregate expects 1 accumulator slot, got %d", len(slots)) } if new == nil || new.IsNull() { return nil @@ -166,7 +165,7 @@ func (avg *AvgAggregate) Name() string { } func (avg *AvgAggregate) Update(target *FieldValue, new *FieldValue) error { - return fmt.Errorf("avg aggregate updates require aggregate state slots") + return merr.WrapErrServiceInternalMsg("avg aggregate updates require aggregate state slots") } func (avg *AvgAggregate) NewState() []*FieldValue { @@ -175,7 +174,7 @@ func (avg *AvgAggregate) NewState() []*FieldValue { func (avg *AvgAggregate) UpdateState(slots []*FieldValue, new *FieldValue) error { if len(slots) != 2 { - return fmt.Errorf("avg expects 2 accumulator slots, got %d", len(slots)) + return merr.WrapErrParameterInvalidMsg("avg expects 2 accumulator slots, got %d", len(slots)) } if err := avg.sum.Update(slots[0], new); err != nil { return err @@ -204,7 +203,7 @@ func (avg *AvgAggregate) OriginalName() string { func updateOrderedState(target *FieldValue, new *FieldValue, op string) error { if target == nil || new == nil { - return fmt.Errorf("target or new field value is nil") + return merr.WrapErrServiceInternalMsg("target or new field value is nil") } if new.IsNull() { return nil @@ -234,41 +233,41 @@ func shouldReplaceOrderedValue(target, new any, op string) (bool, error) { case int: newVal, ok := new.(int) if !ok { - return false, fmt.Errorf("type mismatch: target is int, new is %T", new) + return false, merr.WrapErrParameterInvalidMsg("type mismatch: target is int, new is %T", new) } return shouldReplaceOrdered(newVal, targetVal, op), nil case int32: newVal, ok := new.(int32) if !ok { - return false, fmt.Errorf("type mismatch: target is int32, new is %T", new) + return false, merr.WrapErrParameterInvalidMsg("type mismatch: target is int32, new is %T", new) } return shouldReplaceOrdered(newVal, targetVal, op), nil case int64: newVal, ok := new.(int64) if !ok { - return false, fmt.Errorf("type mismatch: target is int64, new is %T", new) + return false, merr.WrapErrParameterInvalidMsg("type mismatch: target is int64, new is %T", new) } return shouldReplaceOrdered(newVal, targetVal, op), nil case float32: newVal, ok := new.(float32) if !ok { - return false, fmt.Errorf("type mismatch: target is float32, new is %T", new) + return false, merr.WrapErrParameterInvalidMsg("type mismatch: target is float32, new is %T", new) } return shouldReplaceOrdered(newVal, targetVal, op), nil case float64: newVal, ok := new.(float64) if !ok { - return false, fmt.Errorf("type mismatch: target is float64, new is %T", new) + return false, merr.WrapErrParameterInvalidMsg("type mismatch: target is float64, new is %T", new) } return shouldReplaceOrdered(newVal, targetVal, op), nil case string: newVal, ok := new.(string) if !ok { - return false, fmt.Errorf("type mismatch: target is string, new is %T", new) + return false, merr.WrapErrParameterInvalidMsg("type mismatch: target is string, new is %T", new) } return shouldReplaceOrdered(newVal, targetVal, op), nil default: - return false, fmt.Errorf("unsupported type for %s aggregation: %T", op, target) + return false, merr.WrapErrParameterInvalidMsg("unsupported type for %s aggregation: %T", op, target) } } @@ -299,7 +298,7 @@ func (min *MinAggregate) NewState() []*FieldValue { func (min *MinAggregate) UpdateState(slots []*FieldValue, new *FieldValue) error { if len(slots) != 1 { - return fmt.Errorf("aggregate expects 1 accumulator slot, got %d", len(slots)) + return merr.WrapErrParameterInvalidMsg("aggregate expects 1 accumulator slot, got %d", len(slots)) } return min.Update(slots[0], new) } @@ -339,7 +338,7 @@ func (max *MaxAggregate) NewState() []*FieldValue { func (max *MaxAggregate) UpdateState(slots []*FieldValue, new *FieldValue) error { if len(slots) != 1 { - return fmt.Errorf("aggregate expects 1 accumulator slot, got %d", len(slots)) + return merr.WrapErrParameterInvalidMsg("aggregate expects 1 accumulator slot, got %d", len(slots)) } return max.Update(slots[0], new) } diff --git a/internal/agg/type_check.go b/internal/agg/type_check.go index 147f811290..46f9bbe7af 100644 --- a/internal/agg/type_check.go +++ b/internal/agg/type_check.go @@ -1,9 +1,8 @@ package agg import ( - "fmt" - "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) func isSupportedAggFieldType(aggregateName string, dt schemapb.DataType) bool { @@ -51,7 +50,7 @@ func isSupportedAggFieldType(aggregateName string, dt schemapb.DataType) bool { // Note: operator name is expected to be normalized (lowercase) by caller. func ValidateAggFieldType(aggregateName string, dt schemapb.DataType) error { if !isSupportedAggFieldType(aggregateName, dt) { - return fmt.Errorf("aggregation operator %s does not support data type %s", aggregateName, dt.String()) + return merr.WrapErrParameterInvalidMsg("aggregation operator %s does not support data type %s", aggregateName, dt.String()) } return nil } diff --git a/internal/allocator/cached_allocator.go b/internal/allocator/cached_allocator.go index a6ba2f06ad..82af39064b 100644 --- a/internal/allocator/cached_allocator.go +++ b/internal/allocator/cached_allocator.go @@ -18,14 +18,13 @@ package allocator import ( "context" - "fmt" "sync" "time" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) const ( @@ -249,10 +248,9 @@ func (ta *CachedAllocator) finishSyncRequest() { func (ta *CachedAllocator) failRemainRequest() { var err error if ta.SyncErr != nil { - err = fmt.Errorf("%s failRemainRequest err:%w", ta.Role, ta.SyncErr) + err = merr.Wrapf(ta.SyncErr, "%s failRemainRequest", ta.Role) } else { - errMsg := fmt.Sprintf("%s failRemainRequest unexpected error", ta.Role) - err = errors.New(errMsg) + err = merr.WrapErrServiceInternalMsg("%s failRemainRequest unexpected error", ta.Role) } if len(ta.ToDoReqs) > 0 { log.Warn("Allocator has some reqs to fail", @@ -290,8 +288,7 @@ func (ta *CachedAllocator) Close() { ta.CancelFunc() ta.wg.Wait() ta.TChan.Close() - errMsg := fmt.Sprintf("%s is closing", ta.Role) - ta.revokeRequest(errors.New(errMsg)) + ta.revokeRequest(merr.WrapErrServiceInternalMsg("%s is closing", ta.Role)) } // CleanCache is used to force synchronize with global allocator. diff --git a/internal/allocator/id_allocator.go b/internal/allocator/id_allocator.go index bd97d3fcca..9d0f0940bf 100644 --- a/internal/allocator/id_allocator.go +++ b/internal/allocator/id_allocator.go @@ -18,14 +18,12 @@ package allocator import ( "context" - "fmt" "time" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus/pkg/v3/proto/rootcoordpb" "github.com/milvus-io/milvus/pkg/v3/util/commonpbutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) const ( @@ -101,7 +99,7 @@ func (ia *IDAllocator) syncID() (bool, error) { cancel() if err != nil { - return false, fmt.Errorf("syncID Failed:%w", err) + return false, merr.Wrapf(err, "syncID failed") } ia.idStart = resp.GetID() ia.idEnd = ia.idStart + int64(resp.GetCount()) @@ -148,7 +146,7 @@ func (ia *IDAllocator) AllocOne() (UniqueID, error) { // Alloc allocates the id of the count number. func (ia *IDAllocator) Alloc(count uint32) (UniqueID, UniqueID, error) { if ia.closed() { - return 0, 0, errors.New("fail to allocate ID, closed allocator") + return 0, 0, merr.WrapErrServiceInternalMsg("fail to allocate ID, closed allocator") } req := &IDRequest{BaseRequest: BaseRequest{Done: make(chan error), Valid: false}} diff --git a/internal/allocator/local_allocator.go b/internal/allocator/local_allocator.go index 606b5ad779..edea1be322 100644 --- a/internal/allocator/local_allocator.go +++ b/internal/allocator/local_allocator.go @@ -17,8 +17,9 @@ package allocator import ( - "fmt" "sync" + + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // localAllocator implements the Interface. @@ -40,12 +41,12 @@ func NewLocalAllocator(start, end int64) Interface { func (a *localAllocator) Alloc(count uint32) (int64, int64, error) { cnt := int64(count) if cnt <= 0 { - return 0, 0, fmt.Errorf("non-positive count is not allowed, count=%d", cnt) + return 0, 0, merr.WrapErrParameterInvalidMsg("non-positive count is not allowed, count=%d", cnt) } a.mu.Lock() defer a.mu.Unlock() if a.idStart+cnt > a.idEnd { - return 0, 0, fmt.Errorf("ID is exhausted, start=%d, end=%d, count=%d", a.idStart, a.idEnd, cnt) + return 0, 0, merr.WrapErrServiceInternalMsg("ID is exhausted, start=%d, end=%d, count=%d", a.idStart, a.idEnd, cnt) } start := a.idStart a.idStart += cnt diff --git a/internal/cdc/cluster/milvus_client.go b/internal/cdc/cluster/milvus_client.go index 2e4beac84b..02a8ced656 100644 --- a/internal/cdc/cluster/milvus_client.go +++ b/internal/cdc/cluster/milvus_client.go @@ -20,7 +20,6 @@ import ( "context" "crypto/tls" - "github.com/cockroachdb/errors" "go.uber.org/zap" "google.golang.org/grpc" @@ -28,6 +27,7 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb" "github.com/milvus-io/milvus/client/v2/milvusclient" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -52,7 +52,7 @@ func NewMilvusClient(ctx context.Context, cluster *commonpb.MilvusCluster) (Milv // Build TLS config from per-cluster paramtable config. tlsConfig, err := buildCDCTLSConfig(cluster.GetClusterId()) if err != nil { - return nil, errors.Wrap(err, "failed to build CDC TLS config") + return nil, merr.Wrap(err, "failed to build CDC TLS config") } if tlsConfig != nil { config.WithTLSConfig(tlsConfig) @@ -67,7 +67,7 @@ func NewMilvusClient(ctx context.Context, cluster *commonpb.MilvusCluster) (Milv cli, err := milvusclient.New(ctx, config) if err != nil { - return nil, errors.Wrap(err, "failed to create milvus client") + return nil, merr.Wrap(err, "failed to create milvus client") } return cli, nil } diff --git a/internal/cdc/meta/replicate_meta.go b/internal/cdc/meta/replicate_meta.go index 28ca4e169b..8897cb9304 100644 --- a/internal/cdc/meta/replicate_meta.go +++ b/internal/cdc/meta/replicate_meta.go @@ -5,11 +5,11 @@ import ( "fmt" "time" - "github.com/cockroachdb/errors" clientv3 "go.etcd.io/etcd/client/v3" "google.golang.org/protobuf/proto" "github.com/milvus-io/milvus/pkg/v3/proto/streamingpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) const requestTimeout = 30 * time.Second @@ -44,7 +44,7 @@ func ListReplicatePChannels(ctx context.Context, etcdCli *clientv3.Client, prefi meta := &streamingpb.ReplicatePChannelMeta{} err = proto.Unmarshal([]byte(value), meta) if err != nil { - return nil, errors.Wrapf(err, "unmarshal replicate pchannel meta %s failed", keys[k]) + return nil, merr.Wrapf(err, "unmarshal replicate pchannel meta %s failed", keys[k]) } channel := &ReplicateChannel{ Key: keys[k], diff --git a/internal/cdc/replication/replicatemanager/channel_replicator.go b/internal/cdc/replication/replicatemanager/channel_replicator.go index 2969b396dc..3ada8f860c 100644 --- a/internal/cdc/replication/replicatemanager/channel_replicator.go +++ b/internal/cdc/replication/replicatemanager/channel_replicator.go @@ -35,6 +35,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message/adaptor" "github.com/milvus-io/milvus/pkg/v3/streaming/util/options" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/syncutil" ) @@ -202,7 +203,7 @@ func (r *channelReplicator) getReplicateCheckpoint() (*utility.ReplicateCheckpoi } replicateInfo, err := r.targetClient.GetReplicateInfo(ctx, req) if err != nil { - return nil, errors.Wrap(err, "failed to get replicate info") + return nil, merr.Wrap(err, "failed to get replicate info") } checkpoint := replicateInfo.GetCheckpoint() diff --git a/internal/cdc/replication/replicatestream/msg_queue.go b/internal/cdc/replication/replicatestream/msg_queue.go index 9aba7858e0..739a42ea97 100644 --- a/internal/cdc/replication/replicatestream/msg_queue.go +++ b/internal/cdc/replication/replicatestream/msg_queue.go @@ -115,7 +115,7 @@ func (q *msgQueue) Enqueue(ctx context.Context, msg message.ImmutableMessage) er // Optional runtime check: enforce non-decreasing timetick // if n := len(q.buf); n > 0 { // if q.buf[n-1].Timetick() > msg.Timetick() { - // return fmt.Errorf("enqueue timetick order violation: last=%d new=%d", q.buf[n-1].Timetick(), msg.Timetick()) + // return merr.WrapErrParameterInvalidMsg("enqueue timetick order violation: last=%d new=%d", q.buf[n-1].Timetick(), msg.Timetick()) // } // } diff --git a/internal/coordinator/file_resource_observer.go b/internal/coordinator/file_resource_observer.go index 4f388edb53..9ce4445592 100644 --- a/internal/coordinator/file_resource_observer.go +++ b/internal/coordinator/file_resource_observer.go @@ -165,7 +165,7 @@ func (m *FileResourceObserver) CheckNodeSynced(nodeID int64) bool { func (m *FileResourceObserver) CheckAllQnReady() error { // return error if meta is not ready if m.meta == nil { - return merr.WrapErrParameterInvalidMsg("rootcoord meta is not ready") + return merr.WrapErrServiceUnavailable("rootcoord meta is not ready") } resources, version := m.meta.ListFileResource(m.ctx) @@ -177,7 +177,7 @@ func (m *FileResourceObserver) CheckAllQnReady() error { var err error m.distribution.Range(func(nodeID int64, node *NodeInfo) bool { if node.NodeType == QueryNode && node.Version < version { - err = merr.WrapErrParameterInvalidMsg("node %d file resource not synced", nodeID) + err = merr.WrapErrServiceUnavailableMsg("file resource not synced, node-%d", nodeID) return false } return true diff --git a/internal/coordinator/restful_mgr_routes.go b/internal/coordinator/restful_mgr_routes.go index 5a883e2af6..5ff31574f7 100644 --- a/internal/coordinator/restful_mgr_routes.go +++ b/internal/coordinator/restful_mgr_routes.go @@ -298,10 +298,10 @@ func (s *mixCoordImpl) getQueryNodes(ctx context.Context) (*querypb.ListQueryNod Base: commonpbutil.NewMsgBase(), }) if err != nil { - return nil, fmt.Errorf("failed to list query nodes: %w", err) + return nil, merr.Wrapf(err, "failed to list query nodes") } if !merr.Ok(resp.GetStatus()) { - return nil, fmt.Errorf("failed to list query nodes: %s", resp.GetStatus().GetReason()) + return nil, merr.Wrapf(merr.Error(resp.GetStatus()), "failed to list query nodes") } return resp, nil } @@ -514,7 +514,7 @@ func (s *mixCoordImpl) getQueryCoordChannelBalanceActive(ctx context.Context) (b } if !merr.Ok(resp.GetStatus()) { - return channelActivate, fmt.Errorf("%s", resp.GetStatus().GetReason()) + return channelActivate, merr.Error(resp.GetStatus()) } return channelActivate && resp.IsActive, nil @@ -528,7 +528,7 @@ func (s *mixCoordImpl) getQueryCoordBalanceActive(ctx context.Context) (bool, er return false, err } if !merr.Ok(resp.GetStatus()) { - return false, fmt.Errorf("%s", resp.GetStatus().GetReason()) + return false, merr.Error(resp.GetStatus()) } return resp.IsActive, nil } @@ -569,7 +569,7 @@ func (s *mixCoordImpl) controlQueryCoordChannelBalanceStatus(ctx context.Context default: // If the status is not recognized, return an informative error immediately. // This avoids proceeding with an invalid state. - err = fmt.Errorf("invalid status value: '%s'. Use 'suspended', 'resumed' or 'active'", status) + err = merr.WrapErrParameterInvalidMsg("invalid status value: '%s'. Use 'suspended', 'resumed' or 'active'", status) } // --- Unified Error Handling --- @@ -578,7 +578,7 @@ func (s *mixCoordImpl) controlQueryCoordChannelBalanceStatus(ctx context.Context // First, check if there was a low-level error during the method execution (e.g., network issue). if err != nil { // Wrap the original error with more context. - return fmt.Errorf("%s: %w", errMsg, err) + return merr.Wrap(err, errMsg) } // If no errors were encountered, the operation was successful. Return nil to indicate success. return nil @@ -958,11 +958,11 @@ func (s *mixCoordImpl) handleQueryNodeStatusUpdate(ctx context.Context, nodeID i errMsg = "failed to resume node" default: errMsg = "invalid status value. Use 'suspended', 'resumed' or 'active'" - err = fmt.Errorf("%s", errMsg) + err = merr.WrapErrParameterInvalidMsg("%s", errMsg) } err = merr.CheckRPCCall(resp, err) if err != nil { - err = fmt.Errorf("%s: %w", errMsg, err) + err = merr.Wrap(err, errMsg) } return err } diff --git a/internal/coordinator/snmanager/streaming_node_manager.go b/internal/coordinator/snmanager/streaming_node_manager.go index 3b516b551d..2b2720f288 100644 --- a/internal/coordinator/snmanager/streaming_node_manager.go +++ b/internal/coordinator/snmanager/streaming_node_manager.go @@ -12,6 +12,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/streaming/util/types" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/syncutil" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -99,7 +100,7 @@ func (s *StreamingNodeManager) GetLatestWALLocated(ctx context.Context, vchannel } serverID, ok := balancer.GetLatestWALLocated(ctx, pchannel) if !ok { - return -1, errors.Errorf("channel: %s not found", vchannel) + return -1, merr.WrapErrChannelNotFound(vchannel) } return serverID, nil } diff --git a/internal/coordinator/wal_callbacks.go b/internal/coordinator/wal_callbacks.go index ddb6e39b2e..13c556abc8 100644 --- a/internal/coordinator/wal_callbacks.go +++ b/internal/coordinator/wal_callbacks.go @@ -19,12 +19,12 @@ package coordinator import ( "context" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus/internal/streamingcoord/server/broadcaster/registry" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -66,7 +66,7 @@ func (c *WALCallback) alterWALV2AckCallback( targetWALName := result.Message.Header().TargetWalName mqTypeValue := message.WALName(targetWALName).String() if mqTypeValue == "unknown" { - return errors.Errorf("invalid target WAL name: %v", targetWALName) + return merr.WrapErrServiceInternalMsg("invalid target WAL name: %v", targetWALName) } // Get etcd source to update configuration @@ -74,7 +74,7 @@ func (c *WALCallback) alterWALV2AckCallback( etcdSource, ok := paramMgr.GetEtcdSource() if !ok { logger.Warn("failed to update mq.type config, etcd source not enabled") - return errors.New("etcd source is not enabled, cannot update mq.type configuration") + return merr.WrapErrServiceInternalMsg("etcd source is not enabled, cannot update mq.type configuration") } // Update mq.type configuration in etcd @@ -84,7 +84,7 @@ func (c *WALCallback) alterWALV2AckCallback( zap.String("configKey", configKey), zap.String("mqTypeValue", mqTypeValue), zap.Error(err)) - return errors.Wrap(err, "failed to update mq.type configuration in etcd") + return merr.Wrap(err, "failed to update mq.type configuration in etcd") } logger.Info("successfully updated mq.type configuration in etcd", diff --git a/internal/core/thirdparty/tantivy/tantivy-binding/src/analyzer/dict/lindera/fetch.rs b/internal/core/thirdparty/tantivy/tantivy-binding/src/analyzer/dict/lindera/fetch.rs index 6dd9580658..1bf75ac8f3 100644 --- a/internal/core/thirdparty/tantivy/tantivy-binding/src/analyzer/dict/lindera/fetch.rs +++ b/internal/core/thirdparty/tantivy/tantivy-binding/src/analyzer/dict/lindera/fetch.rs @@ -131,10 +131,13 @@ async fn download_with_retry( for url in urls { match client.get(url).send().await { Ok(resp) if resp.status().is_success() => { - let content = resp - .bytes() - .await - .map_err(|e| TantivyBindingError::InternalError(e.to_string()))?; + let content = match resp.bytes().await { + Ok(c) => c, + Err(e) => { + warn!("Failed to read body from {}: {}", url, e); + continue; + } + }; // Calculate MD5 hash let mut context = Context::new(); diff --git a/internal/datacoord/analyze_meta.go b/internal/datacoord/analyze_meta.go index 2f379b2f6c..ccb017ff1c 100644 --- a/internal/datacoord/analyze_meta.go +++ b/internal/datacoord/analyze_meta.go @@ -18,7 +18,6 @@ package datacoord import ( "context" - "fmt" "sync" "go.uber.org/zap" @@ -28,6 +27,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" "github.com/milvus-io/milvus/pkg/v3/proto/workerpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/timerecord" ) @@ -117,7 +117,7 @@ func (m *analyzeMeta) UpdateVersion(taskID int64, nodeID int64) error { t, ok := m.tasks[taskID] if !ok { - return fmt.Errorf("there is no task with taskID: %d", taskID) + return merr.WrapErrServiceInternalMsg("there is no task with taskID: %d", taskID) } cloneT := proto.Clone(t).(*indexpb.AnalyzeTask) @@ -134,7 +134,7 @@ func (m *analyzeMeta) BuildingTask(taskID int64) error { t, ok := m.tasks[taskID] if !ok { - return fmt.Errorf("there is no task with taskID: %d", taskID) + return merr.WrapErrServiceInternalMsg("there is no task with taskID: %d", taskID) } cloneT := proto.Clone(t).(*indexpb.AnalyzeTask) @@ -150,7 +150,7 @@ func (m *analyzeMeta) UpdateState(taskID int64, state indexpb.JobState, failReas t, ok := m.tasks[taskID] if !ok { - return fmt.Errorf("there is no task with taskID: %d", taskID) + return merr.WrapErrServiceInternalMsg("there is no task with taskID: %d", taskID) } cloneT := proto.Clone(t).(*indexpb.AnalyzeTask) @@ -168,7 +168,7 @@ func (m *analyzeMeta) FinishTask(taskID int64, result *workerpb.AnalyzeResult) e t, ok := m.tasks[taskID] if !ok { - return fmt.Errorf("there is no task with taskID: %d", taskID) + return merr.WrapErrServiceInternalMsg("there is no task with taskID: %d", taskID) } log.Info("finish task meta...", zap.Int64("taskID", taskID), zap.String("state", result.GetState().String()), diff --git a/internal/datacoord/backfill_result.go b/internal/datacoord/backfill_result.go index 9d93dbd7ec..f08d8e3ad8 100644 --- a/internal/datacoord/backfill_result.go +++ b/internal/datacoord/backfill_result.go @@ -21,10 +21,9 @@ import ( "strconv" "strings" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus/internal/storage" "github.com/milvus-io/milvus/pkg/v3/proto/datapb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // BackfillResult is the decoded form of the JSON produced by the Spark @@ -86,34 +85,34 @@ var knownObjectSchemes = map[string]struct{}{ // 3. Unknown scheme -> return ErrUnsupportedScheme. func normalizeObjectKey(raw, expectedBucket string) (string, error) { if raw == "" { - return "", errors.New("empty object path") + return "", merr.WrapErrServiceInternalMsg("empty object path") } if !strings.Contains(raw, "://") { key := strings.TrimPrefix(raw, "/") if key == "" { - return "", errors.Newf("empty object key in %q", raw) + return "", merr.WrapErrServiceInternalMsg("empty object key in %q", raw) } return key, nil } idx := strings.Index(raw, "://") scheme := strings.ToLower(raw[:idx]) if _, ok := knownObjectSchemes[scheme]; !ok { - return "", errors.Newf("unsupported object URI scheme %q in %q", scheme, raw) + return "", merr.WrapErrServiceInternalMsg("unsupported object URI scheme %q in %q", scheme, raw) } rest := raw[idx+3:] slash := strings.Index(rest, "/") if slash < 0 { - return "", errors.Newf("malformed object URI %q: missing object key", raw) + return "", merr.WrapErrServiceInternalMsg("malformed object URI %q: missing object key", raw) } bucket := rest[:slash] key := rest[slash+1:] if expectedBucket != "" && bucket != expectedBucket { - return "", errors.Newf("object URI bucket %q differs from datacoord bucket %q (path=%s)", bucket, expectedBucket, raw) + return "", merr.WrapErrServiceInternalMsg("object URI bucket %q differs from datacoord bucket %q (path=%s)", bucket, expectedBucket, raw) } // Reject inputs like "s3a://bucket/" that parse to an empty key -- passing // an empty key to chunkManager.Read has undefined behavior across SDKs. if key == "" { - return "", errors.Newf("empty object key in %q", raw) + return "", merr.WrapErrServiceInternalMsg("empty object key in %q", raw) } return key, nil } @@ -147,20 +146,20 @@ func buildV2Groups(bucket string, entry *BackfillSegment) (map[int64]*datapb.Fie for i := range entry.ColumnGroups { g := &entry.ColumnGroups[i] if len(g.FieldIDs) != 1 { - return nil, errors.Newf("backfill invariant violated: column group has %d field_ids (expected 1)", len(g.FieldIDs)) + return nil, merr.WrapErrServiceInternalMsg("backfill invariant violated: column group has %d field_ids (expected 1)", len(g.FieldIDs)) } n := int64(len(g.BinlogFiles)) if n == 0 { - return nil, errors.Newf("column group for field %d has no binlog files", g.FieldIDs[0]) + return nil, merr.WrapErrServiceInternalMsg("column group for field %d has no binlog files", g.FieldIDs[0]) } fid := g.FieldIDs[0] if _, dup := out[fid]; dup { - return nil, errors.Newf("duplicate column group for field %d", fid) + return nil, merr.WrapErrServiceInternalMsg("duplicate column group for field %d", fid) } // row_count flows into EntriesNum; non-positive values are undefined // (zero collapses presence markers, negatives break accounting). if g.RowCount <= 0 { - return nil, errors.Newf("column group for field %d has non-positive row_count %d", fid, g.RowCount) + return nil, merr.WrapErrServiceInternalMsg("column group for field %d has non-positive row_count %d", fid, g.RowCount) } avg := g.RowCount / n rem := g.RowCount - avg*n @@ -176,7 +175,7 @@ func buildV2Groups(bucket string, entry *BackfillSegment) (map[int64]*datapb.Fie } logID, ok := parseLogIDFromKey(key) if !ok { - return nil, errors.Newf("column group for field %d has binlog file %q with non-numeric trailing segment", fid, p) + return nil, merr.WrapErrServiceInternalMsg("column group for field %d has binlog file %q with non-numeric trailing segment", fid, p) } // LogPath must be empty at persistence time -- catalog.checkLogID // rejects any Binlog with LogPath != "" (see diff --git a/internal/datacoord/compaction_policy_clustering.go b/internal/datacoord/compaction_policy_clustering.go index 17fee4c695..cb9e9c996b 100644 --- a/internal/datacoord/compaction_policy_clustering.go +++ b/internal/datacoord/compaction_policy_clustering.go @@ -31,6 +31,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/common" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/datapb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -241,7 +242,7 @@ func calculateClusteringCompactionConfig(coll *collectionInfo, view CompactionVi func estimateRowsBySegmentSize(segments []*SegmentView, expectedSegmentSize int64) (int64, error) { if expectedSegmentSize <= 0 { - return 0, fmt.Errorf("invalid expected segment size %d", expectedSegmentSize) + return 0, merr.WrapErrServiceInternalMsg("invalid expected segment size %d", expectedSegmentSize) } var totalSize float64 @@ -258,17 +259,17 @@ func estimateRowsBySegmentSize(segments []*SegmentView, expectedSegmentSize int6 } if totalRows == 0 || totalSize == 0 { - return 0, fmt.Errorf("segment view does not contain size info to estimate row count") + return 0, merr.WrapErrServiceInternalMsg("segment view does not contain size info to estimate row count") } rowSize := totalSize / float64(totalRows) if rowSize <= 0 { - return 0, fmt.Errorf("invalid row size calculated from segment view") + return 0, merr.WrapErrServiceInternalMsg("invalid row size calculated from segment view") } rows := float64(expectedSegmentSize) / rowSize if rows <= 0 { - return 0, fmt.Errorf("estimated max row count is not positive") + return 0, merr.WrapErrServiceInternalMsg("estimated max row count is not positive") } return int64(rows), nil diff --git a/internal/datacoord/compaction_policy_forcemerge.go b/internal/datacoord/compaction_policy_forcemerge.go index 54572a2f4d..ea6f3c8d00 100644 --- a/internal/datacoord/compaction_policy_forcemerge.go +++ b/internal/datacoord/compaction_policy_forcemerge.go @@ -2,7 +2,6 @@ package datacoord import ( "context" - "fmt" "math" "github.com/samber/lo" @@ -102,7 +101,7 @@ func (policy *forceMergeCompactionPolicy) triggerOneCollection( configMaxSize := getExpectedSegmentSize(policy.meta, collectionID, collection.Schema) if targetSizeBytes < configMaxSize { - return nil, 0, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("targetSize %d MB should be greater than or equal to configMaxSize %d MB", targetSize, configMaxSize/(1024*1024))) + return nil, 0, merr.WrapErrParameterInvalidMsg("targetSize %d MB should be greater than or equal to configMaxSize %d MB", targetSize, configMaxSize/(1024*1024)) } segments := policy.meta.SelectSegments(ctx, WithCollection(collectionID), SegmentFilterFunc(func(segment *SegmentInfo) bool { @@ -182,7 +181,7 @@ var _ CollectionTopologyQuerier = (*metricsNodeMemoryQuerier)(nil) func (q *metricsNodeMemoryQuerier) GetCollectionTopology(ctx context.Context, collectionID int64) (*CollectionTopology, error) { log := log.Ctx(ctx).With(zap.Int64("collectionID", collectionID)) if q.mixCoord == nil { - return nil, fmt.Errorf("mixCoord not available for topology query") + return nil, merr.WrapErrServiceInternalMsg("mixCoord not available for topology query") } // 1. Get replica information diff --git a/internal/datacoord/compaction_queue.go b/internal/datacoord/compaction_queue.go index 3e71f15f8e..27e11f2372 100644 --- a/internal/datacoord/compaction_queue.go +++ b/internal/datacoord/compaction_queue.go @@ -74,9 +74,12 @@ func (pq *PriorityQueue[T]) Update(item *Item[T], value T, priority int) { heap.Fix(pq, item.index) } +// errFull / errNoSuchElement are INTERNAL sentinels: caught by errors.Is +// inside the compaction inspector / scheduler loop and never serialized +// across any gRPC boundary. See docs/dev/error_sentinel_convention.md. var ( - ErrFull = errors.New("compaction queue is full") - ErrNoSuchElement = errors.New("compaction queue has no element") + errFull = errors.New("compaction queue is full") + errNoSuchElement = errors.New("compaction queue has no element") ) type Prioritizer func(t CompactionTask) int @@ -101,7 +104,7 @@ func (q *CompactionQueue) Enqueue(t CompactionTask) error { q.lock.Lock() defer q.lock.Unlock() if q.capacity > 0 && len(q.pq) >= q.capacity { - return ErrFull + return errFull } heap.Push(&q.pq, &Item[CompactionTask]{value: t, priority: q.prioritizer(t)}) @@ -113,7 +116,7 @@ func (q *CompactionQueue) Dequeue() (CompactionTask, error) { defer q.lock.Unlock() if len(q.pq) == 0 { - return nil, ErrNoSuchElement + return nil, errNoSuchElement } item := heap.Pop(&q.pq).(*Item[CompactionTask]) diff --git a/internal/datacoord/compaction_task_clustering.go b/internal/datacoord/compaction_task_clustering.go index ac3c90aedb..7be0821aef 100644 --- a/internal/datacoord/compaction_task_clustering.go +++ b/internal/datacoord/compaction_task_clustering.go @@ -23,7 +23,6 @@ import ( "strconv" "time" - "github.com/cockroachdb/errors" "github.com/samber/lo" "go.uber.org/atomic" "go.uber.org/zap" @@ -629,7 +628,7 @@ func (t *clusteringCompactionTask) processAnalyzing() error { } case indexpb.JobState_JobStateFailed: log.Warn("analyze task fail", zap.Int64("analyzeID", t.GetTaskProto().GetAnalyzeTaskID())) - return errors.New(analyzeTask.FailReason) + return merr.WrapErrServiceInternalMsg(analyzeTask.FailReason) default: } return nil diff --git a/internal/datacoord/compaction_task_l0.go b/internal/datacoord/compaction_task_l0.go index aac50e63dc..6145ce6b6c 100644 --- a/internal/datacoord/compaction_task_l0.go +++ b/internal/datacoord/compaction_task_l0.go @@ -319,7 +319,7 @@ func (t *l0CompactionTask) selectFlushedSegment() ([]*SegmentInfo, []*datapb.Com for _, info := range flushedSegments { // Sealed is unexpected, fail fast if info.GetState() == commonpb.SegmentState_Sealed { - return nil, nil, fmt.Errorf("L0 compaction selected invalid sealed segment %d", info.GetID()) + return nil, nil, merr.WrapErrServiceInternalMsg("L0 compaction selected invalid sealed segment %d", info.GetID()) } sealedSegBinlogs = append(sealedSegBinlogs, &datapb.CompactionSegmentBinlogs{ @@ -455,7 +455,7 @@ func buildL0V3DeltaLogEntries(segmentID int64, deltalogs []*datapb.FieldBinlog) for _, binlog := range fieldBinlog.GetBinlogs() { path := binlog.GetLogPath() if path == "" { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("L0 V3 compaction result missing deltalog path for segment %d, logID %d", segmentID, binlog.GetLogID())) + return nil, merr.WrapErrServiceInternalMsg("L0 V3 compaction result missing deltalog path for segment %d, logID %d", segmentID, binlog.GetLogID()) } entries = append(entries, packed.DeltaLogEntry{ Path: path, diff --git a/internal/datacoord/compaction_task_mix.go b/internal/datacoord/compaction_task_mix.go index a65394c7ff..e6b0d1c2d9 100644 --- a/internal/datacoord/compaction_task_mix.go +++ b/internal/datacoord/compaction_task_mix.go @@ -398,7 +398,7 @@ func (t *mixCompactionTask) BuildCompactionRequest() (*datapb.CompactionPlan, er resources, err := t.meta.GetFileResources(context.Background(), taskSchema.GetFileResourceIds()...) if err != nil { log.Warn("get file resources for collection failed", zap.Int64("collectionID", taskProto.GetCollectionID()), zap.Error(err)) - return nil, errors.Errorf("get file resources for sort compaction failed: %v", err) + return nil, merr.Wrap(err, "get file resources for sort compaction failed") } plan.FileResources = resources } @@ -411,7 +411,7 @@ func (t *mixCompactionTask) BuildCompactionRequest() (*datapb.CompactionPlan, er return nil, merr.WrapErrSegmentNotFound(segID) } if taskSchemaVersion < segInfo.GetSchemaVersion() { - return nil, merr.WrapErrIllegalCompactionPlan(fmt.Sprintf("compaction task schema version %d is older than input segment %d schema version %d", taskSchemaVersion, segInfo.GetID(), segInfo.GetSchemaVersion())) + return nil, merr.WrapErrIllegalCompactionPlanMsg("compaction task schema version %d is older than input segment %d schema version %d", taskSchemaVersion, segInfo.GetID(), segInfo.GetSchemaVersion()) } plan.SegmentBinlogs = append(plan.SegmentBinlogs, &datapb.CompactionSegmentBinlogs{ SegmentID: segID, diff --git a/internal/datacoord/compaction_trigger.go b/internal/datacoord/compaction_trigger.go index 11d2db661a..c9bb12a519 100644 --- a/internal/datacoord/compaction_trigger.go +++ b/internal/datacoord/compaction_trigger.go @@ -225,7 +225,7 @@ func (t *compactionTrigger) getCollection(collectionID UniqueID) (*collectionInf defer cancel() coll, err := t.handler.GetCollection(ctx, collectionID) if err != nil { - return nil, fmt.Errorf("collection ID %d not found, err: %w", collectionID, err) + return nil, merr.Wrapf(err, "collection ID %d not found", collectionID) } return coll, nil } @@ -588,7 +588,10 @@ func (t *compactionTrigger) getCandidates(signal *compactionSignal) ([]chanPartS segments := t.meta.SelectSegments(context.TODO(), filters...) // some criterion not met or conflicted if len(signal.segmentIDs) > 0 && len(segments) != len(signal.segmentIDs) { - return nil, merr.WrapErrServiceInternal("not all segment ids provided could be compacted") + // SelectSegments also filters segments that are transiently mid-flush / + // compacting / just dropped, so a count mismatch is usually server-side + // state, not a bad id from the caller. + return nil, merr.WrapErrServiceInternalMsg("not all segment ids provided could be compacted") } type category struct { diff --git a/internal/datacoord/copy_segment_task.go b/internal/datacoord/copy_segment_task.go index 7423022ad2..d41e7d9ed2 100644 --- a/internal/datacoord/copy_segment_task.go +++ b/internal/datacoord/copy_segment_task.go @@ -583,7 +583,7 @@ func AssembleCopySegmentRequest(task CopySegmentTask, job CopySegmentJob) (*data if _, exists := newBuildIDs[srcBuildID]; !exists { newID, err := t.alloc.AllocID(ctx) if err != nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("failed to allocate new buildID for source buildID %d", srcBuildID), err.Error()) + return merr.Wrapf(err, "failed to allocate new buildID for source buildID %d", srcBuildID) } newBuildIDs[srcBuildID] = newID } diff --git a/internal/datacoord/ddl_callbacks.go b/internal/datacoord/ddl_callbacks.go index 2751773a62..dfc90f805a 100644 --- a/internal/datacoord/ddl_callbacks.go +++ b/internal/datacoord/ddl_callbacks.go @@ -27,6 +27,7 @@ import ( "github.com/milvus-io/milvus/internal/streamingcoord/server/broadcaster/registry" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // RegisterDDLCallbacks registers the ddl callbacks. @@ -89,7 +90,7 @@ func (s *Server) startBroadcastWithCollectionID(ctx context.Context, collectionI func (s *Server) startBroadcastForRestoreSnapshot(ctx context.Context, collectionID int64, snapshotName string) (broadcaster.BroadcastAPI, error) { coll, err := s.broker.DescribeCollectionInternal(ctx, collectionID) if err != nil { - return nil, fmt.Errorf("collection %d does not exist: %w", collectionID, err) + return nil, merr.Wrapf(err, "collection %d does not exist", collectionID) } dbName := coll.GetDbName() collectionName := coll.GetCollectionName() @@ -161,15 +162,15 @@ func (s *Server) validateRestoreSnapshotResources(ctx context.Context, collectio sourceCollectionID := snapshotData.SnapshotInfo.GetCollectionId() snapshot, err := s.meta.snapshotMeta.GetSnapshot(ctx, sourceCollectionID, snapshotData.SnapshotInfo.GetName()) if err != nil { - return fmt.Errorf("snapshot %s does not exist for collection %d: %w", - snapshotData.SnapshotInfo.GetName(), sourceCollectionID, err) + return merr.Wrapf(err, "snapshot %s does not exist for collection %d", + snapshotData.SnapshotInfo.GetName(), sourceCollectionID) } log.Info("snapshot validated", zap.String("snapshotName", snapshot.GetName())) // ========== Validate Collection Exists ========== coll, err := s.broker.DescribeCollectionInternal(ctx, collectionID) if err != nil { - return fmt.Errorf("collection %d does not exist: %w", collectionID, err) + return merr.Wrapf(err, "collection %d does not exist", collectionID) } dbName := coll.GetDbName() collectionName := coll.GetCollectionName() @@ -180,7 +181,7 @@ func (s *Server) validateRestoreSnapshotResources(ctx context.Context, collectio // ========== Validate Partitions Exist ========== partitionsResp, err := s.broker.ShowPartitions(ctx, collectionID) if err != nil { - return fmt.Errorf("failed to get partitions for collection %d: %w", collectionID, err) + return merr.Wrapf(err, "failed to get partitions for collection %d", collectionID) } // Build set of existing partition names @@ -192,8 +193,7 @@ func (s *Server) validateRestoreSnapshotResources(ctx context.Context, collectio // Check all snapshot partitions exist for partName := range snapshotData.Collection.GetPartitions() { if !existingPartitions[partName] { - return fmt.Errorf("partition %s does not exist in collection %d", - partName, collectionID) + return merr.WrapErrPartitionNotFound(partName, fmt.Sprintf("partition does not exist in collection %d", collectionID)) } } log.Info("partitions validated", zap.Int("count", len(existingPartitions))) @@ -211,8 +211,7 @@ func (s *Server) validateRestoreSnapshotResources(ctx context.Context, collectio } } if !indexFound { - return fmt.Errorf("index %s for field %d does not exist in collection %d", - indexInfo.GetIndexName(), indexInfo.GetFieldID(), collectionID) + return merr.WrapErrIndexNotFound(indexInfo.GetIndexName(), fmt.Sprintf("index for field %d does not exist in collection %d", indexInfo.GetFieldID(), collectionID)) } } log.Info("indexes validated", zap.Int("count", len(snapshotData.Indexes))) diff --git a/internal/datacoord/ddl_callbacks_import.go b/internal/datacoord/ddl_callbacks_import.go index 5cd8558b85..835522b09b 100644 --- a/internal/datacoord/ddl_callbacks_import.go +++ b/internal/datacoord/ddl_callbacks_import.go @@ -134,10 +134,10 @@ func (s *Server) validateImportReplication(ctx context.Context, options []*commo } if !paramtable.Get().DataCoordCfg.ImportInReplicatingCluster.GetAsBool() { - return merr.WrapErrImportFailed("import in replicating cluster is not supported yet") + return merr.WrapErrOperationNotSupportedMsg("import in replicating cluster is not supported yet") } if importutilv2.IsAutoCommit(options) { - return merr.WrapErrImportFailed("auto_commit=true import in replicating cluster is not supported") + return merr.WrapErrOperationNotSupportedMsg("auto_commit=true import in replicating cluster is not supported") } return nil } @@ -168,14 +168,14 @@ func (s *Server) broadcastImport(ctx context.Context, // Validate the request before broadcasting if err := s.validateImportRequest(ctx, msgFiles, options); err != nil { - return errors.Wrap(err, "failed to validate import request") + return merr.Wrap(err, "failed to validate import request") } // Get database name from collection metadata via broker // This is safer than extracting from schema which may be stale broadcaster, err := s.startBroadcastWithCollectionID(ctx, collectionID) if err != nil { - return errors.Wrap(err, "failed to start broadcast with collection id") + return merr.Wrap(err, "failed to start broadcast with collection id") } defer broadcaster.Close() diff --git a/internal/datacoord/ddl_callbacks_import_test.go b/internal/datacoord/ddl_callbacks_import_test.go index fd3812687d..4cd10ebda5 100644 --- a/internal/datacoord/ddl_callbacks_import_test.go +++ b/internal/datacoord/ddl_callbacks_import_test.go @@ -107,8 +107,9 @@ func (s *ImportCallbacksSuite) TestValidateImportRequest_MaxJobsExceededReturnsE err := server.validateImportRequest(ctx, files, options) s.Error(err) - // ValidateMaxImportJobExceed returns WrapErrImportFailed, not ErrServiceQuotaExceeded - s.True(errors.Is(err, merr.ErrImportFailed)) + // Job-count backpressure is a server-side condition -> ErrImportSysFailed + // (must not be bucketed as fail_input). + s.True(errors.Is(err, merr.ErrImportSysFailed)) s.Contains(err.Error(), "The number of jobs has reached the limit") } @@ -185,7 +186,7 @@ func (s *ImportCallbacksSuite) TestValidateImportRequest_ReplicatingClusterRetur err := server.validateImportRequest(ctx, files, options) s.Error(err) - s.True(errors.Is(err, merr.ErrImportFailed)) + s.True(errors.Is(err, merr.ErrOperationNotSupported)) s.Contains(err.Error(), "replicating cluster") } @@ -230,7 +231,7 @@ func (s *ImportCallbacksSuite) TestValidateImportRequest_ReplicatingClusterEnabl {Key: "timeout", Value: "300s"}, }) s.Error(err) - s.True(errors.Is(err, merr.ErrImportFailed)) + s.True(errors.Is(err, merr.ErrOperationNotSupported)) s.Contains(err.Error(), "auto_commit=true") err = server.validateImportRequest(ctx, files, []*commonpb.KeyValuePair{ @@ -1159,7 +1160,8 @@ func testBroadcastRequiresVchannels(t *testing.T, broadcastFn func(*Server, cont err := broadcastFn(server, ctx, job) assert.Error(t, err) - assert.True(t, errors.Is(err, merr.ErrImportFailed)) + // Missing vchannels is internal broadcast state -> ErrImportSysFailed. + assert.True(t, errors.Is(err, merr.ErrImportSysFailed)) assert.Contains(t, err.Error(), "job 7 has no vchannels") } diff --git a/internal/datacoord/ddl_callbacks_snapshot_test.go b/internal/datacoord/ddl_callbacks_snapshot_test.go index 16bc4b7709..ca868e0565 100644 --- a/internal/datacoord/ddl_callbacks_snapshot_test.go +++ b/internal/datacoord/ddl_callbacks_snapshot_test.go @@ -636,7 +636,8 @@ func TestValidateRestoreSnapshotResources_IndexNotFound(t *testing.T) { err := server.validateRestoreSnapshotResources(ctx, 100, buildBaseSnapshotData()) assert.Error(t, err) - assert.Contains(t, err.Error(), "index vec_idx for field 100 does not exist") + assert.Contains(t, err.Error(), "index for field 100 does not exist") + assert.Contains(t, err.Error(), "vec_idx") } func TestValidateRestoreSnapshotResources_IndexFieldMismatch(t *testing.T) { @@ -656,7 +657,8 @@ func TestValidateRestoreSnapshotResources_IndexFieldMismatch(t *testing.T) { err := server.validateRestoreSnapshotResources(ctx, 100, buildBaseSnapshotData()) assert.Error(t, err) - assert.Contains(t, err.Error(), "index vec_idx for field 100 does not exist") + assert.Contains(t, err.Error(), "index for field 100 does not exist") + assert.Contains(t, err.Error(), "vec_idx") } func TestValidateRestoreSnapshotResources_Success(t *testing.T) { diff --git a/internal/datacoord/errors.go b/internal/datacoord/errors.go index 9127a28f8a..5ce4854e47 100644 --- a/internal/datacoord/errors.go +++ b/internal/datacoord/errors.go @@ -20,6 +20,9 @@ import ( "fmt" "github.com/cockroachdb/errors" + + "github.com/milvus-io/milvus/pkg/v3/util/merr" + "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) // errors for VerifyResponse @@ -34,7 +37,7 @@ func msgDataCoordIsUnhealthy(coordID UniqueID) string { } func errDataCoordIsUnhealthy(coordID UniqueID) error { - return errors.New(msgDataCoordIsUnhealthy(coordID)) + return merr.WrapErrServiceNotReady(typeutil.DataCoordRole, coordID, "not ready") } func msgSegmentNotFound(segID UniqueID) string { diff --git a/internal/datacoord/external_collection_refresh_manager.go b/internal/datacoord/external_collection_refresh_manager.go index 6981132f11..6a7cabb703 100644 --- a/internal/datacoord/external_collection_refresh_manager.go +++ b/internal/datacoord/external_collection_refresh_manager.go @@ -306,7 +306,7 @@ func (m *externalCollectionRefreshManager) cleanupExploreTempForJob(jobID int64) func (m *externalCollectionRefreshManager) applyFinishedJobSegments(ctx context.Context, job *datapb.ExternalCollectionRefreshJob) error { tasks := m.refreshMeta.GetTasksByJobID(job.GetJobId()) if len(tasks) == 0 { - return fmt.Errorf("job %d has no tasks to apply", job.GetJobId()) + return merr.WrapErrServiceInternalMsg("job %d has no tasks to apply", job.GetJobId()) } keptSet := make(map[int64]struct{}) @@ -315,11 +315,11 @@ func (m *externalCollectionRefreshManager) applyFinishedJobSegments(ctx context. updatedSegments := make([]*datapb.SegmentInfo, 0) for _, task := range tasks { if task.GetState() != indexpb.JobState_JobStateFinished { - return fmt.Errorf("job %d has non-finished task %d in state %s", + return merr.WrapErrServiceInternalMsg("job %d has non-finished task %d in state %s", job.GetJobId(), task.GetTaskId(), task.GetState().String()) } if !task.GetResultReady() { - return fmt.Errorf("job %d has finished task %d without persisted refresh result; please retry refresh", + return merr.WrapErrServiceInternalMsg("job %d has finished task %d without persisted refresh result; please retry refresh", job.GetJobId(), task.GetTaskId()) } for _, segmentID := range task.GetKeptSegments() { @@ -334,7 +334,7 @@ func (m *externalCollectionRefreshManager) applyFinishedJobSegments(ctx context. continue } if _, ok := updatedSet[segment.GetID()]; ok { - return fmt.Errorf("job %d has duplicate updated segment %d from task %d", + return merr.WrapErrServiceInternalMsg("job %d has duplicate updated segment %d from task %d", job.GetJobId(), segment.GetID(), task.GetTaskId()) } updatedSet[segment.GetID()] = struct{}{} @@ -714,7 +714,7 @@ func (m *externalCollectionRefreshManager) createTasksForJob( if errors.Is(err, packed.ErrLoonTransient) { return nil, newNonRetriableJobError("explore external files failed: %v", err) } - return nil, fmt.Errorf("failed to explore external files: %w", err) + return nil, merr.WrapErrServiceInternalErr(err, "failed to explore external files") } if len(allFiles) == 0 { return nil, newNonRetriableJobError("no files found in external source: %s", job.GetExternalSource()) @@ -829,7 +829,7 @@ func normalizeRefreshJobProgress(job *datapb.ExternalCollectionRefreshJob, state func (m *externalCollectionRefreshManager) GetJobProgress(ctx context.Context, jobID int64) (*datapb.ExternalCollectionRefreshJob, error) { job := m.refreshMeta.GetJob(jobID) if job == nil { - return nil, fmt.Errorf("job %d not found", jobID) + return nil, merr.WrapErrParameterInvalidMsg("refresh job %d not found", jobID) } // Aggregate state and progress from tasks @@ -877,17 +877,17 @@ func (m *externalCollectionRefreshManager) exploreExternalFiles( // when both present. if job.GetExternalSource() != "" { if err := externalspec.ValidateSourceAndSpec(job.GetExternalSource(), job.GetExternalSpec()); err != nil { - return nil, "", fmt.Errorf("external source/spec failed revalidation: %w", err) + return nil, "", merr.Wrap(err, "external source/spec failed revalidation") } } spec, err := externalspec.ParseExternalSpec(job.GetExternalSpec()) if err != nil { - return nil, "", fmt.Errorf("failed to parse external spec: %w", err) + return nil, "", merr.Wrap(err, "failed to parse external spec") } collInfo := m.mt.GetCollection(job.GetCollectionId()) if collInfo == nil { - return nil, "", fmt.Errorf("collection %d not found", job.GetCollectionId()) + return nil, "", merr.WrapErrCollectionNotFound(job.GetCollectionId()) } columns := packed.GetColumnNamesFromSchema(collInfo.Schema) @@ -908,7 +908,7 @@ func (m *externalCollectionRefreshManager) exploreExternalFiles( extfs, ) if err != nil { - return nil, "", fmt.Errorf("ExploreFilesReturnManifestPath failed: %w", err) + return nil, "", merr.WrapErrServiceInternalErr(err, "failed to explore files returning manifest path") } // Convert to proto type diff --git a/internal/datacoord/external_collection_refresh_meta.go b/internal/datacoord/external_collection_refresh_meta.go index b562888928..e8c3cb6fe7 100644 --- a/internal/datacoord/external_collection_refresh_meta.go +++ b/internal/datacoord/external_collection_refresh_meta.go @@ -18,7 +18,6 @@ package datacoord import ( "context" - "fmt" "sort" "time" @@ -30,6 +29,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/datapb" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" "github.com/milvus-io/milvus/pkg/v3/util/lock" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/timerecord" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -295,7 +295,7 @@ func (m *externalCollectionRefreshMeta) mutateJob( ) (applied bool, err error) { job, ok := m.jobs.Get(jobID) if !ok { - return false, fmt.Errorf("job %d not found", jobID) + return false, merr.WrapErrServiceInternalMsg("job %d not found", jobID) } m.jobLock.Lock(job.GetCollectionId()) @@ -304,7 +304,7 @@ func (m *externalCollectionRefreshMeta) mutateJob( // Re-fetch after lock job, ok = m.jobs.Get(jobID) if !ok { - return false, fmt.Errorf("job %d not found", jobID) + return false, merr.WrapErrServiceInternalMsg("job %d not found", jobID) } cloneJob := proto.Clone(job).(*datapb.ExternalCollectionRefreshJob) @@ -385,7 +385,7 @@ func (m *externalCollectionRefreshMeta) UpdateJobStateWithPreApply( ) (bool, error) { job, ok := m.jobs.Get(jobID) if !ok { - return false, fmt.Errorf("job %d not found", jobID) + return false, merr.WrapErrServiceInternalMsg("job %d not found", jobID) } m.jobLock.Lock(job.GetCollectionId()) @@ -395,7 +395,7 @@ func (m *externalCollectionRefreshMeta) UpdateJobStateWithPreApply( // terminal state owns the one-time side effects. job, ok = m.jobs.Get(jobID) if !ok { - return false, fmt.Errorf("job %d not found", jobID) + return false, merr.WrapErrServiceInternalMsg("job %d not found", jobID) } if job.GetState() == indexpb.JobState_JobStateFinished || job.GetState() == indexpb.JobState_JobStateFailed { @@ -416,7 +416,7 @@ func (m *externalCollectionRefreshMeta) UpdateJobStateWithPreApply( log.Warn("update job state after pre-apply failed", zap.Int64("jobID", jobID), zap.Error(saveErr)) - return false, fmt.Errorf("pre-apply failed: %w; additionally failed to persist Failed job state: %v", err, saveErr) + return false, merr.Wrapf(err, "pre-apply failed; additionally failed to persist Failed job state: %v", saveErr) } m.jobs.Insert(jobID, cloneJob) m.addToCollectionJobs(cloneJob) @@ -613,7 +613,7 @@ func (m *externalCollectionRefreshMeta) mutateTask( ) (applied bool, cloned *datapb.ExternalCollectionRefreshTask, err error) { task, ok := m.tasks.Get(taskID) if !ok { - return false, nil, fmt.Errorf("task %d not found", taskID) + return false, nil, merr.WrapErrServiceInternalMsg("task %d not found", taskID) } m.taskLock.Lock(task.GetJobId()) @@ -622,7 +622,7 @@ func (m *externalCollectionRefreshMeta) mutateTask( // Re-fetch after lock task, ok = m.tasks.Get(taskID) if !ok { - return false, nil, fmt.Errorf("task %d not found", taskID) + return false, nil, merr.WrapErrServiceInternalMsg("task %d not found", taskID) } cloneTask := proto.Clone(task).(*datapb.ExternalCollectionRefreshTask) diff --git a/internal/datacoord/garbage_collector.go b/internal/datacoord/garbage_collector.go index 964e0236af..b558eabfb1 100644 --- a/internal/datacoord/garbage_collector.go +++ b/internal/datacoord/garbage_collector.go @@ -1067,13 +1067,13 @@ func (gc *garbageCollector) isExpire(dropts Timestamp) bool { // Returns segmentID or error if path doesn't match. func parseV3SegmentID(rootPath, filePath string) (int64, error) { if !strings.HasPrefix(filePath, rootPath) { - return 0, fmt.Errorf("path %q does not contain rootPath %q", filePath, rootPath) + return 0, merr.WrapErrServiceInternalMsg("path %q does not contain rootPath %q", filePath, rootPath) } p := strings.TrimPrefix(filePath[len(rootPath):], "/") parts := strings.Split(p, "/") // Minimum: insert_log/coll/part/seg/something if len(parts) < 5 || parts[0] != common.SegmentInsertLogPath { - return 0, fmt.Errorf("not a V3 insert_log path: %s", filePath) + return 0, merr.WrapErrServiceInternalMsg("not a V3 insert_log path: %s", filePath) } return strconv.ParseInt(parts[3], 10, 64) } diff --git a/internal/datacoord/garbage_collector_lob.go b/internal/datacoord/garbage_collector_lob.go index 10ed58c748..13a5c8fe92 100644 --- a/internal/datacoord/garbage_collector_lob.go +++ b/internal/datacoord/garbage_collector_lob.go @@ -18,7 +18,6 @@ package datacoord import ( "context" - "fmt" "path" "strings" "sync" @@ -34,6 +33,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" "github.com/milvus-io/milvus/pkg/v3/util/conc" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -271,8 +271,7 @@ func (lobCtx *lobGCContext) collectLOBFilesFromSegment(ctx context.Context, segm lobFiles, err := lobCtx.cache.Get(ctx, manifestPath, lobCtx.storageConfig) if err != nil { - return fmt.Errorf("failed to get LOB files from manifest for segment %d (path=%s): %w", - segment.GetID(), manifestPath, err) + return merr.WrapErrServiceInternalErr(err, "failed to get LOB files from manifest for segment %d (path=%s)", segment.GetID(), manifestPath) } for _, lobFile := range lobFiles { diff --git a/internal/datacoord/import_meta.go b/internal/datacoord/import_meta.go index 98719a17c8..bd6eca3227 100644 --- a/internal/datacoord/import_meta.go +++ b/internal/datacoord/import_meta.go @@ -18,7 +18,6 @@ package datacoord import ( "context" - "fmt" "time" "github.com/hashicorp/golang-lru/v2/expirable" @@ -345,7 +344,7 @@ func (m *importMeta) HandleCommitVchannel(ctx context.Context, jobID int64, vcha job := m.jobs[jobID] if job == nil { - return merr.WrapErrImportFailed(fmt.Sprintf("job %d not found", jobID)) + return merr.WrapErrImportSysFailedMsg("job %d not found", jobID) } // Idempotency: if vchannel already committed, skip. for _, c := range job.GetCommittedVchannels() { diff --git a/internal/datacoord/import_services_test.go b/internal/datacoord/import_services_test.go index be0712f32a..56a9997d2f 100644 --- a/internal/datacoord/import_services_test.go +++ b/internal/datacoord/import_services_test.go @@ -104,7 +104,7 @@ func (s *ImportServicesSuite) TestImportV2_AllocatorNilReturnsError() { s.NoError(err) s.NotNil(resp) - s.True(errors.Is(merr.Error(resp.GetStatus()), merr.ErrImportFailed)) + s.True(errors.Is(merr.Error(resp.GetStatus()), merr.ErrServiceUnavailable)) s.Contains(resp.GetStatus().GetReason(), "allocator not initialized") } @@ -114,7 +114,7 @@ func (s *ImportServicesSuite) TestImportV2_AllocatorFailsReturnsError() { server.stateCode.Store(commonpb.StateCode_Healthy) mockAllocator := allocator.NewMockAllocator(s.T()) - mockAllocator.EXPECT().AllocN(mock.Anything).Return(int64(0), int64(0), errors.New("allocation failed")) + mockAllocator.EXPECT().AllocN(mock.Anything).Return(int64(0), int64(0), merr.WrapErrServiceUnavailable("allocation failed")) server.allocator = mockAllocator req := &internalpb.ImportRequestInternal{ @@ -127,7 +127,7 @@ func (s *ImportServicesSuite) TestImportV2_AllocatorFailsReturnsError() { s.NoError(err) s.NotNil(resp) - s.True(errors.Is(merr.Error(resp.GetStatus()), merr.ErrImportFailed)) + s.True(errors.Is(merr.Error(resp.GetStatus()), merr.ErrServiceUnavailable)) s.Contains(resp.GetStatus().GetReason(), "failed to allocate job ID") } @@ -164,7 +164,7 @@ func (s *ImportServicesSuite) TestImportV2_BroadcastFailsReturnsError() { // Mock StartBroadcastWithResourceKeys to fail mockBroadcast := mockey.Mock(broadcast.StartBroadcastWithResourceKeys).To( func(ctx context.Context, keys ...message.ResourceKey) (broadcaster.BroadcastAPI, error) { - return nil, errors.New("broadcast failed") + return nil, merr.WrapErrServiceUnavailable("broadcast failed") }).Build() defer mockBroadcast.UnPatch() @@ -199,8 +199,8 @@ func (s *ImportServicesSuite) TestImportV2_BroadcastFailsReturnsError() { s.NoError(err) s.NotNil(resp) - s.True(errors.Is(merr.Error(resp.GetStatus()), merr.ErrImportFailed)) - s.Contains(resp.GetStatus().GetReason(), "failed to broadcast import") + s.True(errors.Is(merr.Error(resp.GetStatus()), merr.ErrServiceUnavailable)) + s.Contains(resp.GetStatus().GetReason(), "broadcast") } func (s *ImportServicesSuite) TestImportV2_SuccessReturnsJobID() { @@ -395,7 +395,7 @@ func (s *ImportServicesSuite) TestCreateImportJobFromAck_AllocatorFailsReturnsEr server.stateCode.Store(commonpb.StateCode_Healthy) mockAllocator := allocator.NewMockAllocator(s.T()) - mockAllocator.EXPECT().AllocN(mock.Anything).Return(int64(0), int64(0), errors.New("allocation failed")) + mockAllocator.EXPECT().AllocN(mock.Anything).Return(int64(0), int64(0), merr.WrapErrServiceUnavailable("allocation failed")) server.allocator = mockAllocator req := &internalpb.ImportRequestInternal{ @@ -412,7 +412,7 @@ func (s *ImportServicesSuite) TestCreateImportJobFromAck_AllocatorFailsReturnsEr s.NoError(err) s.NotNil(resp) - s.True(errors.Is(merr.Error(resp.GetStatus()), merr.ErrImportFailed)) + s.True(errors.Is(merr.Error(resp.GetStatus()), merr.ErrServiceUnavailable)) s.Contains(resp.GetStatus().GetReason(), "alloc id failed") } @@ -493,7 +493,7 @@ func (s *ImportServicesSuite) TestCreateImportJobFromAck_AddJobFailsReturnsError catalog.EXPECT().ListImportJobs(mock.Anything).Return(nil, nil) catalog.EXPECT().ListPreImportTasks(mock.Anything).Return(nil, nil) catalog.EXPECT().ListImportTasks(mock.Anything).Return(nil, nil) - catalog.EXPECT().SaveImportJob(mock.Anything, mock.Anything).Return(errors.New("save job failed")) + catalog.EXPECT().SaveImportJob(mock.Anything, mock.Anything).Return(merr.WrapErrServiceUnavailable("save job failed")) importMeta, err := NewImportMeta(context.TODO(), catalog, nil, nil) s.NoError(err) @@ -528,7 +528,7 @@ func (s *ImportServicesSuite) TestCreateImportJobFromAck_AddJobFailsReturnsError s.NoError(err) s.NotNil(resp) - s.True(errors.Is(merr.Error(resp.GetStatus()), merr.ErrImportFailed)) + s.True(errors.Is(merr.Error(resp.GetStatus()), merr.ErrServiceUnavailable)) s.Contains(resp.GetStatus().GetReason(), "add import job failed") } diff --git a/internal/datacoord/import_util.go b/internal/datacoord/import_util.go index 787d257a1e..e23e54eb8f 100644 --- a/internal/datacoord/import_util.go +++ b/internal/datacoord/import_util.go @@ -670,8 +670,8 @@ func ListBinlogsAndGroupBySegment(ctx context.Context, return nil, merr.WrapErrImportFailed("no insert binlogs to import") } if len(importFile.GetPaths()) > 2 { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("too many input paths for binlog import. "+ - "Valid paths length should be one or two, but got paths:%s", importFile.GetPaths())) + return nil, merr.WrapErrImportFailedMsg("too many input paths for binlog import. "+ + "Valid paths length should be one or two, but got paths:%s", importFile.GetPaths()) } insertPrefix := importFile.GetPaths()[0] @@ -790,18 +790,18 @@ func ListBinlogImportRequestFiles(ctx context.Context, cm storage.ChunkManager, } err := conc.AwaitAll(futures...) if err != nil { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("list binlogs failed, err=%s", err)) + return nil, merr.WrapErrServiceUnavailableMsg("list binlogs failed, err=%s", err) } resFiles = lo.Filter(resFiles, func(file *internalpb.ImportFile, _ int) bool { return len(file.GetPaths()) > 0 }) if len(resFiles) == 0 { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("no binlog to import, input=%s", reqFiles)) + return nil, merr.WrapErrImportFailedMsg("no binlog to import, input=%s", reqFiles) } if len(resFiles) > paramtable.Get().DataCoordCfg.MaxFilesPerImportReq.GetAsInt() { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("The max number of import files should not exceed %d, but got %d", - paramtable.Get().DataCoordCfg.MaxFilesPerImportReq.GetAsInt(), len(resFiles))) + return nil, merr.WrapErrImportFailedMsg("The max number of import files should not exceed %d, but got %d", + paramtable.Get().DataCoordCfg.MaxFilesPerImportReq.GetAsInt(), len(resFiles)) } log.Info("list binlogs prefixes for import done", zap.Int("num", len(resFiles)), zap.Any("binlog_prefixes", resFiles)) return resFiles, nil @@ -812,7 +812,7 @@ func ValidateMaxImportJobExceed(ctx context.Context, importMeta ImportMeta) erro maxNum := paramtable.Get().DataCoordCfg.MaxImportJobNum.GetAsInt() executingNum := importMeta.CountJobBy(ctx, WithoutJobStates(internalpb.ImportJobState_Completed, internalpb.ImportJobState_Failed)) if executingNum >= maxNum { - return merr.WrapErrImportFailed( + return merr.WrapErrImportSysFailed( fmt.Sprintf("The number of jobs has reached the limit, please try again later. " + "If your request is set to only import a single file, " + "please consider importing multiple files in one request for better efficiency.")) diff --git a/internal/datacoord/index_meta.go b/internal/datacoord/index_meta.go index 1f46e34581..3a9733c7df 100644 --- a/internal/datacoord/index_meta.go +++ b/internal/datacoord/index_meta.go @@ -468,7 +468,7 @@ func (m *indexMeta) canCreateIndex(req *indexpb.CreateIndexRequest, isJSON bool) index.IndexName, index.FieldID, index.IndexParams, index.UserIndexParams, index.TypeParams)), zap.String("current index", fmt.Sprintf("{index_name: %s, field_id: %d, index_params: %v, user_params: %v, type_params: %v}", req.GetIndexName(), req.GetFieldID(), req.GetIndexParams(), req.GetUserIndexParams(), req.GetTypeParams()))) - return 0, fmt.Errorf("CreateIndex failed: %s", errMsg) + return 0, merr.WrapErrParameterInvalidMsg("%s", errMsg) } if req.FieldID == index.FieldID { if isJSON { @@ -484,7 +484,11 @@ func (m *indexMeta) canCreateIndex(req *indexpb.CreateIndexRequest, isJSON bool) // creating multiple indexes on same field is not supported errMsg := "CreateIndex failed: creating multiple indexes on same field is not supported" log.Warn(errMsg) - return 0, errors.New(errMsg) + // Client-caused conflict, same family as the "one distinct index per + // field" case above: return an input-class error rather than + // ServiceInternal (code 5, "never return out of Milvus") so the + // caller is not blamed with a system error / counted as fail_system. + return 0, merr.WrapErrParameterInvalidMsg("%s", errMsg) } } return 0, nil @@ -1012,7 +1016,7 @@ func (m *indexMeta) UpdateVersion(buildID, nodeID UniqueID) error { log.Ctx(m.ctx).Info("IndexCoord metaTable UpdateVersion receive", zap.Int64("buildID", buildID), zap.Int64("nodeID", nodeID)) segIdx, ok := m.segmentBuildInfo.Get(buildID) if !ok { - return fmt.Errorf("there is no index with buildID: %d", buildID) + return merr.WrapErrServiceInternalMsg("there is no index with buildID: %d", buildID) } updateFunc := func(segIdx *model.SegmentIndex) error { @@ -1031,7 +1035,7 @@ func (m *indexMeta) UpdateIndexState(buildID UniqueID, state commonpb.IndexState segIdx, ok := m.segmentBuildInfo.Get(buildID) if !ok { log.Ctx(m.ctx).Warn("there is no index with buildID", zap.Int64("buildID", buildID)) - return fmt.Errorf("there is no index with buildID: %d", buildID) + return merr.WrapErrServiceInternalMsg("there is no index with buildID: %d", buildID) } updateFunc := func(segIdx *model.SegmentIndex) error { @@ -1141,7 +1145,7 @@ func (m *indexMeta) BuildIndex(buildID UniqueID) error { segIdx, ok := m.segmentBuildInfo.Get(buildID) if !ok { - return fmt.Errorf("there is no index with buildID: %d", buildID) + return merr.WrapErrServiceInternalMsg("there is no index with buildID: %d", buildID) } updateFunc := func(segIdx *model.SegmentIndex) error { diff --git a/internal/datacoord/index_service.go b/internal/datacoord/index_service.go index 6dcec1fb23..b9084f741b 100644 --- a/internal/datacoord/index_service.go +++ b/internal/datacoord/index_service.go @@ -1052,7 +1052,7 @@ func (s *Server) DropIndex(ctx context.Context, req *indexpb.DropIndexRequest) ( } if typeutil.IsVectorType(field.GetDataType()) { log.Warn("vector index cannot be dropped on loaded collection", zap.String("indexName", req.IndexName), zap.Int64("collectionID", req.GetCollectionID()), zap.Int64("fieldID", index.FieldID)) - return merr.Status(merr.WrapErrParameterInvalidMsg(fmt.Sprintf("vector index cannot be dropped on loaded collection: %d", req.GetCollectionID()))), nil + return merr.Status(merr.WrapErrParameterInvalidMsg("vector index cannot be dropped on loaded collection: %d", req.GetCollectionID())), nil } } } diff --git a/internal/datacoord/meta.go b/internal/datacoord/meta.go index 21160e34b0..8d069592be 100644 --- a/internal/datacoord/meta.go +++ b/internal/datacoord/meta.go @@ -816,7 +816,7 @@ func (m *meta) GetSegmentsChannels(segmentIDs []UniqueID) (map[int64]string, err for _, segmentID := range segmentIDs { segment := m.segments.GetSegment(segmentID) if segment == nil { - return nil, errors.New(fmt.Sprintf("cannot find segment %d", segmentID)) + return nil, merr.WrapErrServiceInternalMsg("cannot find segment %d", segmentID) } segChannels[segmentID] = segment.GetInsertChannel() } @@ -840,7 +840,7 @@ func (m *meta) SetState(ctx context.Context, segmentID UniqueID, targetState com if targetState == commonpb.SegmentState_Dropped { return nil } - return fmt.Errorf("segment is not exist with ID = %d", segmentID) + return merr.WrapErrSegmentNotFound(segmentID) } // Persist segment updates first. clonedSegment := curSegInfo.Clone() @@ -941,12 +941,12 @@ func (p *updateSegmentPack) Validate() error { segmentInMeta := p.meta.segments.GetSegment(segment.ID) if segmentInMeta.State == commonpb.SegmentState_Flushed && segment.State != commonpb.SegmentState_Dropped { // if the segment is flushed, we should not update the segment meta, ignore the operation directly. - return errors.Wrapf(ErrIgnoredSegmentMetaOperation, + return merr.Wrapf(errIgnoredSegmentMetaOperation, "segment is flushed, segmentID: %d", segment.ID) } if segment.GetDmlPosition().GetTimestamp() < segmentInMeta.GetDmlPosition().GetTimestamp() { - return errors.Wrapf(ErrIgnoredSegmentMetaOperation, + return merr.Wrapf(errIgnoredSegmentMetaOperation, "dml time tick is less than the segment meta, segmentID: %d, new incoming time tick: %d, existing time tick: %d", segment.ID, segment.GetDmlPosition().GetTimestamp(), @@ -1223,7 +1223,7 @@ func updateManifestPathIfNewer(segment *SegmentInfo, manifestPath string) error return err } if currentBase != incomingBase { - return merr.WrapErrServiceInternal(fmt.Sprintf("manifest base path mismatch for segment %d: current %s, incoming %s", segment.GetID(), currentBase, incomingBase)) + return merr.WrapErrServiceInternalMsg("manifest base path mismatch for segment %d: current %s, incoming %s", segment.GetID(), currentBase, incomingBase) } if incomingVersion > currentVersion { segment.ManifestPath = manifestPath @@ -1824,6 +1824,14 @@ func (m *meta) UpdateSegmentsInfo(ctx context.Context, operators ...UpdateOperat // Validate the update pack. if err := updatePack.Validate(); err != nil { + // A stale save-binlog-paths update (segment already flushed, or an + // outdated time tick) is a benign no-op: skip the meta write and + // report success so the caller does not retry. The signal stays + // inside this package on purpose; see errIgnoredSegmentMetaOperation. + if errors.Is(err, errIgnoredSegmentMetaOperation) { + log.Ctx(ctx).Info("meta update: ignored stale segment meta operation", zap.Error(err)) + return nil + } return err } updatePack.prepareSegmentMetricUpdates() @@ -2100,7 +2108,7 @@ func (m *meta) AddAllocation(segmentID UniqueID, allocation *Allocation) error { if curSegInfo == nil { // TODO: Error handling. log.Ctx(m.ctx).Error("meta update: add allocation failed - segment not found", zap.Int64("segmentID", segmentID)) - return errors.New("meta update: add allocation failed - segment not found") + return merr.WrapErrSegmentNotFound(segmentID, "meta update: add allocation failed") } // As we use global segment lastExpire to guarantee data correctness after restart // there is no need to persist allocation to meta store, only update allocation in-memory meta. @@ -2287,7 +2295,7 @@ func validateCompactionFallbackStartPosition(compactFromSegInfos []*SegmentInfo, if maxCommitTs == 0 || fallbackStart.GetTimestamp() >= maxCommitTs { return nil } - return errors.Errorf( + return merr.WrapErrServiceInternalMsg( "compaction fallback start position timestamp %d is earlier than max input commit timestamp %d", fallbackStart.GetTimestamp(), maxCommitTs) @@ -2661,7 +2669,7 @@ func (m *meta) HasSegments(segIDs []UniqueID) (bool, error) { for _, segID := range segIDs { if _, ok := m.segments.segments[segID]; !ok { - return false, fmt.Errorf("segment is not exist with ID = %d", segID) + return false, merr.WrapErrServiceInternalMsg("segment is not exist with ID = %d", segID) } } return true, nil @@ -2747,7 +2755,7 @@ func (m *meta) collectionHasTextFields(collectionID int64) bool { // UpdateChannelCheckpoint updates and saves channel checkpoint. func (m *meta) UpdateChannelCheckpoint(ctx context.Context, vChannel string, pos *msgpb.MsgPosition) error { if pos == nil || pos.GetMsgID() == nil { - return fmt.Errorf("channelCP is nil, vChannel=%s", vChannel) + return merr.WrapErrServiceInternalMsg("channelCP is nil, vChannel=%s", vChannel) } // Clamp checkpoint to not advance beyond min growing segment checkpoint. @@ -3331,7 +3339,7 @@ func (m *meta) completeBumpSchemaVersionCompactionMutation( if currentManifest != resultManifest { manifestCompare, err := packed.CompareManifestPath(resultManifest, currentManifest) if err != nil { - return nil, nil, merr.WrapErrIllegalCompactionPlan(fmt.Sprintf("schema bump compaction result manifest is not comparable with current manifest: %v", err)) + return nil, nil, merr.WrapErrIllegalCompactionPlanMsg("schema bump compaction result manifest is not comparable with current manifest: %v", err) } if manifestCompare <= 0 { return nil, nil, merr.WrapErrIllegalCompactionPlan("schema bump compaction result manifest is not newer than current manifest") @@ -3392,7 +3400,7 @@ func (m *meta) completeBumpSchemaVersionReplacementMutation( ) ([]*SegmentInfo, *segMetricMutation, error) { idRange := t.GetPreAllocatedSegmentIDs() if idRange == nil || idRange.GetBegin()+1 != idRange.GetEnd() || resultSegment.GetSegmentID() != idRange.GetBegin() { - return nil, nil, merr.WrapErrIllegalCompactionPlan(fmt.Sprintf("schema bump replacement result segment ID %d does not match the pre-allocated segment ID", resultSegment.GetSegmentID())) + return nil, nil, merr.WrapErrIllegalCompactionPlanMsg("schema bump replacement result segment ID %d does not match the pre-allocated segment ID", resultSegment.GetSegmentID()) } dropped := oldSegment.Clone() @@ -3555,7 +3563,7 @@ func (m *meta) GetFileResources(ctx context.Context, resourceIDs ...int64) ([]*i if resource, ok := m.resourceIDMap[id]; ok { resources = append(resources, resource) } else { - return nil, errors.Errorf("file resource %d not found", id) + return nil, merr.WrapErrServiceInternalMsg("file resource %d not found", id) } } return resources, nil diff --git a/internal/datacoord/meta_test.go b/internal/datacoord/meta_test.go index 394d419b6a..6c8eec9599 100644 --- a/internal/datacoord/meta_test.go +++ b/internal/datacoord/meta_test.go @@ -3498,7 +3498,7 @@ func TestUpdateSegmentsInfo(t *testing.T) { UpdateStartPosition([]*datapb.SegmentStartPosition{{SegmentID: 1, StartPosition: &msgpb.MsgPosition{MsgID: []byte{1, 2, 3}}}}), UpdateCheckPointOperator(1, []*datapb.CheckPoint{{SegmentID: 1, NumOfRows: 10, Position: &msgpb.MsgPosition{MsgID: []byte{1, 2, 3}, Timestamp: 99}}}, true), ) - assert.True(t, errors.Is(err, ErrIgnoredSegmentMetaOperation)) + assert.NoError(t, err) // stale update is swallowed as a benign no-op; segment must stay unchanged below err = meta.UpdateSegmentsInfo( context.TODO(), @@ -3536,7 +3536,7 @@ func TestUpdateSegmentsInfo(t *testing.T) { []*datapb.FieldBinlog{}), UpdateCheckPointOperator(1, []*datapb.CheckPoint{{SegmentID: 1, NumOfRows: 12, Position: &msgpb.MsgPosition{MsgID: []byte{1, 2, 3}, Timestamp: 101}}}, true), ) - assert.True(t, errors.Is(err, ErrIgnoredSegmentMetaOperation)) + assert.NoError(t, err) // stale update is swallowed as a benign no-op; segment must stay unchanged below updated = meta.GetHealthySegment(context.TODO(), 1) assert.Equal(t, updated.NumOfRows, int64(20)) diff --git a/internal/datacoord/meta_util.go b/internal/datacoord/meta_util.go index 184bec85ca..577067ed78 100644 --- a/internal/datacoord/meta_util.go +++ b/internal/datacoord/meta_util.go @@ -22,7 +22,16 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/datapb" ) -var ErrIgnoredSegmentMetaOperation = errors.New("ignored segment meta operation") +// errIgnoredSegmentMetaOperation is an internal control-flow signal: the +// requested segment meta update is stale (segment already flushed, or an +// outdated time tick) and is skipped as a benign no-op. It never crosses the +// package boundary — UpdateSegmentsInfo swallows it and returns nil. +// +// It MUST stay a plain errors.New identity sentinel. A merr-typed value would +// make errors.Is match by error code (milvusError.Is compares codes only), so +// any error sharing the code — e.g. a real etcd write failure wrapped as +// ServiceInternal — would be misread as ignorable and acknowledged as success. +var errIgnoredSegmentMetaOperation = errors.New("ignored segment meta operation") // reviseVChannelInfo will revise the datapb.VchannelInfo for upgrade compatibility from 2.0.2 func reviseVChannelInfo(vChannel *datapb.VchannelInfo) { diff --git a/internal/datacoord/metrics_info.go b/internal/datacoord/metrics_info.go index bb7261d537..c8d477f6da 100644 --- a/internal/datacoord/metrics_info.go +++ b/internal/datacoord/metrics_info.go @@ -18,10 +18,8 @@ package datacoord import ( "context" - "fmt" "sync" - "github.com/cockroachdb/errors" "github.com/tidwall/gjson" "go.uber.org/zap" "golang.org/x/sync/errgroup" @@ -138,7 +136,7 @@ func (s *Server) getSegmentsJSON(ctx context.Context, req *milvuspb.GetMetricsRe } return string(bs), nil } - return "", fmt.Errorf("invalid param value in=[%s], it should be dc or dn", in) + return "", merr.WrapErrParameterInvalidMsg("invalid param value in=[%s], it should be dc or dn", in) } func (s *Server) getDistJSON(ctx context.Context, req *milvuspb.GetMetricsRequest) string { @@ -308,7 +306,7 @@ func (s *Server) getIndexNodeMetrics(ctx context.Context, req *milvuspb.GetMetri }, } if node == nil { - return infos, errors.New("IndexNode is nil") + return infos, merr.WrapErrServiceInternalMsg("index node is nil") } metrics, err := node.GetMetrics(ctx, req) diff --git a/internal/datacoord/partition_stats_meta.go b/internal/datacoord/partition_stats_meta.go index 6e1adec77e..0dd819ee10 100644 --- a/internal/datacoord/partition_stats_meta.go +++ b/internal/datacoord/partition_stats_meta.go @@ -2,7 +2,6 @@ package datacoord import ( "context" - "fmt" "sync" "go.uber.org/zap" @@ -183,11 +182,11 @@ func (psm *partitionStatsMeta) innerSaveCurrentPartitionStatsVersion(collectionI if _, ok := psm.partitionStatsInfos[vChannel]; !ok { return merr.WrapErrClusteringCompactionMetaError("SaveCurrentPartitionStatsVersion", - fmt.Errorf("update current partition stats version failed, there is no partition info exists with collID: %d, partID: %d, vChannel: %s", collectionID, partitionID, vChannel)) + merr.WrapErrServiceInternalMsg("update current partition stats version failed, there is no partition info exists with collID: %d, partID: %d, vChannel: %s", collectionID, partitionID, vChannel)) } if _, ok := psm.partitionStatsInfos[vChannel][partitionID]; !ok { return merr.WrapErrClusteringCompactionMetaError("SaveCurrentPartitionStatsVersion", - fmt.Errorf("update current partition stats version failed, there is no partition info exists with collID: %d, partID: %d, vChannel: %s", collectionID, partitionID, vChannel)) + merr.WrapErrServiceInternalMsg("update current partition stats version failed, there is no partition info exists with collID: %d, partID: %d, vChannel: %s", collectionID, partitionID, vChannel)) } if err := psm.catalog.SaveCurrentPartitionStatsVersion(psm.ctx, collectionID, partitionID, vChannel, currentPartitionStatsVersion); err != nil { diff --git a/internal/datacoord/segment_allocation_policy.go b/internal/datacoord/segment_allocation_policy.go index 73f27d82e0..0f8e57486d 100644 --- a/internal/datacoord/segment_allocation_policy.go +++ b/internal/datacoord/segment_allocation_policy.go @@ -23,12 +23,12 @@ import ( "sort" "time" - "github.com/cockroachdb/errors" "github.com/samber/lo" "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/proto/datapb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/tsoutil" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" @@ -38,7 +38,7 @@ type calUpperLimitPolicy func(schema *schemapb.CollectionSchema) (int, error) func calBySchemaPolicy(schema *schemapb.CollectionSchema) (int, error) { if schema == nil { - return -1, errors.New("nil schema") + return -1, merr.WrapErrServiceInternalMsg("nil schema") } sizePerRecord, err := typeutil.EstimateSizePerRecord(schema) if err != nil { @@ -46,7 +46,7 @@ func calBySchemaPolicy(schema *schemapb.CollectionSchema) (int, error) { } // check zero value, preventing panicking if sizePerRecord == 0 { - return -1, errors.New("zero size record schema found") + return -1, merr.WrapErrServiceInternalMsg("zero size record schema found") } threshold := Params.DataCoordCfg.SegmentMaxSize.GetAsFloat() * 1024 * 1024 return int(threshold / float64(sizePerRecord)), nil diff --git a/internal/datacoord/segment_manager.go b/internal/datacoord/segment_manager.go index 30115b9b7f..8512c91c9b 100644 --- a/internal/datacoord/segment_manager.go +++ b/internal/datacoord/segment_manager.go @@ -23,7 +23,6 @@ import ( "sync" "time" - "github.com/cockroachdb/errors" "github.com/samber/lo" "go.opentelemetry.io/otel" "go.uber.org/zap" @@ -36,6 +35,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/datapb" "github.com/milvus-io/milvus/pkg/v3/util/lock" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/metautil" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/retry" @@ -299,7 +299,7 @@ func (s *SegmentManager) genLastExpireTsForSegments() (Timestamp, error) { }, retry.Attempts(Params.DataCoordCfg.AllocLatestExpireAttempt.GetAsUint()), retry.Sleep(200*time.Millisecond)) if allocateErr != nil { log.Warn("cannot allocate latest lastExpire from rootCoord", zap.Error(allocateErr)) - return 0, errors.New("global max expire ts is unavailable for segment manager") + return 0, merr.WrapErrServiceInternalMsg("global max expire ts is unavailable for segment manager") } return latestTs, nil } @@ -466,7 +466,7 @@ func (s *SegmentManager) estimateMaxNumOfRows(collectionID UniqueID) (int, error // it's ok to use meta.GetCollection here, since collection meta is set before using segmentManager collMeta := s.meta.GetCollection(collectionID) if collMeta == nil { - return -1, fmt.Errorf("failed to get collection %d", collectionID) + return -1, merr.WrapErrServiceInternalMsg("failed to get collection %d", collectionID) } return s.estimatePolicy(collMeta.Schema) } diff --git a/internal/datacoord/server.go b/internal/datacoord/server.go index 7381f16936..a22bbb28cf 100644 --- a/internal/datacoord/server.go +++ b/internal/datacoord/server.go @@ -26,7 +26,6 @@ import ( "time" "github.com/blang/semver/v4" - "github.com/cockroachdb/errors" "github.com/samber/lo" "github.com/tidwall/gjson" "github.com/tikv/client-go/v2/txnkv" @@ -462,7 +461,7 @@ func (s *Server) SetSession(session sessionutil.SessionInterface) error { s.session = session s.icSession = session if s.session == nil { - return errors.New("session is nil, the etcd client connection may have failed") + return merr.WrapErrServiceNotReadyMsg("session is nil, the etcd client connection may have failed") } return nil } @@ -618,7 +617,7 @@ func (s *Server) initKV() error { s.kv = etcdkv.NewEtcdKV(s.etcdCli, s.metaRootPath, etcdkv.WithRequestTimeout(paramtable.Get().EtcdCfg.RequestTimeout.GetAsDuration(time.Millisecond))) default: - return retry.Unrecoverable(fmt.Errorf("not supported meta store: %s", metaType)) + return retry.Unrecoverable(merr.WrapErrServiceInternalMsg("unsupported meta store: %s", metaType)) } log.Info("data coordinator successfully connected to metadata store", zap.String("metaType", metaType)) return nil @@ -1027,7 +1026,7 @@ func (s *Server) flushFlushingSegment(ctx context.Context, segmentID UniqueID) e return ctx.Err() } // underlying etcd may return context canceled, so we need to return a error to retry. - return errors.New("flush segment complete failed") + return merr.WrapErrServiceInternalMsg("flush segment complete failed") } return nil }, retry.AttemptAlways()) @@ -1117,7 +1116,7 @@ func (s *Server) CleanMeta() error { err2 := s.watchClient.RemoveWithPrefix(s.ctx, "") if err2 != nil { if err != nil { - err = fmt.Errorf("failed to CleanMeta[metadata cleanup error: %w][watchdata cleanup error: %v]", err, err2) + err = merr.Wrapf(err, "failed to clean meta (watchdata cleanup error: %v)", err2) } else { err = err2 } diff --git a/internal/datacoord/services.go b/internal/datacoord/services.go index f51275ae78..edb95c322a 100644 --- a/internal/datacoord/services.go +++ b/internal/datacoord/services.go @@ -137,7 +137,7 @@ func (s *Server) flushCollection(ctx context.Context, collectionID UniqueID, flu for _, channel := range coll.VChannelNames { sealedSegmentIDs, err := s.segmentManager.SealAllSegments(ctx, channel, toFlushSegments) if err != nil { - return nil, errors.Wrapf(err, "failed to flush collection %d", collectionID) + return nil, merr.Wrapf(err, "failed to flush collection %d", collectionID) } for _, sealedSegmentID := range sealedSegmentIDs { sealedSegmentsIDDict[sealedSegmentID] = true @@ -691,13 +691,12 @@ func (s *Server) SaveBinlogPaths(ctx context.Context, req *datapb.SaveBinlogPath UpdateAsDroppedIfEmptyWhenFlushing(req.GetSegmentID()), ) - // Update segment info in memory and meta. + // Update segment info in memory and meta. Stale updates (segment already + // flushed / outdated time tick) are swallowed inside UpdateSegmentsInfo as + // benign no-ops, so any error here is a real failure. if err := s.meta.UpdateSegmentsInfo(ctx, operators...); err != nil { - if !errors.Is(err, ErrIgnoredSegmentMetaOperation) { - log.Error("save binlog and checkpoints failed", zap.Error(err)) - return merr.Status(err), nil - } - log.Info("save binlog and checkpoints failed with ignorable error", zap.Error(err)) + log.Error("save binlog and checkpoints failed", zap.Error(err)) + return merr.Status(err), nil } s.meta.SetLastWrittenTime(req.GetSegmentID()) @@ -1292,7 +1291,7 @@ func (s *Server) GetMetrics(ctx context.Context, req *milvuspb.GetMetricsRequest msg := "failed to get metrics" log.Warn(msg, zap.Error(err)) return &milvuspb.GetMetricsResponse{ - Status: merr.Status(errors.Wrap(err, msg)), + Status: merr.Status(merr.Wrap(err, msg)), }, nil } @@ -1627,7 +1626,7 @@ OUTER: break OUTER } } else { - resp.Status = merr.Status(merr.WrapErrParameterInvalidMsg("FlushAllTss or FlushAllTs is required")) + resp.Status = merr.Status(merr.WrapErrParameterMissingMsg("FlushAllTss or FlushAllTs is required")) return resp, nil } } @@ -1925,12 +1924,12 @@ func (s *Server) ImportV2(ctx context.Context, in *internalpb.ImportRequestInter jobID := in.GetJobID() if jobID == 0 { if s.allocator == nil { - resp.Status = merr.Status(merr.WrapErrImportFailed("allocator not initialized")) + resp.Status = merr.Status(merr.WrapErrServiceUnavailable("allocator not initialized")) return resp, nil } jobID, _, err = s.allocator.AllocN(1) if err != nil { - resp.Status = merr.Status(merr.WrapErrImportFailed(fmt.Sprintf("failed to allocate job ID: %v", err))) + resp.Status = merr.Status(merr.Wrap(err, "failed to allocate job ID")) return resp, nil } } @@ -1950,7 +1949,7 @@ func (s *Server) ImportV2(ctx context.Context, in *internalpb.ImportRequestInter ) if err != nil { log.Warn("failed to broadcast import message", zap.Error(err)) - resp.Status = merr.Status(merr.WrapErrImportFailed(fmt.Sprintf("failed to broadcast import: %v", err))) + resp.Status = merr.Status(merr.Wrap(err, "failed to broadcast import")) return resp, nil } @@ -2000,7 +1999,7 @@ func (s *Server) createImportJobFromAck(ctx context.Context, in *internalpb.Impo // Allocate file ids. idStart, _, err := s.allocator.AllocN(int64(len(files)) + 1) if err != nil { - resp.Status = merr.Status(merr.WrapErrImportFailed(fmt.Sprintf("alloc id failed: %v", err))) + resp.Status = merr.Status(merr.Wrap(err, "alloc id failed")) return resp, nil } files = lo.Map(files, func(importFile *internalpb.ImportFile, i int) *internalpb.ImportFile { @@ -2013,7 +2012,7 @@ func (s *Server) createImportJobFromAck(ctx context.Context, in *internalpb.Impo return resp, nil } if err != nil { - resp.Status = merr.Status(merr.WrapErrImportFailed(fmt.Sprintf("get collection failed: %v", err))) + resp.Status = merr.Status(merr.Wrap(err, "get collection failed")) return resp, nil } if importCollectionInfo == nil { @@ -2048,7 +2047,7 @@ func (s *Server) createImportJobFromAck(ctx context.Context, in *internalpb.Impo } err = s.importMeta.AddJob(ctx, job) if err != nil { - resp.Status = merr.Status(merr.WrapErrImportFailed(fmt.Sprintf("add import job failed: %v", err))) + resp.Status = merr.Status(merr.Wrap(err, "add import job failed")) return resp, nil } @@ -2075,13 +2074,13 @@ func (s *Server) GetImportProgress(ctx context.Context, in *internalpb.GetImport } jobID, err := strconv.ParseInt(in.GetJobID(), 10, 64) if err != nil { - resp.Status = merr.Status(merr.WrapErrImportFailed(fmt.Sprintf("parse job id failed: %v", err))) + resp.Status = merr.Status(merr.WrapErrParameterInvalidMsg("parse job id failed: %v", err)) return resp, nil } job := s.importMeta.GetJob(ctx, jobID) if job == nil { - resp.Status = merr.Status(merr.WrapErrImportFailed(fmt.Sprintf("import job does not exist, jobID=%d", jobID))) + resp.Status = merr.Status(merr.WrapErrImportSysFailedMsg("import job does not exist, jobID=%d", jobID)) return resp, nil } progress, state, importedRows, totalRows, reason := GetJobProgress(ctx, jobID, s.importMeta, s.meta) @@ -2481,14 +2480,14 @@ func (s *Server) RestoreSnapshot(ctx context.Context, req *datapb.RestoreSnapsho // Validate parameters if req.GetName() == "" { - err := merr.WrapErrParameterInvalidMsg("snapshot name is required") + err := merr.WrapErrParameterMissingMsg("snapshot name is required") log.Warn("invalid request", zap.Error(err)) return &datapb.RestoreSnapshotResponse{ Status: merr.Status(err), }, nil } if req.GetTargetCollectionName() == "" { - err := merr.WrapErrParameterInvalidMsg("target collection name is required") + err := merr.WrapErrParameterMissingMsg("target collection name is required") log.Warn("invalid request", zap.Error(err)) return &datapb.RestoreSnapshotResponse{ Status: merr.Status(err), @@ -2887,7 +2886,7 @@ func (s *Server) ListRefreshExternalCollectionJobs(ctx context.Context, req *dat func (s *Server) broadcastCommitImportMessage(ctx context.Context, job ImportJob) error { vchannels := job.GetVchannels() if len(vchannels) == 0 { - return merr.WrapErrImportFailed(fmt.Sprintf("job %d has no vchannels", job.GetJobID())) + return merr.WrapErrImportSysFailedMsg("job %d has no vchannels", job.GetJobID()) } broadcaster, err := s.startBroadcastWithCollectionID(ctx, job.GetCollectionID()) @@ -2914,7 +2913,7 @@ func (s *Server) broadcastCommitImportMessage(ctx context.Context, job ImportJob func (s *Server) broadcastRollbackImportMessage(ctx context.Context, job ImportJob) error { vchannels := job.GetVchannels() if len(vchannels) == 0 { - return merr.WrapErrImportFailed(fmt.Sprintf("job %d has no vchannels", job.GetJobID())) + return merr.WrapErrImportSysFailedMsg("job %d has no vchannels", job.GetJobID()) } broadcaster, err := s.startBroadcastWithCollectionID(ctx, job.GetCollectionID()) @@ -2949,7 +2948,7 @@ func (s *Server) validateAndExecuteImportAction( } job := s.importMeta.GetJob(ctx, jobID) if job == nil { - return merr.Status(merr.WrapErrImportFailed(fmt.Sprintf("job %d not found", jobID))), nil + return merr.Status(merr.WrapErrImportSysFailedMsg("job %d not found", jobID)), nil } if st := validateState(job); st != nil { return st, nil diff --git a/internal/datacoord/services_commit_backfill.go b/internal/datacoord/services_commit_backfill.go index b20ef6279f..25cf24a496 100644 --- a/internal/datacoord/services_commit_backfill.go +++ b/internal/datacoord/services_commit_backfill.go @@ -186,7 +186,7 @@ const maxBackfillResultBytes int64 = 64 * 1024 * 1024 // 64MiB // reject s3a:///... paths early. func (s *Server) loadBackfillResult(ctx context.Context, rawPath string) (*BackfillResult, error) { if rawPath == "" { - return nil, merr.WrapErrParameterInvalidMsg("result_path is required") + return nil, merr.WrapErrParameterMissingMsg("result_path is required") } bucket := bucketFromChunkManager(s.meta.chunkManager) key, err := normalizeObjectKey(rawPath, bucket) diff --git a/internal/datacoord/services_test.go b/internal/datacoord/services_test.go index 7ef121bd90..a56b3fdc08 100644 --- a/internal/datacoord/services_test.go +++ b/internal/datacoord/services_test.go @@ -1645,7 +1645,6 @@ func TestGetRecoveryInfoV2(t *testing.T) { func TestImportV2(t *testing.T) { ctx := context.Background() - mockErr := errors.New("mock err") t.Run("ImportV2", func(t *testing.T) { // server not healthy @@ -1682,11 +1681,11 @@ func TestImportV2(t *testing.T) { s.importMeta, err = NewImportMeta(context.TODO(), catalog, nil, nil) assert.NoError(t, err) alloc := allocator.NewMockAllocator(t) - alloc.EXPECT().AllocN(mock.Anything).Return(0, 0, mockErr) + alloc.EXPECT().AllocN(mock.Anything).Return(0, 0, merr.WrapErrServiceUnavailable("mock err")) s.allocator = alloc resp, err = s.ImportV2(ctx, &internalpb.ImportRequestInternal{}) assert.NoError(t, err) - assert.True(t, errors.Is(merr.Error(resp.GetStatus()), merr.ErrImportFailed)) + assert.True(t, errors.Is(merr.Error(resp.GetStatus()), merr.ErrServiceUnavailable)) }) t.Run("GetImportProgress", func(t *testing.T) { @@ -1703,7 +1702,7 @@ func TestImportV2(t *testing.T) { JobID: "@%$%$#%", }) assert.NoError(t, err) - assert.True(t, errors.Is(merr.Error(resp.GetStatus()), merr.ErrImportFailed)) + assert.True(t, errors.Is(merr.Error(resp.GetStatus()), merr.ErrParameterInvalid)) // job does not exist catalog := mocks.NewDataCoordCatalog(t) @@ -1723,7 +1722,8 @@ func TestImportV2(t *testing.T) { JobID: "-1", }) assert.NoError(t, err) - assert.True(t, errors.Is(merr.Error(resp.GetStatus()), merr.ErrImportFailed)) + // job-not-found is a server-side orchestration issue, not malformed user data + assert.True(t, errors.Is(merr.Error(resp.GetStatus()), merr.ErrImportSysFailed)) // normal case var job ImportJob = &importJob{ @@ -3156,7 +3156,7 @@ func TestServer_RestoreSnapshot(t *testing.T) { assert.NoError(t, err) assert.Error(t, merr.Error(resp.GetStatus())) - assert.True(t, errors.Is(merr.Error(resp.GetStatus()), merr.ErrParameterInvalid)) + assert.True(t, errors.Is(merr.Error(resp.GetStatus()), merr.ErrParameterMissing)) }) t.Run("missing_collection_name", func(t *testing.T) { @@ -3173,7 +3173,7 @@ func TestServer_RestoreSnapshot(t *testing.T) { assert.NoError(t, err) assert.Error(t, merr.Error(resp.GetStatus())) - assert.True(t, errors.Is(merr.Error(resp.GetStatus()), merr.ErrParameterInvalid)) + assert.True(t, errors.Is(merr.Error(resp.GetStatus()), merr.ErrParameterMissing)) }) t.Run("snapshot_not_found", func(t *testing.T) { diff --git a/internal/datacoord/session/session.go b/internal/datacoord/session/session.go index 39b3500317..6863da25d6 100644 --- a/internal/datacoord/session/session.go +++ b/internal/datacoord/session/session.go @@ -18,12 +18,12 @@ package session import ( "context" - "fmt" "github.com/cockroachdb/errors" "github.com/milvus-io/milvus/internal/types" "github.com/milvus-io/milvus/pkg/v3/util/lock" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) var errDisposed = errors.New("client is disposed") @@ -84,7 +84,7 @@ func (n *Session) GetOrCreateClient(ctx context.Context) (types.DataNodeClient, } if n.clientCreator == nil { - return nil, fmt.Errorf("unable to create client for %s because of a nil client creator", n.info.Address) + return nil, merr.WrapErrServiceInternalMsg("unable to create client for %s because of a nil client creator", n.info.Address) } err := n.initClient(ctx) diff --git a/internal/datacoord/snapshot.go b/internal/datacoord/snapshot.go index 9c6440d81f..ea8010ab47 100644 --- a/internal/datacoord/snapshot.go +++ b/internal/datacoord/snapshot.go @@ -33,6 +33,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/datapb" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // S3 snapshot storage path constants. @@ -136,7 +137,7 @@ func getManifestSchemaByVersion(version int) (avro.Schema, error) { case 3: return getManifestSchemaV3() default: - return nil, fmt.Errorf("unsupported manifest schema version: %d", version) + return nil, merr.WrapErrServiceInternalMsg("unsupported manifest schema version: %d", version) } } @@ -508,22 +509,22 @@ func GetSegmentManifestPath(manifestDir string, segmentID int64) string { func (w *SnapshotWriter) Save(ctx context.Context, snapshot *SnapshotData) (string, error) { // Validate input parameters if snapshot == nil { - return "", fmt.Errorf("snapshot cannot be nil") + return "", merr.WrapErrServiceInternalMsg("snapshot cannot be nil") } if snapshot.SnapshotInfo == nil { - return "", fmt.Errorf("snapshot info cannot be nil") + return "", merr.WrapErrServiceInternalMsg("snapshot info cannot be nil") } collectionID := snapshot.SnapshotInfo.GetCollectionId() if collectionID <= 0 { - return "", fmt.Errorf("invalid collection ID: %d", collectionID) + return "", merr.WrapErrServiceInternalMsg("invalid collection ID: %d", collectionID) } if snapshot.Collection == nil { - return "", fmt.Errorf("collection description cannot be nil") + return "", merr.WrapErrServiceInternalMsg("collection description cannot be nil") } snapshotID := snapshot.SnapshotInfo.GetId() if snapshotID <= 0 { - return "", fmt.Errorf("invalid snapshot ID: %d", snapshotID) + return "", merr.WrapErrServiceInternalMsg("invalid snapshot ID: %d", snapshotID) } manifestDir, metadataPath := GetSnapshotPaths(w.chunkManager.RootPath(), collectionID, snapshotID) @@ -533,7 +534,7 @@ func (w *SnapshotWriter) Save(ctx context.Context, snapshot *SnapshotData) (stri for _, segment := range snapshot.Segments { manifestPath := GetSegmentManifestPath(manifestDir, segment.GetSegmentId()) if err := w.writeSegmentManifest(ctx, manifestPath, segment); err != nil { - return "", fmt.Errorf("failed to write manifest for segment %d: %w", segment.GetSegmentId(), err) + return "", merr.Wrapf(err, "failed to write manifest for segment %d", segment.GetSegmentId()) } manifestPaths = append(manifestPaths, manifestPath) } @@ -557,7 +558,7 @@ func (w *SnapshotWriter) Save(ctx context.Context, snapshot *SnapshotData) (stri // Step 3: Write metadata file with all manifest paths // This file is the entry point for reading the snapshot if err := w.writeMetadataFile(ctx, metadataPath, snapshot, manifestPaths, storagev2Manifests); err != nil { - return "", fmt.Errorf("failed to write metadata file: %w", err) + return "", merr.Wrap(err, "failed to write metadata file") } log.Info("Successfully wrote metadata file", @@ -584,12 +585,12 @@ func (w *SnapshotWriter) writeSegmentManifest(ctx context.Context, manifestPath // Serialize to Avro binary format (single record per file) avroSchema, err := getManifestSchema() if err != nil { - return fmt.Errorf("failed to get manifest schema: %w", err) + return merr.WrapErrServiceInternalErr(err, "failed to get manifest schema") } binaryData, err := avro.Marshal(avroSchema, entry) if err != nil { - return fmt.Errorf("failed to serialize entry to avro: %w", err) + return merr.WrapErrServiceInternalErr(err, "failed to serialize entry to avro") } // Write to object storage @@ -634,7 +635,7 @@ func (w *SnapshotWriter) writeMetadataFile(ctx context.Context, metadataPath str } jsonData, err := opts.Marshal(metadata) if err != nil { - return fmt.Errorf("failed to marshal metadata to JSON: %w", err) + return merr.WrapErrServiceInternalErr(err, "failed to marshal metadata to JSON") } // Write to object storage @@ -663,13 +664,13 @@ func (w *SnapshotWriter) writeMetadataFile(ctx context.Context, metadataPath str func (w *SnapshotWriter) Drop(ctx context.Context, metadataFilePath string) error { // Validate input parameter if metadataFilePath == "" { - return fmt.Errorf("metadata file path cannot be empty") + return merr.WrapErrServiceInternalMsg("metadata file path cannot be empty") } // Step 1: Read metadata file to discover manifest file paths metadata, err := w.readMetadataFile(ctx, metadataFilePath) if err != nil { - return fmt.Errorf("failed to read metadata file: %w", err) + return merr.Wrap(err, "failed to read metadata file") } snapshotID := metadata.GetSnapshotInfo().GetId() @@ -678,7 +679,7 @@ func (w *SnapshotWriter) Drop(ctx context.Context, metadataFilePath string) erro manifestList := metadata.GetManifestList() if len(manifestList) > 0 { if err := w.chunkManager.MultiRemove(ctx, manifestList); err != nil { - return fmt.Errorf("failed to remove manifest files: %w", err) + return merr.Wrap(err, "failed to remove manifest files") } log.Info("Successfully removed manifest files", zap.Int("count", len(manifestList)), @@ -687,7 +688,7 @@ func (w *SnapshotWriter) Drop(ctx context.Context, metadataFilePath string) erro // Step 3: Remove the metadata file (entry point) if err := w.chunkManager.Remove(ctx, metadataFilePath); err != nil { - return fmt.Errorf("failed to remove metadata file: %w", err) + return merr.Wrap(err, "failed to remove metadata file") } log.Info("Successfully removed metadata file", zap.String("metadataFilePath", metadataFilePath)) @@ -703,7 +704,7 @@ func (w *SnapshotWriter) readMetadataFile(ctx context.Context, filePath string) // Read raw JSON content from object storage data, err := w.chunkManager.Read(ctx, filePath) if err != nil { - return nil, fmt.Errorf("failed to read metadata file: %w", err) + return nil, merr.Wrap(err, "failed to read metadata file") } // Use protojson for deserialization to correctly handle protobuf oneof fields @@ -712,7 +713,7 @@ func (w *SnapshotWriter) readMetadataFile(ctx context.Context, filePath string) DiscardUnknown: true, } if err := opts.Unmarshal(data, metadata); err != nil { - return nil, fmt.Errorf("failed to parse metadata JSON: %w", err) + return nil, merr.WrapErrServiceInternalErr(err, "failed to parse metadata JSON") } return metadata, nil @@ -775,13 +776,13 @@ func NewSnapshotReader(cm storage.ChunkManager) *SnapshotReader { func (r *SnapshotReader) ReadSnapshot(ctx context.Context, metadataFilePath string, includeSegments bool) (*SnapshotData, error) { // Validate input if metadataFilePath == "" { - return nil, fmt.Errorf("metadata file path cannot be empty") + return nil, merr.WrapErrServiceInternalMsg("metadata file path cannot be empty") } // Step 1: Read metadata file (always required) metadata, err := r.readMetadataFile(ctx, metadataFilePath) if err != nil { - return nil, fmt.Errorf("failed to read metadata file: %w", err) + return nil, merr.Wrap(err, "failed to read metadata file") } // Step 2: Optionally read segment details from manifest files @@ -791,7 +792,7 @@ func (r *SnapshotReader) ReadSnapshot(ctx context.Context, metadataFilePath stri for _, manifestPath := range metadata.GetManifestList() { segments, err := r.readManifestFile(ctx, manifestPath, int(metadata.GetFormatVersion())) if err != nil { - return nil, fmt.Errorf("failed to read manifest file %s: %w", manifestPath, err) + return nil, merr.Wrapf(err, "failed to read manifest file %s", manifestPath) } allSegments = append(allSegments, segments...) } @@ -830,7 +831,7 @@ func (r *SnapshotReader) readMetadataFile(ctx context.Context, filePath string) // Read raw JSON content from object storage data, err := r.chunkManager.Read(ctx, filePath) if err != nil { - return nil, fmt.Errorf("failed to read metadata file: %w", err) + return nil, merr.Wrap(err, "failed to read metadata file") } // Use protojson for deserialization to correctly handle protobuf oneof fields @@ -839,12 +840,12 @@ func (r *SnapshotReader) readMetadataFile(ctx context.Context, filePath string) DiscardUnknown: true, } if err := opts.Unmarshal(data, metadata); err != nil { - return nil, fmt.Errorf("failed to parse metadata JSON: %w", err) + return nil, merr.WrapErrServiceInternalErr(err, "failed to parse metadata JSON") } // Validate format version compatibility if err := validateFormatVersion(int(metadata.GetFormatVersion())); err != nil { - return nil, fmt.Errorf("incompatible snapshot format: %w", err) + return nil, merr.WrapErrServiceInternalErr(err, "incompatible snapshot format") } return metadata, nil @@ -865,7 +866,7 @@ func validateFormatVersion(version int) error { // Check if version is too new for current code if version > SnapshotFormatVersion { - return fmt.Errorf("snapshot format version %d is too new, current supported version: %d (please upgrade Milvus)", + return merr.WrapErrServiceInternalMsg("snapshot format version %d is too new, current supported version: %d (please upgrade Milvus)", version, SnapshotFormatVersion) } @@ -888,20 +889,20 @@ func (r *SnapshotReader) readManifestFile(ctx context.Context, filePath string, // Read raw Avro content from object storage data, err := r.chunkManager.Read(ctx, filePath) if err != nil { - return nil, fmt.Errorf("failed to read manifest file: %w", err) + return nil, merr.Wrap(err, "failed to read manifest file") } // Get Avro schema for the specified format version avroSchema, err := getManifestSchemaByVersion(formatVersion) if err != nil { - return nil, fmt.Errorf("failed to get manifest schema for version %d: %w", formatVersion, err) + return nil, merr.WrapErrServiceInternalErr(err, "failed to get manifest schema for version %d", formatVersion) } // Deserialize Avro binary data to single ManifestEntry record var record ManifestEntry err = avro.Unmarshal(avroSchema, data, &record) if err != nil { - return nil, fmt.Errorf("failed to parse avro data: %w", err) + return nil, merr.WrapErrServiceInternalErr(err, "failed to parse avro data") } // Convert ManifestEntry record to protobuf SegmentDescription @@ -974,7 +975,7 @@ func (r *SnapshotReader) readManifestFile(ctx context.Context, filePath string, func (r *SnapshotReader) ListSnapshots(ctx context.Context, collectionID int64) ([]*datapb.SnapshotInfo, error) { // Validate input parameters if collectionID <= 0 { - return nil, fmt.Errorf("invalid collection ID: %d", collectionID) + return nil, merr.WrapErrServiceInternalMsg("invalid collection ID: %d", collectionID) } // Construct metadata directory path @@ -984,7 +985,7 @@ func (r *SnapshotReader) ListSnapshots(ctx context.Context, collectionID int64) // List all files in the metadata directory files, _, err := storage.ListAllChunkWithPrefix(ctx, r.chunkManager, metadataDir, false) if err != nil { - return nil, fmt.Errorf("failed to list metadata files: %w", err) + return nil, merr.Wrap(storage.ToMilvusIoError(metadataDir, err), "failed to list metadata files") } // Read each metadata file and extract SnapshotInfo diff --git a/internal/datacoord/snapshot_manager.go b/internal/datacoord/snapshot_manager.go index 0765e53373..5bb4d1b569 100644 --- a/internal/datacoord/snapshot_manager.go +++ b/internal/datacoord/snapshot_manager.go @@ -562,7 +562,7 @@ func (sm *snapshotManager) validateCMEKCompatibility( // Get target database properties first (needed for both encrypted and non-encrypted snapshots) dbResp, err := sm.broker.DescribeDatabase(ctx, targetDbName) if err != nil { - return fmt.Errorf("failed to describe target database %s: %w", targetDbName, err) + return merr.Wrapf(err, "failed to describe target database %s", targetDbName) } targetIsEncrypted := hookutil.IsDBEncrypted(dbResp.GetProperties()) @@ -640,7 +640,7 @@ func (sm *snapshotManager) RestoreSnapshot( // ======================================================================== phase0Lock, err := startRestoreLock(ctx, sourceCollectionID, snapshotName, targetDbName, targetCollectionName) if err != nil { - return 0, fmt.Errorf("failed to acquire restore lock: %w", err) + return 0, merr.Wrap(err, "failed to acquire restore lock") } // Pin the source snapshot while holding the phase-0 lock. The pin is the @@ -662,7 +662,7 @@ func (sm *snapshotManager) RestoreSnapshot( pinID, activePins, err := sm.snapshotMeta.PinSnapshot(ctx, sourceCollectionID, snapshotName, pinTTLSeconds) if err != nil { phase0Lock.Close() - return 0, fmt.Errorf("failed to pin source snapshot under restore lock: %w", err) + return 0, merr.Wrap(err, "failed to pin source snapshot under restore lock") } setSnapshotActivePinsGauge(sourceCollectionID, snapshotName, activePins) phase0Lock.Close() @@ -691,7 +691,7 @@ func (sm *snapshotManager) RestoreSnapshot( // Phase 1: Read snapshot data (now protected by the refcount guard) snapshotData, err := sm.ReadSnapshotData(ctx, sourceCollectionID, snapshotName) if err != nil { - return 0, fmt.Errorf("failed to read snapshot data: %w", err) + return 0, merr.Wrap(err, "failed to read snapshot data") } log.Info("snapshot data loaded", zap.Int("segmentCount", len(snapshotData.Segments)), @@ -707,7 +707,7 @@ func (sm *snapshotManager) RestoreSnapshot( // Phase 2: Restore collection and partitions collectionID, err := sm.RestoreCollection(ctx, snapshotData, targetCollectionName, targetDbName) if err != nil { - return 0, fmt.Errorf("failed to restore collection: %w", err) + return 0, merr.Wrap(err, "failed to restore collection") } log.Info("collection and partitions restored", zap.Int64("collectionID", collectionID)) @@ -718,7 +718,7 @@ func (sm *snapshotManager) RestoreSnapshot( if rollbackErr := rollback(ctx, targetDbName, targetCollectionName); rollbackErr != nil { log.Error("rollback failed", zap.Error(rollbackErr)) } - return 0, fmt.Errorf("failed to restore indexes: %w", err) + return 0, merr.Wrap(err, "failed to restore indexes") } log.Info("indexes restored", zap.Int("indexCount", len(snapshotData.Indexes))) @@ -730,7 +730,7 @@ func (sm *snapshotManager) RestoreSnapshot( if rollbackErr := rollback(ctx, targetDbName, targetCollectionName); rollbackErr != nil { log.Error("rollback failed", zap.Error(rollbackErr)) } - return 0, fmt.Errorf("failed to allocate job ID: %w", err) + return 0, merr.Wrap(err, "failed to allocate job ID") } log.Info("pre-allocated job ID for restore", zap.Int64("jobID", jobID)) @@ -741,7 +741,7 @@ func (sm *snapshotManager) RestoreSnapshot( if rollbackErr := rollback(ctx, targetDbName, targetCollectionName); rollbackErr != nil { log.Error("rollback failed", zap.Error(rollbackErr)) } - return 0, fmt.Errorf("failed to start broadcaster for restore message: %w", err) + return 0, merr.Wrap(err, "failed to start broadcaster for restore message") } defer func() { if restoreBroadcaster != nil { @@ -760,7 +760,7 @@ func (sm *snapshotManager) RestoreSnapshot( if rollbackErr := rollback(ctx, targetDbName, targetCollectionName); rollbackErr != nil { log.Error("rollback failed", zap.Error(rollbackErr)) } - err = fmt.Errorf("resource validation failed: %w", valErr) + err = merr.Wrap(valErr, "resource validation failed") return 0, err } @@ -785,7 +785,7 @@ func (sm *snapshotManager) RestoreSnapshot( if rollbackErr := rollback(ctx, targetDbName, targetCollectionName); rollbackErr != nil { log.Error("rollback failed", zap.Error(rollbackErr)) } - err = fmt.Errorf("failed to broadcast restore message: %w", bcErr) + err = merr.Wrap(bcErr, "failed to broadcast restore message") return 0, err } @@ -872,14 +872,14 @@ func (sm *snapshotManager) RestoreIndexes( // Get collection info for dbId coll, err := sm.broker.DescribeCollectionInternal(ctx, collectionID) if err != nil { - return fmt.Errorf("failed to describe collection %d: %w", collectionID, err) + return merr.Wrapf(err, "failed to describe collection %d", collectionID) } for _, indexInfo := range snapshotData.Indexes { // Allocate new index ID indexID, err := sm.allocator.AllocID(ctx) if err != nil { - return fmt.Errorf("failed to allocate index ID: %w", err) + return merr.Wrap(err, "failed to allocate index ID") } // Build index model from snapshot data @@ -898,19 +898,19 @@ func (sm *snapshotManager) RestoreIndexes( // Validate the index params (basic validation without JSON path parsing) if err := ValidateIndexParams(index); err != nil { - return fmt.Errorf("failed to validate index %s: %w", indexInfo.GetIndexName(), err) + return merr.Wrapf(err, "failed to validate index %s", indexInfo.GetIndexName()) } // Check scalar index engine version for JSON path indexes with new types if err := sm.checkJSONPathIndexVersion(index); err != nil { - return fmt.Errorf("failed to validate index %s: %w", indexInfo.GetIndexName(), err) + return merr.Wrapf(err, "failed to validate index %s", indexInfo.GetIndexName()) } // Create a new broadcaster for each index // (each broadcaster can only be used once due to resource key lock consumption) b, err := startBroadcaster(ctx, collectionID, snapshotName) if err != nil { - return fmt.Errorf("failed to start broadcaster for index %s: %w", indexInfo.GetIndexName(), err) + return merr.Wrapf(err, "failed to start broadcaster for index %s", indexInfo.GetIndexName()) } // Broadcast CreateIndex message directly to DDL WAL @@ -930,7 +930,7 @@ func (sm *snapshotManager) RestoreIndexes( ) b.Close() if err != nil { - return fmt.Errorf("failed to broadcast create index %s: %w", indexInfo.GetIndexName(), err) + return merr.Wrapf(err, "failed to broadcast create index %s", indexInfo.GetIndexName()) } log.Ctx(ctx).Info("index restored via DDL WAL broadcast", @@ -977,14 +977,14 @@ func (sm *snapshotManager) RestoreData( snapshotData, err := sm.ReadSnapshotData(ctx, sourceCollectionID, snapshotName) if err != nil { log.Error("failed to read snapshot data", zap.Error(err)) - return 0, fmt.Errorf("failed to read snapshot data: %w", err) + return 0, merr.Wrap(err, "failed to read snapshot data") } // ========== Phase 2: Build partition mapping ========== partitionMapping, err := sm.buildPartitionMapping(ctx, snapshotData, collectionID) if err != nil { log.Error("failed to build partition mapping", zap.Error(err)) - return 0, fmt.Errorf("partition mapping failed: %w", err) + return 0, merr.Wrap(err, "partition mapping failed") } log.Info("partition mapping built", zap.Any("partitionMapping", partitionMapping)) @@ -992,14 +992,14 @@ func (sm *snapshotManager) RestoreData( channelMapping, err := sm.buildChannelMapping(ctx, snapshotData, collectionID) if err != nil { log.Error("failed to build channel mapping", zap.Error(err)) - return 0, fmt.Errorf("channel mapping failed: %w", err) + return 0, merr.Wrap(err, "channel mapping failed") } // ========== Phase 4: Create copy segment job ========== // Use the pre-allocated jobID from the WAL message if err := sm.createRestoreJob(ctx, collectionID, channelMapping, partitionMapping, snapshotData, jobID, pinID); err != nil { log.Error("failed to create restore job", zap.Error(err)) - return 0, fmt.Errorf("restore job creation failed: %w", err) + return 0, merr.Wrap(err, "restore job creation failed") } log.Info("restore data completed successfully", @@ -1047,7 +1047,7 @@ func (sm *snapshotManager) restoreUserPartitions( } if err := sm.broker.CreatePartition(ctx, req); err != nil { - return fmt.Errorf("failed to create partition %s: %w", partitionName, err) + return merr.Wrapf(err, "failed to create partition %s", partitionName) } } @@ -1330,7 +1330,7 @@ func (sm *snapshotManager) GetRestoreState(ctx context.Context, jobID int64) (*d // Get job job := sm.copySegmentMeta.GetJob(ctx, jobID) if job == nil { - err := merr.WrapErrImportFailed(fmt.Sprintf("restore job not found: jobID=%d", jobID)) + err := merr.WrapErrImportSysFailedMsg("restore job not found: jobID=%d", jobID) log.Warn("restore job not found") return nil, err } diff --git a/internal/datacoord/snapshot_meta.go b/internal/datacoord/snapshot_meta.go index 92bcba1d9c..e62339572d 100644 --- a/internal/datacoord/snapshot_meta.go +++ b/internal/datacoord/snapshot_meta.go @@ -572,7 +572,7 @@ func (sm *snapshotMeta) SaveSnapshot(ctx context.Context, snapshot *SnapshotData if err := sm.catalog.SaveSnapshot(ctx, snapshot.SnapshotInfo); err != nil { log.Error("failed to save pending snapshot to catalog", zap.Error(err)) - return fmt.Errorf("failed to save pending snapshot to catalog: %w", err) + return merr.Wrap(err, "failed to save pending snapshot to catalog") } log.Info("saved pending snapshot to catalog") @@ -582,7 +582,7 @@ func (sm *snapshotMeta) SaveSnapshot(ctx context.Context, snapshot *SnapshotData // S3 write failed, leave PENDING record for GC to clean up log.Error("failed to save snapshot to S3, pending record left for GC cleanup", zap.Error(err)) - return fmt.Errorf("failed to save snapshot to S3: %w", err) + return merr.Wrap(err, "failed to save snapshot to S3") } snapshot.SnapshotInfo.S3Location = metadataFilePath log.Info("saved snapshot data to S3", zap.String("s3Location", metadataFilePath)) @@ -598,7 +598,7 @@ func (sm *snapshotMeta) SaveSnapshot(ctx context.Context, snapshot *SnapshotData // GC will eventually cleanup via the PENDING marker. log.Error("failed to update snapshot to committed state, pending record left for GC cleanup", zap.Error(err)) - return fmt.Errorf("failed to update snapshot to committed state: %w", err) + return merr.Wrap(err, "failed to update snapshot to committed state") } // Catalog committed — safe to insert into in-memory cache and register protection. @@ -809,8 +809,7 @@ func (sm *snapshotMeta) DropSnapshotsByCollection(ctx context.Context, collectio } if len(errs) > 0 { - return dropped, fmt.Errorf("failed to drop %d/%d snapshots for collection %d: %w", - len(errs), len(snapshotNames), collectionID, merr.Combine(errs...)) + return dropped, merr.WrapErrServiceInternalErr(merr.Combine(errs...), "failed to drop %d/%d snapshots for collection %d", len(errs), len(snapshotNames), collectionID) } return dropped, nil @@ -1006,7 +1005,7 @@ func (sm *snapshotMeta) GetPendingSnapshots(ctx context.Context, pendingTimeout // Get all snapshots from catalog (etcd) snapshots, err := sm.catalog.ListSnapshots(ctx) if err != nil { - return nil, fmt.Errorf("failed to list snapshots from catalog: %w", err) + return nil, merr.Wrap(err, "failed to list snapshots from catalog") } now := time.Now().UnixMilli() @@ -1057,7 +1056,7 @@ func (sm *snapshotMeta) CleanupPendingSnapshot(ctx context.Context, snapshot *da func (sm *snapshotMeta) GetDeletingSnapshots(ctx context.Context) ([]*datapb.SnapshotInfo, error) { snapshots, err := sm.catalog.ListSnapshots(ctx) if err != nil { - return nil, fmt.Errorf("failed to list snapshots from catalog: %w", err) + return nil, merr.Wrap(err, "failed to list snapshots from catalog") } deletingSnapshots := make([]*datapb.SnapshotInfo, 0) @@ -1774,7 +1773,7 @@ func (sm *snapshotMeta) generatePinID(snapshot *datapb.SnapshotInfo) (int64, err for i := 0; i < 3; i++ { var b [8]byte if _, err := cryptorand.Read(b[:]); err != nil { - return 0, fmt.Errorf("failed to generate random pin ID: %w", err) + return 0, merr.WrapErrServiceInternalErr(err, "failed to generate random pin ID") } id := int64(binary.BigEndian.Uint64(b[:]) >> 1) if id == 0 { @@ -1784,5 +1783,5 @@ func (sm *snapshotMeta) generatePinID(snapshot *datapb.SnapshotInfo) (int64, err return id, nil } } - return 0, fmt.Errorf("failed to generate unique pin ID after 3 attempts") + return 0, merr.WrapErrServiceInternalMsg("failed to generate unique pin ID after 3 attempts") } diff --git a/internal/datacoord/stats_task_meta.go b/internal/datacoord/stats_task_meta.go index 6293fb0c87..147d0ff2e0 100644 --- a/internal/datacoord/stats_task_meta.go +++ b/internal/datacoord/stats_task_meta.go @@ -182,7 +182,7 @@ func (stm *statsTaskMeta) UpdateVersion(taskID, nodeID int64) error { t, ok := stm.tasks.Get(taskID) if !ok { - return fmt.Errorf("task %d not found", taskID) + return merr.WrapErrServiceInternalMsg("task %d not found", taskID) } cloneT := proto.Clone(t).(*indexpb.StatsTask) @@ -212,7 +212,7 @@ func (stm *statsTaskMeta) UpdateTaskState(taskID int64, state indexpb.JobState, t, ok := stm.tasks.Get(taskID) if !ok { - return fmt.Errorf("task %d not found", taskID) + return merr.WrapErrServiceInternalMsg("task %d not found", taskID) } cloneT := proto.Clone(t).(*indexpb.StatsTask) @@ -239,7 +239,7 @@ func (stm *statsTaskMeta) UpdateBuildingTask(taskID int64) error { t, ok := stm.tasks.Get(taskID) if !ok { - return fmt.Errorf("task %d not found", taskID) + return merr.WrapErrServiceInternalMsg("task %d not found", taskID) } cloneT := proto.Clone(t).(*indexpb.StatsTask) @@ -267,7 +267,7 @@ func (stm *statsTaskMeta) FinishTask(taskID int64, result *workerpb.StatsResult) t, ok := stm.tasks.Get(taskID) if !ok { - return fmt.Errorf("task %d not found", taskID) + return merr.WrapErrServiceInternalMsg("task %d not found", taskID) } cloneT := proto.Clone(t).(*indexpb.StatsTask) @@ -358,7 +358,7 @@ func (stm *statsTaskMeta) MarkTaskCanRecycle(taskID int64) error { t, ok := stm.tasks.Get(taskID) if !ok { - return fmt.Errorf("task %d not found", taskID) + return merr.WrapErrServiceInternalMsg("task %d not found", taskID) } cloneT := proto.Clone(t).(*indexpb.StatsTask) diff --git a/internal/datacoord/task_index.go b/internal/datacoord/task_index.go index 8ac8b6e2a9..50ae449a0e 100644 --- a/internal/datacoord/task_index.go +++ b/internal/datacoord/task_index.go @@ -18,7 +18,6 @@ package datacoord import ( "context" - "fmt" "path" "time" @@ -241,7 +240,7 @@ func (it *indexBuildTask) prepareJobRequest(ctx context.Context, segment *Segmen var err error params, err = Params.KnowhereConfig.UpdateIndexParams(GetIndexType(params), paramtable.BuildStage, params) if err != nil { - return nil, fmt.Errorf("failed to update index build params: %w", err) + return nil, merr.WrapErrServiceInternalErr(err, "failed to update index build params") } } @@ -249,14 +248,14 @@ func (it *indexBuildTask) prepareJobRequest(ctx context.Context, segment *Segmen var err error params, err = indexparams.UpdateDiskIndexBuildParams(Params, params) if err != nil { - return nil, fmt.Errorf("failed to append index build params: %w", err) + return nil, merr.WrapErrServiceInternalErr(err, "failed to append index build params") } } // Get collection info and field collectionInfo, err := it.handler.GetCollection(ctx, segment.GetCollectionID()) if err != nil { - return nil, fmt.Errorf("failed to get collection info: %w", err) + return nil, merr.Wrap(err, "failed to get collection info") } schema := collectionInfo.Schema @@ -271,7 +270,7 @@ func (it *indexBuildTask) prepareJobRequest(ctx context.Context, segment *Segmen } if field == nil { - return nil, fmt.Errorf("field not found with ID %d", fieldID) + return nil, merr.WrapErrFieldNotFound(fieldID) } // Extract dim only for vector types to avoid unnecessary warnings diff --git a/internal/datacoord/task_refresh_external_collection.go b/internal/datacoord/task_refresh_external_collection.go index 208f9a78cc..67e5ccc164 100644 --- a/internal/datacoord/task_refresh_external_collection.go +++ b/internal/datacoord/task_refresh_external_collection.go @@ -34,6 +34,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/datapb" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" "github.com/milvus-io/milvus/pkg/v3/taskcommon" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -115,7 +116,7 @@ func (t *refreshExternalCollectionTask) validateSource() error { // Validate against job-level snapshot to isolate in-flight tasks from schema changes. job := t.refreshMeta.GetJob(t.GetJobId()) if job == nil { - return fmt.Errorf("job %d not found", t.GetJobId()) + return merr.WrapErrServiceInternalMsg("job %d not found", t.GetJobId()) } currentSource := job.GetExternalSource() @@ -125,7 +126,7 @@ func (t *refreshExternalCollectionTask) validateSource() error { taskSpec := t.GetExternalSpec() if currentSource != taskSource || currentSpec != taskSpec { - return fmt.Errorf( + return merr.WrapErrServiceInternalMsg( "task source mismatch: task source=%s/%s, job source=%s/%s (task belongs to a different refresh job)", taskSource, taskSpec, currentSource, currentSpec, ) @@ -215,7 +216,7 @@ func applyExternalCollectionSegmentUpdate( logFields ...zap.Field, ) error { if mt == nil { - return fmt.Errorf("meta is nil, cannot update segments") + return merr.WrapErrServiceInternalMsg("meta is nil, cannot update segments") } fields := append(logFields, zap.Int64("collectionID", collectionID)) log := log.Ctx(ctx).With(fields...) @@ -228,14 +229,14 @@ func applyExternalCollectionSegmentUpdate( for _, segID := range keptSegmentIDs { segment := mt.segments.GetSegment(segID) if segment == nil { - return fmt.Errorf("kept segment %d not found", segID) + return merr.WrapErrServiceInternalMsg("kept segment %d not found", segID) } if segment.GetCollectionID() != collectionID { - return fmt.Errorf("collection mismatch for kept segment %d: existing %d, want %d", + return merr.WrapErrServiceInternalMsg("collection mismatch for kept segment %d: existing %d, want %d", segID, segment.GetCollectionID(), collectionID) } if segment.GetState() == commonpb.SegmentState_Dropped { - return fmt.Errorf("cannot keep dropped segment %d", segID) + return merr.WrapErrServiceInternalMsg("cannot keep dropped segment %d", segID) } keptSegmentMap[segID] = true } @@ -250,10 +251,10 @@ func applyExternalCollectionSegmentUpdate( return err } if keptSegmentMap[seg.GetID()] { - return fmt.Errorf("segment %d cannot be both kept and updated", seg.GetID()) + return merr.WrapErrServiceInternalMsg("segment %d cannot be both kept and updated", seg.GetID()) } if _, ok := upsertSegmentMap[seg.GetID()]; ok { - return fmt.Errorf("duplicate updated segment %d", seg.GetID()) + return merr.WrapErrServiceInternalMsg("duplicate updated segment %d", seg.GetID()) } upsertSegmentMap[seg.GetID()] = seg validUpdatedSegments = append(validUpdatedSegments, seg) @@ -308,7 +309,7 @@ func applyExternalCollectionSegmentUpdate( zap.Int("activeSegmentCount", activeSegmentCount), zap.Int("keptSegments", len(keptSegmentMap)), zap.Int("updatedSegments", len(upsertSegmentMap))) - return fmt.Errorf("safety check failed: refusing to drop all %d segments without replacement (keptSegments=%d, updatedSegments=%d)", + return merr.WrapErrServiceInternalMsg("safety check failed: refusing to drop all %d segments without replacement (keptSegments=%d, updatedSegments=%d)", activeSegmentCount, len(keptSegmentMap), len(upsertSegmentMap)) } @@ -330,15 +331,15 @@ func applyExternalCollectionSegmentUpdate( collInfo := mt.GetCollection(collectionID) if collInfo == nil { - return fmt.Errorf("collection %d not found in meta", collectionID) + return merr.WrapErrServiceInternalMsg("collection %d not found in meta", collectionID) } // External collections are single-shard, single-partition (enforced at creation). // Assert exactly-one here to catch any invariant violation from data corruption or legacy data. if len(collInfo.VChannelNames) != 1 { - return fmt.Errorf("external collection %d expected exactly 1 VChannel, got %d", collectionID, len(collInfo.VChannelNames)) + return merr.WrapErrServiceInternalMsg("external collection %d expected exactly 1 VChannel, got %d", collectionID, len(collInfo.VChannelNames)) } if len(collInfo.Partitions) != 1 { - return fmt.Errorf("external collection %d expected exactly 1 partition, got %d", collectionID, len(collInfo.Partitions)) + return merr.WrapErrServiceInternalMsg("external collection %d expected exactly 1 partition, got %d", collectionID, len(collInfo.Partitions)) } insertChannel := collInfo.VChannelNames[0] partitionID := collInfo.Partitions[0] @@ -477,14 +478,14 @@ func applyExternalCollectionSegmentUpdate( func validateExternalRefreshUpdatedSegment(incoming *datapb.SegmentInfo, collectionID int64) error { if incoming.GetCollectionID() != 0 && incoming.GetCollectionID() != collectionID { - return fmt.Errorf("collection mismatch for segment %d: got %d, want %d", + return merr.WrapErrServiceInternalMsg("collection mismatch for segment %d: got %d, want %d", incoming.GetID(), incoming.GetCollectionID(), collectionID) } if incoming.GetManifestPath() == "" { - return fmt.Errorf("updated segment %d has empty manifest path", incoming.GetID()) + return merr.WrapErrServiceInternalMsg("updated segment %d has empty manifest path", incoming.GetID()) } if len(incoming.GetBinlogs()) == 0 { - return fmt.Errorf("updated segment %d has empty fake binlogs", incoming.GetID()) + return merr.WrapErrServiceInternalMsg("updated segment %d has empty fake binlogs", incoming.GetID()) } return nil } @@ -513,36 +514,36 @@ func validateExternalRefreshNewSegment(incoming *datapb.SegmentInfo) error { func validateExternalRefreshPatch(oldSeg *SegmentInfo, incoming *datapb.SegmentInfo, collectionID int64) error { if oldSeg == nil { - return fmt.Errorf("existing segment is nil") + return merr.WrapErrServiceInternalMsg("existing segment is nil") } if oldSeg.GetCollectionID() != collectionID { - return fmt.Errorf("collection mismatch for segment %d: existing %d, want %d", + return merr.WrapErrServiceInternalMsg("collection mismatch for segment %d: existing %d, want %d", oldSeg.GetID(), oldSeg.GetCollectionID(), collectionID) } if oldSeg.GetState() == commonpb.SegmentState_Dropped { - return fmt.Errorf("cannot patch dropped segment %d", oldSeg.GetID()) + return merr.WrapErrServiceInternalMsg("cannot patch dropped segment %d", oldSeg.GetID()) } if incoming.GetCollectionID() != 0 && incoming.GetCollectionID() != collectionID { - return fmt.Errorf("collection mismatch for segment %d: got %d, want %d", + return merr.WrapErrServiceInternalMsg("collection mismatch for segment %d: got %d, want %d", incoming.GetID(), incoming.GetCollectionID(), collectionID) } if incoming.GetNumOfRows() != oldSeg.GetNumOfRows() { - return fmt.Errorf("row count changed for segment %d: got %d, want %d", + return merr.WrapErrServiceInternalMsg("row count changed for segment %d: got %d, want %d", incoming.GetID(), incoming.GetNumOfRows(), oldSeg.GetNumOfRows()) } if incoming.GetStorageVersion() != 0 && incoming.GetStorageVersion() != oldSeg.GetStorageVersion() { - return fmt.Errorf("storage version changed for segment %d: got %d, want %d", + return merr.WrapErrServiceInternalMsg("storage version changed for segment %d: got %d, want %d", incoming.GetID(), incoming.GetStorageVersion(), oldSeg.GetStorageVersion()) } if incoming.GetSchemaVersion() < oldSeg.GetSchemaVersion() { - return fmt.Errorf("schema version rollback for segment %d: got %d, want >= %d", + return merr.WrapErrServiceInternalMsg("schema version rollback for segment %d: got %d, want >= %d", incoming.GetID(), incoming.GetSchemaVersion(), oldSeg.GetSchemaVersion()) } if incoming.GetManifestPath() == "" { - return fmt.Errorf("patched segment %d has empty manifest path", incoming.GetID()) + return merr.WrapErrServiceInternalMsg("patched segment %d has empty manifest path", incoming.GetID()) } if len(incoming.GetBinlogs()) == 0 { - return fmt.Errorf("patched segment %d has empty fake binlogs", incoming.GetID()) + return merr.WrapErrServiceInternalMsg("patched segment %d has empty fake binlogs", incoming.GetID()) } if err := validateExternalRefreshBinlogRowCount(incoming, oldSeg.GetNumOfRows()); err != nil { return err @@ -553,14 +554,14 @@ func validateExternalRefreshPatch(oldSeg *SegmentInfo, incoming *datapb.SegmentI func validateExternalRefreshBinlogRowCount(segment *datapb.SegmentInfo, expectedRows int64) error { binlogRows := segmentutil.CalcRowCountFromBinLog(segment) if binlogRows == -1 { - return fmt.Errorf("invalid binlog row count for segment %d", segment.GetID()) + return merr.WrapErrServiceInternalMsg("invalid binlog row count for segment %d", segment.GetID()) } if expectedRows > 0 && binlogRows != expectedRows { - return fmt.Errorf("binlog row count mismatch for segment %d: got %d, want %d", + return merr.WrapErrServiceInternalMsg("binlog row count mismatch for segment %d: got %d, want %d", segment.GetID(), binlogRows, expectedRows) } if binlogRows > 0 && binlogRows != segment.GetNumOfRows() { - return fmt.Errorf("binlog row count mismatch for segment %d: got %d, segment rows %d", + return merr.WrapErrServiceInternalMsg("binlog row count mismatch for segment %d: got %d, segment rows %d", segment.GetID(), binlogRows, segment.GetNumOfRows()) } return nil @@ -612,7 +613,7 @@ func (t *refreshExternalCollectionTask) CreateTaskOnWorker(nodeID int64, cluster log.Info("creating refresh task on worker") if t.mt == nil { - err = fmt.Errorf("meta is nil, cannot create task on worker") + err = merr.WrapErrServiceInternalMsg("meta is nil, cannot create task on worker") return } @@ -625,7 +626,7 @@ func (t *refreshExternalCollectionTask) CreateTaskOnWorker(nodeID int64, cluster // Re-read task from meta to sync in-memory state (nodeID and version) updatedTask := t.refreshMeta.GetTask(t.GetTaskId()) if updatedTask == nil { - err = fmt.Errorf("task %d not found after version update", t.GetTaskId()) + err = merr.WrapErrServiceInternalMsg("task %d not found after version update", t.GetTaskId()) return } t.ExternalCollectionRefreshTask = updatedTask @@ -668,11 +669,11 @@ func (t *refreshExternalCollectionTask) CreateTaskOnWorker(nodeID int64, cluster // lock, before they are supported. collInfo := t.mt.GetCollection(t.GetCollectionId()) if collInfo == nil { - err = fmt.Errorf("collection %d not found in meta", t.GetCollectionId()) + err = merr.WrapErrServiceInternalMsg("collection %d not found in meta", t.GetCollectionId()) return } if len(collInfo.Partitions) != 1 { - err = fmt.Errorf("external collection %d expected exactly 1 partition, got %d", t.GetCollectionId(), len(collInfo.Partitions)) + err = merr.WrapErrServiceInternalMsg("external collection %d expected exactly 1 partition, got %d", t.GetCollectionId(), len(collInfo.Partitions)) return } partitionID := collInfo.Partitions[0] diff --git a/internal/datacoord/task_stats.go b/internal/datacoord/task_stats.go index 38237863aa..7fd7d19be0 100644 --- a/internal/datacoord/task_stats.go +++ b/internal/datacoord/task_stats.go @@ -18,7 +18,6 @@ package datacoord import ( "context" - "fmt" "time" "github.com/cockroachdb/errors" @@ -299,11 +298,17 @@ func (st *statsTask) handleEmptySegment(ctx context.Context) error { // Prepare the stats request func (st *statsTask) prepareJobRequest(ctx context.Context, segment *SegmentInfo) (*workerpb.CreateStatsRequest, error) { collInfo, err := st.handler.GetCollection(ctx, segment.GetCollectionID()) - if err != nil || collInfo == nil { - return nil, fmt.Errorf("failed to get collection info: %w", err) + if err != nil { + return nil, merr.Wrap(err, "failed to get collection info") + } + // GetCollection can return (nil, nil) on a cache miss; merr.Wrap(nil) would + // be nil and silently submit a malformed request, so guard collInfo + // separately with a typed not-found. + if collInfo == nil { + return nil, merr.WrapErrCollectionNotFound(segment.GetCollectionID()) } if collInfo.Schema == nil || len(collInfo.Schema.GetFields()) == 0 { - return nil, fmt.Errorf("collection schema is nil or has no fields, collectionID: %d", segment.GetCollectionID()) + return nil, merr.WrapErrServiceInternalMsg("collection schema is nil or has no fields, collectionID: %d", segment.GetCollectionID()) } // Calculate binlog allocation @@ -314,7 +319,7 @@ func (st *statsTask) prepareJobRequest(ctx context.Context, segment *SegmentInfo // Allocate IDs start, end, err := st.allocator.AllocN(binlogNum + int64(len(collInfo.Schema.GetFunctions())) + 1) if err != nil { - return nil, fmt.Errorf("failed to allocate log IDs: %w", err) + return nil, merr.Wrap(err, "failed to allocate log IDs") } // Create the request diff --git a/internal/datanode/compactor/bump_schema_version_compactor.go b/internal/datanode/compactor/bump_schema_version_compactor.go index 68eb6d5101..fb327e441f 100644 --- a/internal/datanode/compactor/bump_schema_version_compactor.go +++ b/internal/datanode/compactor/bump_schema_version_compactor.go @@ -26,7 +26,6 @@ import ( "github.com/apache/arrow/go/v17/arrow" "github.com/apache/arrow/go/v17/arrow/array" - "github.com/cockroachdb/errors" "go.opentelemetry.io/otel" "go.uber.org/zap" @@ -113,14 +112,16 @@ func (t *bumpSchemaVersionCompactionTask) preCompact() error { return t.ctx.Err() } - // Check segment binlogs: must have exactly one segment + // Check segment binlogs: must have exactly one segment. + // The plan is produced by datacoord, so a malformed plan is an internal + // protocol violation, not user input. if len(t.plan.GetSegmentBinlogs()) != 1 { - return errors.Newf("schema bump compaction plan is illegal, must have exactly one segment, but got %d segments, planID = %d", len(t.plan.GetSegmentBinlogs()), t.GetPlanID()) + return merr.WrapErrServiceInternalMsg("schema bump compaction plan is illegal, must have exactly one segment, but got %d segments, planID = %d", len(t.plan.GetSegmentBinlogs()), t.GetPlanID()) } segment := t.plan.GetSegmentBinlogs()[0] if segment.GetStorageVersion() < storage.StorageV3 || segment.GetManifest() == "" { - return errors.Newf("schema bump compaction requires a StorageV3 segment with manifest, planID = %d, segmentID = %d, storageVersion = %d", t.GetPlanID(), segment.GetSegmentID(), segment.GetStorageVersion()) + return merr.WrapErrServiceInternalMsg("schema bump compaction requires a StorageV3 segment with manifest, planID = %d, segmentID = %d, storageVersion = %d", t.GetPlanID(), segment.GetSegmentID(), segment.GetStorageVersion()) } return nil @@ -147,7 +148,7 @@ func (t *bumpSchemaVersionCompactionTask) missingFunctionInputSchema(missingFunc for _, inputFieldID := range functionSchema.GetInputFieldIds() { inputField := typeutil.GetField(schema, inputFieldID) if inputField == nil { - return nil, nil, errors.New("input field not found in schema") + return nil, nil, merr.WrapErrParameterInvalidMsg("input field not found in schema") } if err := validateMaterializationInputField(functionSchema, inputField); err != nil { return nil, nil, err @@ -196,11 +197,11 @@ func bm25AdditionalInputFields(schema *schemapb.CollectionSchema, inputField *sc return nil, err } if multiAnalyzerParams.ByField == "" { - return nil, errors.New("multi_analyzer_params missing required 'by_field' key") + return nil, merr.WrapErrParameterInvalidMsg("multi_analyzer_params missing required 'by_field' key") } byField := typeutil.GetFieldByName(schema, multiAnalyzerParams.ByField) if byField == nil { - return nil, errors.New("input field not found in schema") + return nil, merr.WrapErrParameterInvalidMsg("input field not found in schema") } return []*schemapb.FieldSchema{byField}, nil } @@ -217,7 +218,7 @@ func (t *bumpSchemaVersionCompactionTask) missingFunctionOutputFields(missingFun outputFieldID := functionSchema.GetOutputFieldIds()[outputIndex] outputField := typeutil.GetField(schema, outputFieldID) if outputField == nil { - return nil, nil, errors.New("output field not found in schema") + return nil, nil, merr.WrapErrParameterInvalidMsg("output field not found in schema") } if err := validateMaterializationOutputField(functionSchema, outputField); err != nil { return nil, nil, err @@ -227,7 +228,7 @@ func (t *bumpSchemaVersionCompactionTask) missingFunctionOutputFields(missingFun } } if len(fields) == 0 { - return nil, nil, errors.New("no missing function output fields") + return nil, nil, merr.WrapErrParameterInvalidMsg("no missing function output fields") } return fields, fieldIDs, nil } @@ -236,22 +237,22 @@ func validateSupportedMissingFunctionMaterialization(functionSchema *schemapb.Fu switch functionSchema.GetType() { case schemapb.FunctionType_BM25: if len(functionSchema.GetInputFieldIds()) == 0 { - return errors.New("bm25 function should have input fields") + return merr.WrapErrParameterInvalidMsg("bm25 function should have input fields") } if len(functionSchema.GetOutputFieldIds()) == 0 { - return errors.New("bm25 function should have output fields") + return merr.WrapErrParameterInvalidMsg("bm25 function should have output fields") } return nil case schemapb.FunctionType_MinHash: if len(functionSchema.GetInputFieldIds()) == 0 { - return errors.New("minhash function should have input fields") + return merr.WrapErrParameterInvalidMsg("minhash function should have input fields") } if len(functionSchema.GetOutputFieldIds()) == 0 { - return errors.New("minhash function should have output fields") + return merr.WrapErrParameterInvalidMsg("minhash function should have output fields") } return nil default: - return errors.New("unsupported function type") + return merr.WrapErrParameterInvalidMsg("unsupported function type") } } @@ -259,11 +260,11 @@ func validateMaterializationInputField(functionSchema *schemapb.FunctionSchema, switch functionSchema.GetType() { case schemapb.FunctionType_BM25: if field.GetDataType() != schemapb.DataType_VarChar && field.GetDataType() != schemapb.DataType_Text { - return errors.New("input field data type must be varchar or text for bm25 materialization") + return merr.WrapErrParameterInvalidMsg("input field data type must be varchar or text for bm25 materialization") } case schemapb.FunctionType_MinHash: if field.GetDataType() != schemapb.DataType_VarChar && field.GetDataType() != schemapb.DataType_Text { - return errors.New("input field data type must be varchar or text for minhash materialization") + return merr.WrapErrParameterInvalidMsg("input field data type must be varchar or text for minhash materialization") } } return nil @@ -273,11 +274,11 @@ func validateMaterializationOutputField(functionSchema *schemapb.FunctionSchema, switch functionSchema.GetType() { case schemapb.FunctionType_BM25: if field.GetDataType() != schemapb.DataType_SparseFloatVector { - return errors.New("output field data type must be sparse float vector for bm25 materialization") + return merr.WrapErrParameterInvalidMsg("output field data type must be sparse float vector for bm25 materialization") } case schemapb.FunctionType_MinHash: if field.GetDataType() != schemapb.DataType_BinaryVector { - return errors.New("output field data type must be binary vector for minhash materialization") + return merr.WrapErrParameterInvalidMsg("output field data type must be binary vector for minhash materialization") } } return nil @@ -546,7 +547,7 @@ func (t *bumpSchemaVersionCompactionTask) runFullSchemaRewrite(existingFields ma } manifestPath, err = packed.AddStatsToManifest(manifestPath, t.compactionParams.StorageConfig, statEntries) if err != nil { - return nil, merr.WrapErrServiceInternal("failed to add writer stats to schema bump full rewrite manifest", err.Error()) + return nil, merr.Wrap(err, "failed to add writer stats to schema bump full rewrite manifest") } } sortedInsertLogs := storage.SortFieldBinlogs(insertLogs) @@ -602,7 +603,7 @@ func (t *bumpSchemaVersionCompactionTask) runFullSchemaRewrite(existingFields ma func appendBM25StatsFromArrowArray(stats *storage.BM25Stats, arr arrow.Array) (int, error) { binaryArray, ok := arr.(*array.Binary) if !ok { - return 0, errors.Newf("bm25 output field must be arrow binary array, got %T", arr) + return 0, merr.WrapErrParameterInvalidMsg("bm25 output field must be arrow binary array, got %T", arr) } memorySize := 0 for i := 0; i < binaryArray.Len(); i++ { @@ -667,7 +668,7 @@ func (t *bumpSchemaVersionCompactionTask) setupWriter(outputFields []*schemapb.F basePath, existingVersion, err := packed.UnmarshalManifestPath(segment.GetManifest()) if err != nil { - return nil, merr.WrapErrServiceInternal("failed to parse existing manifest for schema bump", err.Error()) + return nil, merr.WrapErrDataIntegrityMsg("failed to parse existing manifest for schema bump: %s", err.Error()) } writerResult, err := t.newV3WriterResult(outputSchema, newColumnGroups, segment, collectionID, basePath, existingVersion) if err != nil { @@ -685,7 +686,7 @@ func (t *bumpSchemaVersionCompactionTask) setupWriter(outputFields []*schemapb.F func (t *bumpSchemaVersionCompactionTask) newV3WriterResult(schema *schemapb.CollectionSchema, columnGroups []storagecommon.ColumnGroup, segment *datapb.CompactionSegmentBinlogs, collectionID int64, basePath string, baseVersion int64) (*bumpSchemaVersionWriterResult, error) { if segment.GetStorageVersion() < storage.StorageV3 || segment.GetManifest() == "" { - return nil, errors.New("schema bump compaction requires a StorageV3 segment with manifest") + return nil, merr.WrapErrParameterInvalidMsg("schema bump compaction requires a StorageV3 segment with manifest") } pluginContext, err := hookutil.GetCPluginContext(t.plan.GetPluginContext(), collectionID) @@ -732,7 +733,7 @@ func (t *bumpSchemaVersionCompactionTask) addV3Stats(prefix string, fieldID int6 statsRelPath := fmt.Sprintf("_stats/%s.%d/%d", prefix, fieldID, statsID) absStatsPath := path.Join(writerResult.basePath, statsRelPath) if err := packed.WriteFile(t.compactionParams.StorageConfig, absStatsPath, bytes); err != nil { - return merr.WrapErrServiceInternal("failed to write V3 stats", err.Error()) + return merr.Wrap(err, "failed to write V3 stats") } writerResult.v3Stats = append(writerResult.v3Stats, packed.FieldBinlogStatEntry(prefix, fieldID, &datapb.FieldBinlog{ FieldID: fieldID, @@ -773,7 +774,7 @@ func (t *bumpSchemaVersionCompactionTask) buildMergedLogsV3(segment *datapb.Comp func (t *bumpSchemaVersionCompactionTask) preserveDeltaLogsV3(segment *datapb.CompactionSegmentBinlogs, manifestPath string) (string, error) { deltaPaths, err := packed.GetDeltaLogPathsFromManifest(segment.GetManifest(), t.compactionParams.StorageConfig) if err != nil { - return "", merr.WrapErrServiceInternal("failed to read V3 delta logs from existing manifest", err.Error()) + return "", merr.Wrap(err, "failed to read V3 delta logs from existing manifest") } if len(deltaPaths) == 0 { return manifestPath, nil @@ -784,12 +785,12 @@ func (t *bumpSchemaVersionCompactionTask) preserveDeltaLogsV3(segment *datapb.Co deltaSummaries = append(deltaSummaries, fieldBinlog.GetBinlogs()...) } if len(deltaSummaries) != len(deltaPaths) { - return "", merr.WrapErrServiceInternal(fmt.Sprintf("V3 delta manifest path count %d does not match segment delta summary count %d", len(deltaPaths), len(deltaSummaries))) + return "", merr.WrapErrServiceInternalMsg("V3 delta manifest path count %d does not match segment delta summary count %d", len(deltaPaths), len(deltaSummaries)) } basePath, _, err := packed.UnmarshalManifestPath(manifestPath) if err != nil { - return "", merr.WrapErrServiceInternal("failed to parse new V3 manifest for delta preservation", err.Error()) + return "", merr.WrapErrDataIntegrityMsg("failed to parse new V3 manifest for delta preservation: %s", err.Error()) } deltaEntries := make([]packed.DeltaLogEntry, 0, len(deltaPaths)) @@ -797,7 +798,7 @@ func (t *bumpSchemaVersionCompactionTask) preserveDeltaLogsV3(segment *datapb.Co newDeltaPath := metautil.BuildDeltaLogPathV3(basePath, deltaSummaries[i].GetLogID()) if newDeltaPath != deltaPath { if err := t.chunkManager.Copy(t.ctx, deltaPath, newDeltaPath); err != nil { - return "", merr.WrapErrServiceInternal("failed to copy V3 delta log for schema bump full rewrite", err.Error()) + return "", merr.Wrap(err, "failed to copy V3 delta log for schema bump full rewrite") } } deltaEntries = append(deltaEntries, packed.DeltaLogEntry{ @@ -807,7 +808,7 @@ func (t *bumpSchemaVersionCompactionTask) preserveDeltaLogsV3(segment *datapb.Co } newManifest, err := packed.AddDeltaLogsToManifest(manifestPath, t.compactionParams.StorageConfig, deltaEntries) if err != nil { - return "", merr.WrapErrServiceInternal("failed to preserve V3 delta logs in full rewrite manifest", err.Error()) + return "", merr.Wrap(err, "failed to preserve V3 delta logs in full rewrite manifest") } return newManifest, nil } @@ -923,7 +924,7 @@ func (t *bumpSchemaVersionCompactionTask) runMissingFunctionMaterialization(ctx if outputCol == nil { releaseWrappedRecord(wrapped, record) span.End() - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("output field %d not found in materialized record", outputFieldID)) + return nil, merr.WrapErrServiceInternalMsg("output field %d not found in materialized record", outputFieldID) } batchMemSize := arrowArrayMemorySize(outputCol) if stats, ok := statsByField[outputFieldID]; ok { @@ -997,7 +998,7 @@ func (t *bumpSchemaVersionCompactionTask) runMissingFunctionMaterialization(ctx }, ) if err != nil { - return nil, merr.WrapErrServiceInternal("failed to commit schema bump V3 manifest", err.Error()) + return nil, merr.Wrap(err, "failed to commit schema bump V3 manifest") } log.Info("[schema-bump-partial-writer] writer output and bm25 stats committed", zap.String("manifestPath", manifestPath), diff --git a/internal/datanode/compactor/clustering_compactor.go b/internal/datanode/compactor/clustering_compactor.go index d8c6219b6d..5b57f808cd 100644 --- a/internal/datanode/compactor/clustering_compactor.go +++ b/internal/datanode/compactor/clustering_compactor.go @@ -28,7 +28,6 @@ import ( "sync" "time" - "github.com/cockroachdb/errors" "github.com/samber/lo" "go.opentelemetry.io/otel" "go.uber.org/atomic" @@ -663,7 +662,7 @@ func (t *clusteringCompactionTask) mappingSegment( row, ok := v.Value.(map[typeutil.UniqueID]interface{}) if !ok { log.Warn("convert interface to map wrong") - return errors.New("unexpected error") + return merr.WrapErrServiceInternalMsg("unexpected error") } expireTs := int64(-1) if hasTTLField { @@ -976,7 +975,7 @@ func (t *clusteringCompactionTask) iterAndGetScalarAnalyzeResult(pkIter *storage // rowValue := vIter.GetData().(*iterators.InsertRow).GetValue() row, ok := (*v).Value.(map[typeutil.UniqueID]interface{}) if !ok { - return nil, 0, errors.New("unexpected error") + return nil, 0, merr.WrapErrServiceInternalMsg("unexpected error") } expireTs := int64(-1) diff --git a/internal/datanode/compactor/compactor_common.go b/internal/datanode/compactor/compactor_common.go index 01fd98fee7..f49c87aa68 100644 --- a/internal/datanode/compactor/compactor_common.go +++ b/internal/datanode/compactor/compactor_common.go @@ -46,6 +46,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/datapb" "github.com/milvus-io/milvus/pkg/v3/proto/indexcgopb" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/metautil" "github.com/milvus-io/milvus/pkg/v3/util/tsoutil" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" @@ -80,7 +81,7 @@ func createTextIndex(ctx context.Context, } binlogs, ok := fieldBinlogs[fieldID] if !ok { - return nil, fmt.Errorf("field binlog not found for field %d", fieldID) + return nil, merr.WrapErrParameterInvalidMsg("field binlog not found for field %d", fieldID) } result := make([]string, 0, len(binlogs)) for _, binlog := range binlogs { @@ -136,7 +137,7 @@ func createTextIndex(ctx context.Context, if segment.GetManifest() != "" { basePath, _, err := packed.UnmarshalManifestPath(segment.GetManifest()) if err != nil { - return fmt.Errorf("failed to unmarshal manifest path for text_index basePath: %w", err) + return merr.Wrap(err, "failed to unmarshal manifest path for text_index basePath") } statsBasePath = fmt.Sprintf("%s/_stats/text_index.%d", basePath, field.GetFieldID()) } diff --git a/internal/datanode/compactor/l0_compactor.go b/internal/datanode/compactor/l0_compactor.go index 46dd9a25ee..b6e5473711 100644 --- a/internal/datanode/compactor/l0_compactor.go +++ b/internal/datanode/compactor/l0_compactor.go @@ -18,10 +18,10 @@ package compactor import ( "context" + "fmt" "math" "sync" - "github.com/cockroachdb/errors" "github.com/samber/lo" "go.opentelemetry.io/otel" "go.uber.org/zap" @@ -40,6 +40,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/util/conc" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" "github.com/milvus-io/milvus/pkg/v3/util/hardware" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/metautil" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/timerecord" @@ -135,7 +136,9 @@ func (t *LevelZeroCompactionTask) Compact() (*datapb.CompactionPlanResult, error }) if len(targetSegments) == 0 { log.Warn("compact wrong, not target sealed segments") - return nil, errors.New("illegal compaction plan with empty target segments") + // The plan is produced by datacoord, so a malformed plan is an internal + // protocol violation, not user input. + return nil, merr.WrapErrServiceInternalMsg("illegal compaction plan with empty target segments") } err = binlog.DecompressCompactionBinlogsWithRootPath(t.compactionParams.StorageConfig.GetRootPath(), l0Segments) if err != nil { @@ -416,7 +419,7 @@ func (t *LevelZeroCompactionTask) process(ctx context.Context, l0MemSize int64, ratio := paramtable.Get().DataNodeCfg.L0BatchMemoryRatio.GetAsFloat() memLimit := float64(hardware.GetFreeMemoryCount()) * ratio if float64(l0MemSize) > memLimit { - return nil, errors.Newf("L0 compaction failed, not enough memory, request memory size: %v, memory limit: %v", l0MemSize, memLimit) + return nil, merr.Wrap(merr.ErrServiceMemoryLimitExceeded, fmt.Sprintf("L0 compaction failed, not enough memory, request memory size: %v, memory limit: %v", l0MemSize, memLimit)) } log.Info("L0 compaction process start") diff --git a/internal/datanode/compactor/mix_compactor.go b/internal/datanode/compactor/mix_compactor.go index 948159fde2..af80d6cb08 100644 --- a/internal/datanode/compactor/mix_compactor.go +++ b/internal/datanode/compactor/mix_compactor.go @@ -25,7 +25,6 @@ import ( "time" "github.com/apache/arrow/go/v17/arrow/array" - "github.com/cockroachdb/errors" "github.com/samber/lo" "go.opentelemetry.io/otel" "go.uber.org/zap" @@ -42,6 +41,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/datapb" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/metautil" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/timerecord" @@ -110,11 +110,13 @@ func (t *mixCompactionTask) preCompact() error { } if len(t.plan.GetSegmentBinlogs()) < 1 { - return errors.Newf("compaction plan is illegal, there's no segments in compaction plan, planID = %d", t.GetPlanID()) + // The plan is produced by datacoord, so a malformed plan is an internal + // protocol violation, not user input. + return merr.WrapErrServiceInternalMsg("compaction plan is illegal, there's no segments in compaction plan, planID = %d", t.GetPlanID()) } if t.plan.GetMaxSize() == 0 { - return errors.Newf("compaction plan is illegal, empty maxSize, planID = %d", t.GetPlanID()) + return merr.WrapErrServiceInternalMsg("compaction plan is illegal, empty maxSize, planID = %d", t.GetPlanID()) } t.collectionID = t.plan.GetSegmentBinlogs()[0].GetCollectionID() @@ -440,7 +442,7 @@ func (t *mixCompactionTask) Compact() (*datapb.CompactionPlanResult, error) { if isEmpty { log.Warn("compact wrong, all segments' binlogs are empty") - return nil, errors.New("illegal compaction plan") + return nil, merr.WrapErrServiceInternalMsg("illegal compaction plan") } sortMergeAppicable := t.compactionParams.UseMergeSort diff --git a/internal/datanode/compactor/record_materializer.go b/internal/datanode/compactor/record_materializer.go index b5101f44fc..19e1bb6cf6 100644 --- a/internal/datanode/compactor/record_materializer.go +++ b/internal/datanode/compactor/record_materializer.go @@ -17,12 +17,9 @@ package compactor import ( - "fmt" - "github.com/apache/arrow/go/v17/arrow" "github.com/apache/arrow/go/v17/arrow/array" "github.com/apache/arrow/go/v17/arrow/memory" - "github.com/cockroachdb/errors" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/storage" @@ -79,7 +76,7 @@ func NewRecordMaterializer(schema *schemapb.CollectionSchema, functions []*schem } if runner == nil { materializer.Close() - return nil, errors.Newf("failed to set up function runner for %s", functionSchema.GetName()) + return nil, merr.WrapErrFunctionFailedMsg("failed to set up function runner for %s", functionSchema.GetName()) } functionMaterializer, err := newFunctionMaterializer(schema, runner, outputIndexes, true) if err != nil { @@ -243,7 +240,7 @@ func (r *selectedRecord) Column(fieldID storage.FieldID) arrow.Array { defer selected.Release() col := selected.Column(fieldID) if col == nil { - r.err = errors.Newf("selected record field %d not found", fieldID) + r.err = merr.WrapErrServiceInternalMsg("selected record field %d not found", fieldID) return nil } col.Retain() @@ -347,7 +344,7 @@ func newFunctionMaterializer(schema *schemapb.CollectionSchema, runner function. case schemapb.FunctionType_MinHash: return newMinHashFunctionMaterializer(schema, runner, missingOutputIndexes, ownRunner) default: - return nil, errors.Newf("unsupported function type %s", functionSchema.GetType().String()) + return nil, merr.WrapErrParameterInvalidMsg("unsupported function type %s", functionSchema.GetType().String()) } } @@ -355,35 +352,35 @@ func newMinHashFunctionMaterializer(schema *schemapb.CollectionSchema, runner fu functionSchema := runner.GetSchema() inputFields := runner.GetInputFields() if len(inputFields) == 0 { - return nil, errors.New("minhash function should have input fields") + return nil, merr.WrapErrFunctionFailedMsg("minhash function should have input fields") } inputFieldIDs := make([]int64, 0, len(inputFields)) for _, inputField := range inputFields { if inputField == nil || typeutil.GetField(schema, inputField.GetFieldID()) == nil { - return nil, errors.New("input field not found in schema") + return nil, merr.WrapErrFunctionFailedMsg("input field not found in schema") } if inputField.GetDataType() != schemapb.DataType_VarChar && inputField.GetDataType() != schemapb.DataType_Text { - return nil, errors.New("input field data type must be varchar or text for minhash function materialization") + return nil, merr.WrapErrFunctionFailedMsg("input field data type must be varchar or text for minhash function materialization") } inputFieldIDs = append(inputFieldIDs, inputField.GetFieldID()) } outputFieldIDs := functionSchema.GetOutputFieldIds() if len(outputFieldIDs) == 0 { - return nil, errors.New("minhash function should have output fields") + return nil, merr.WrapErrFunctionFailedMsg("minhash function should have output fields") } outputFields := make(map[int64]*schemapb.FieldSchema, len(outputFieldIDs)) for _, outputFieldID := range outputFieldIDs { outputField := typeutil.GetField(schema, outputFieldID) if outputField == nil { - return nil, errors.New("output field not found in schema") + return nil, merr.WrapErrFunctionFailedMsg("output field not found in schema") } if outputField.GetDataType() != schemapb.DataType_BinaryVector { - return nil, errors.New("output field data type must be binary vector for minhash function materialization") + return nil, merr.WrapErrFunctionFailedMsg("output field data type must be binary vector for minhash function materialization") } if outputField.GetNullable() { - return nil, errors.Newf("function output field cannot be nullable: function %s, field %s", functionSchema.GetName(), outputField.GetName()) + return nil, merr.WrapErrFunctionFailedMsg("function output field cannot be nullable: function %s, field %s", functionSchema.GetName(), outputField.GetName()) } outputFields[outputFieldID] = outputField } @@ -402,35 +399,35 @@ func newBM25FunctionMaterializer(schema *schemapb.CollectionSchema, runner funct functionSchema := runner.GetSchema() inputFields := runner.GetInputFields() if len(inputFields) == 0 { - return nil, errors.New("bm25 function should have input fields") + return nil, merr.WrapErrParameterInvalidMsg("bm25 function should have input fields") } inputFieldIDs := make([]int64, 0, len(inputFields)) for _, inputField := range inputFields { if inputField == nil || typeutil.GetField(schema, inputField.GetFieldID()) == nil { - return nil, errors.New("input field not found in schema") + return nil, merr.WrapErrParameterInvalidMsg("input field not found in schema") } if inputField.GetDataType() != schemapb.DataType_VarChar && inputField.GetDataType() != schemapb.DataType_Text { - return nil, errors.New("input field data type must be varchar or text for bm25 function materialization") + return nil, merr.WrapErrParameterInvalidMsg("input field data type must be varchar or text for bm25 function materialization") } inputFieldIDs = append(inputFieldIDs, inputField.GetFieldID()) } outputFieldIDs := functionSchema.GetOutputFieldIds() if len(outputFieldIDs) == 0 { - return nil, errors.New("bm25 function should have output fields") + return nil, merr.WrapErrParameterInvalidMsg("bm25 function should have output fields") } outputFields := make(map[int64]*schemapb.FieldSchema, len(outputFieldIDs)) for _, outputFieldID := range outputFieldIDs { outputField := typeutil.GetField(schema, outputFieldID) if outputField == nil { - return nil, errors.New("output field not found in schema") + return nil, merr.WrapErrParameterInvalidMsg("output field not found in schema") } if outputField.GetDataType() != schemapb.DataType_SparseFloatVector { - return nil, errors.New("output field data type must be sparse float vector for bm25 function materialization") + return nil, merr.WrapErrParameterInvalidMsg("output field data type must be sparse float vector for bm25 function materialization") } if outputField.GetNullable() { - return nil, errors.Newf("function output field cannot be nullable: function %s, field %s", functionSchema.GetName(), outputField.GetName()) + return nil, merr.WrapErrParameterInvalidMsg("function output field cannot be nullable: function %s, field %s", functionSchema.GetName(), outputField.GetName()) } outputFields[outputFieldID] = outputField } @@ -459,7 +456,7 @@ func (m *bm25FunctionMaterializer) Materialize(rec storage.Record) (map[int64]ar return nil, err } if len(outputs) != len(m.outputFieldIDs) { - return nil, errors.Newf("bm25 function materialization expects %d outputs, got %d", len(m.outputFieldIDs), len(outputs)) + return nil, merr.WrapErrFunctionFailedMsg("bm25 function materialization expects %d outputs, got %d", len(m.outputFieldIDs), len(outputs)) } result := make(map[int64]arrow.Array, len(m.missingOutputIndexes)) @@ -468,7 +465,7 @@ func (m *bm25FunctionMaterializer) Materialize(rec storage.Record) (map[int64]ar outputSparseArray, ok := outputs[outputIndex].(*schemapb.SparseFloatArray) if !ok { releaseArrowArrays(result) - return nil, errors.Newf("unexpected output type from BM25 function runner, expected SparseFloatArray, got %T", outputs[outputIndex]) + return nil, merr.WrapErrFunctionFailedMsg("unexpected output type from BM25 function runner, expected SparseFloatArray, got %T", outputs[outputIndex]) } arr, err := buildSparseFloatVectorArrowArray(m.outputFields[outputFieldID], outputSparseArray, rec.Len()) if err != nil { @@ -500,7 +497,7 @@ func (m *minHashFunctionMaterializer) Materialize(rec storage.Record) (map[int64 return nil, err } if len(outputs) != len(m.outputFieldIDs) { - return nil, errors.Newf("minhash function materialization expects %d outputs, got %d", len(m.outputFieldIDs), len(outputs)) + return nil, merr.WrapErrFunctionFailedMsg("minhash function materialization expects %d outputs, got %d", len(m.outputFieldIDs), len(outputs)) } result := make(map[int64]arrow.Array, len(m.missingOutputIndexes)) @@ -509,12 +506,12 @@ func (m *minHashFunctionMaterializer) Materialize(rec storage.Record) (map[int64 outputFieldData, ok := outputs[outputIndex].(*schemapb.FieldData) if !ok { releaseArrowArrays(result) - return nil, errors.Newf("unexpected output type from MinHash function runner, expected FieldData, got %T", outputs[outputIndex]) + return nil, merr.WrapErrFunctionFailedMsg("unexpected output type from MinHash function runner, expected FieldData, got %T", outputs[outputIndex]) } vectorField := outputFieldData.GetVectors() if vectorField == nil || vectorField.GetBinaryVector() == nil { releaseArrowArrays(result) - return nil, errors.New("unexpected output from MinHash function runner, expected binary vector field data") + return nil, merr.WrapErrFunctionFailedMsg("unexpected output from MinHash function runner, expected binary vector field data") } fieldData := &storage.BinaryVectorFieldData{ Data: vectorField.GetBinaryVector(), @@ -522,7 +519,7 @@ func (m *minHashFunctionMaterializer) Materialize(rec storage.Record) (map[int64 } if fieldData.RowNum() != rec.Len() { releaseArrowArrays(result) - return nil, errors.Newf("minhash function output row count mismatch, expected %d, got %d", rec.Len(), fieldData.RowNum()) + return nil, merr.WrapErrFunctionFailedMsg("minhash function output row count mismatch, expected %d, got %d", rec.Len(), fieldData.RowNum()) } arr, err := buildArrowArrayFromFieldData(m.outputFields[outputFieldID], fieldData, rec.Len()) if err != nil { @@ -577,7 +574,7 @@ func missingNonMaterializedSchemaFields(schema *schemapb.CollectionSchema, exist func stringInputsFromRecord(rec storage.Record, fieldID int64) ([]string, error) { col := rec.Column(fieldID) if col == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("input field %d not found in record", fieldID)) + return nil, merr.WrapErrFunctionFailedMsg("input field %d not found in record", fieldID) } inputs := make([]string, rec.Len()) switch values := col.(type) { @@ -588,16 +585,16 @@ func stringInputsFromRecord(rec storage.Record, fieldID int64) ([]string, error) } } case *array.Binary: - return nil, merr.WrapErrServiceInternal("cannot materialize bm25 from text binary values without lob decoding") + return nil, merr.WrapErrFunctionFailedMsg("cannot materialize bm25 from text binary values without lob decoding") default: - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("input field %d data type must be varchar or text for bm25 function materialization, got %T", fieldID, col)) + return nil, merr.WrapErrFunctionFailedMsg("input field %d data type must be varchar or text for bm25 function materialization, got %T", fieldID, col) } return inputs, nil } func buildSparseFloatVectorArrowArray(field *schemapb.FieldSchema, outputSparseArray *schemapb.SparseFloatArray, rowCount int) (arrow.Array, error) { if len(outputSparseArray.GetContents()) != rowCount { - return nil, errors.Newf("bm25 function output row count mismatch, expected %d, got %d", rowCount, len(outputSparseArray.GetContents())) + return nil, merr.WrapErrFunctionFailedMsg("bm25 function output row count mismatch, expected %d, got %d", rowCount, len(outputSparseArray.GetContents())) } fieldData := &storage.SparseFloatVectorFieldData{ @@ -612,7 +609,7 @@ func buildSparseFloatVectorArrowArray(field *schemapb.FieldSchema, outputSparseA func buildArrowArrayFromFieldData(field *schemapb.FieldSchema, fieldData storage.FieldData, rowCount int) (arrow.Array, error) { if fieldData.RowNum() != rowCount { - return nil, errors.Newf("function output row count mismatch for field %d, expected %d, got %d", field.GetFieldID(), rowCount, fieldData.RowNum()) + return nil, merr.WrapErrFunctionFailedMsg("function output row count mismatch for field %d, expected %d, got %d", field.GetFieldID(), rowCount, fieldData.RowNum()) } outputSchema := &schemapb.CollectionSchema{Fields: []*schemapb.FieldSchema{field}} diff --git a/internal/datanode/compactor/segment_writer.go b/internal/datanode/compactor/segment_writer.go index 2846aca50a..651a0b749b 100644 --- a/internal/datanode/compactor/segment_writer.go +++ b/internal/datanode/compactor/segment_writer.go @@ -22,7 +22,6 @@ import ( "math" "github.com/apache/arrow/go/v17/arrow/array" - "github.com/cockroachdb/errors" "github.com/samber/lo" "go.uber.org/atomic" "go.uber.org/zap" @@ -37,6 +36,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/datapb" "github.com/milvus-io/milvus/pkg/v3/proto/etcdpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -318,7 +318,7 @@ func (w *SegmentWriter) WriteRecord(r storage.Record) error { for fieldID, stats := range w.bm25Stats { field, ok := r.Column(fieldID).(*array.Binary) if !ok { - return errors.New("bm25 field value not found") + return merr.WrapErrServiceInternalMsg("bm25 field value not found") } stats.AppendBytes(field.Value(i)) } @@ -341,12 +341,12 @@ func (w *SegmentWriter) Write(v *storage.Value) error { for fieldID, stats := range w.bm25Stats { data, ok := v.Value.(map[storage.FieldID]interface{})[fieldID] if !ok { - return errors.New("bm25 field value not found") + return merr.WrapErrServiceInternalMsg("bm25 field value not found") } bytes, ok := data.([]byte) if !ok { - return errors.New("bm25 field value not sparse bytes") + return merr.WrapErrServiceInternalMsg("bm25 field value not sparse bytes") } stats.AppendBytes(bytes) } diff --git a/internal/datanode/compactor/sort_compaction.go b/internal/datanode/compactor/sort_compaction.go index c6bc484f4a..1bfa5d22e8 100644 --- a/internal/datanode/compactor/sort_compaction.go +++ b/internal/datanode/compactor/sort_compaction.go @@ -23,7 +23,6 @@ import ( "time" "github.com/apache/arrow/go/v17/arrow/array" - "github.com/cockroachdb/errors" "github.com/samber/lo" "go.opentelemetry.io/otel" "go.uber.org/zap" @@ -109,7 +108,9 @@ func (t *sortCompactionTask) preCompact() error { } if len(t.plan.GetSegmentBinlogs()) != 1 { - return errors.Newf("sort compaction should handle exactly one segment, but got %d segments, planID = %d", + // The plan is produced by datacoord, so a malformed plan is an internal + // protocol violation, not user input. + return merr.WrapErrServiceInternalMsg("sort compaction should handle exactly one segment, but got %d segments, planID = %d", len(t.plan.GetSegmentBinlogs()), t.GetPlanID()) } diff --git a/internal/datanode/data_node.go b/internal/datanode/data_node.go index 3748718b51..6201bffd60 100644 --- a/internal/datanode/data_node.go +++ b/internal/datanode/data_node.go @@ -27,7 +27,6 @@ import ( "sync" "time" - "github.com/cockroachdb/errors" "github.com/tidwall/gjson" clientv3 "go.etcd.io/etcd/client/v3" "go.uber.org/zap" @@ -49,6 +48,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/util/conc" "github.com/milvus-io/milvus/pkg/v3/util/expr" "github.com/milvus-io/milvus/pkg/v3/util/lifetime" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/metricsinfo" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" @@ -166,7 +166,7 @@ func (node *DataNode) Register() error { func (node *DataNode) initSession() error { node.session = sessionutil.NewSession(node.ctx) if node.session == nil { - return errors.New("failed to initialize session") + return merr.WrapErrServiceInternalMsg("failed to initialize session") } node.session.Init(typeutil.DataNodeRole, node.address, false) sessionutil.SaveServerInfo(typeutil.DataNodeRole, node.session.ServerID) @@ -282,7 +282,7 @@ func (node *DataNode) isHealthy() bool { // ReadyToFlush tells whether DataNode is ready for flushing func (node *DataNode) ReadyToFlush() error { if !node.isHealthy() { - return errors.New("DataNode not in HEALTHY state") + return merr.Wrap(merr.ErrServiceNotReady, "DataNode not in HEALTHY state") } return nil } diff --git a/internal/datanode/external/function_executor.go b/internal/datanode/external/function_executor.go index f0be097d02..98c95b559c 100644 --- a/internal/datanode/external/function_executor.go +++ b/internal/datanode/external/function_executor.go @@ -20,6 +20,7 @@ import ( "github.com/milvus-io/milvus/internal/util/function/embedding" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -65,11 +66,11 @@ func ExecuteFunctionsForSegment( inputManifestPath, err := packed.CreateSegmentManifestWithBasePath( ctx, basePath, format, sourceColumns, fragments, storageConfig) if err != nil { - return "", fmt.Errorf("create input manifest: %w", err) + return "", merr.Wrap(err, "create input manifest") } _, inputVersion, err := packed.UnmarshalManifestPath(inputManifestPath) if err != nil { - return "", fmt.Errorf("parse input manifest path: %w", err) + return "", merr.Wrap(err, "parse input manifest path") } outputFields, outputSchema, err := buildOutputSchema(schema) @@ -94,7 +95,7 @@ func ExecuteFunctionsForSegment( colGroups := []storagecommon.ColumnGroup{{Columns: lo.Range(len(outputFields))}} writer, err := packed.NewFFIPackedWriter(basePath, outputArrow, colGroups, storageConfig, nil) if err != nil { - return "", fmt.Errorf("open output writer: %w", err) + return "", merr.Wrap(err, "open output writer") } writer.AsNewColumnGroups() @@ -108,7 +109,7 @@ func ExecuteFunctionsForSegment( output, err := writer.Close() if err != nil { - return "", fmt.Errorf("close output writer: %w", err) + return "", merr.Wrap(err, "close output writer") } if output != nil { defer output.Destroy() @@ -120,7 +121,7 @@ func ExecuteFunctionsForSegment( } manifestPath, err := packed.CommitManifestUpdates(basePath, inputVersion, storageConfig, updates) if err != nil { - return "", fmt.Errorf("commit function output manifest: %w", err) + return "", merr.Wrap(err, "commit function output manifest") } log.Info("function execution completed", @@ -139,7 +140,7 @@ func buildOutputSchema(schema *schemapb.CollectionSchema) ([]*schemapb.FieldSche return nil, nil, err } if len(outputFields) == 0 { - return nil, nil, fmt.Errorf("no function output fields; executor should not have been invoked") + return nil, nil, merr.WrapErrServiceInternalMsg("no function output fields; executor should not have been invoked") } return outputFields, &schemapb.CollectionSchema{ Name: schema.GetName(), @@ -177,7 +178,7 @@ func functionOutputFields(schema *schemapb.CollectionSchema) ([]*schemapb.FieldS continue } if _, ok := fieldsByID[fieldID]; !ok { - return nil, fmt.Errorf("function output field id %d not found in schema", fieldID) + return nil, merr.WrapErrParameterInvalidMsg("function output field id %d not found in schema", fieldID) } addOutputID(fieldID) } @@ -187,7 +188,7 @@ func functionOutputFields(schema *schemapb.CollectionSchema) ([]*schemapb.FieldS } field, ok := fieldsByName[fieldName] if !ok { - return nil, fmt.Errorf("function output field %s not found in schema", fieldName) + return nil, merr.WrapErrParameterInvalidMsg("function output field %s not found in schema", fieldName) } addOutputID(field.GetFieldID()) } @@ -222,7 +223,7 @@ func openInputReader( }), ) if err != nil { - return nil, fmt.Errorf("open input manifest: %w", err) + return nil, merr.Wrap(err, "open input manifest") } return reader, nil } @@ -236,7 +237,7 @@ func buildFunctionExecutionSchema( schema *schemapb.CollectionSchema, ) (*schemapb.CollectionSchema, *schemapb.CollectionSchema, typeutil.Set[int64], error) { if schema == nil { - return nil, nil, nil, fmt.Errorf("collection schema is nil") + return nil, nil, nil, merr.WrapErrParameterInvalidMsg("collection schema is nil") } inputIDs := make(map[int64]struct{}) for _, fn := range schema.GetFunctions() { @@ -291,24 +292,24 @@ func buildFunctionExecutionSchema( inputSchema.Fields = append(inputSchema.Fields, f) if schema.GetExternalSource() != "" && f.GetExternalField() == "" { - return nil, nil, nil, fmt.Errorf("function input field %s has no external_field", f.GetName()) + return nil, nil, nil, merr.WrapErrParameterInvalidMsg("function input field %s has no external_field", f.GetName()) } } for inputID := range inputIDs { if _, ok := seenInputFields[inputID]; !ok { if _, ok := seenExecutionFields[inputID]; !ok { - return nil, nil, nil, fmt.Errorf("function input field id %d not found in schema", inputID) + return nil, nil, nil, merr.WrapErrParameterInvalidMsg("function input field id %d not found in schema", inputID) } } } for outputID := range outputIDs { if _, ok := seenExecutionFields[outputID]; !ok { - return nil, nil, nil, fmt.Errorf("function output field id %d not found in schema", outputID) + return nil, nil, nil, merr.WrapErrParameterInvalidMsg("function output field id %d not found in schema", outputID) } } if len(inputSchema.GetFields()) == 0 { - return nil, nil, nil, fmt.Errorf("no source input columns for function execution") + return nil, nil, nil, merr.WrapErrParameterInvalidMsg("no source input columns for function execution") } return inputSchema, executionSchema, requiredInputFields, nil } @@ -332,7 +333,7 @@ func streamBatches( break } if err != nil { - return totalRows, fmt.Errorf("read input batch: %w", err) + return totalRows, merr.Wrap(err, "read input batch") } if rec == nil { break @@ -341,7 +342,7 @@ func streamBatches( batch, err := storage.RecordToInsertData(rec, executionSchema, requiredInputFields) rec.Release() if err != nil { - return totalRows, fmt.Errorf("record to InsertData: %w", err) + return totalRows, merr.Wrap(err, "record to InsertData") } if batch.GetRowNum() == 0 { continue @@ -351,7 +352,7 @@ func streamBatches( ClusterID: clusterID, DBName: schema.GetDbName(), }); err != nil { - return totalRows, fmt.Errorf("execute functions: %w", err) + return totalRows, merr.Wrap(err, "execute functions") } if err := accumulateBM25Stats(batch, bm25Acc); err != nil { @@ -359,7 +360,7 @@ func streamBatches( } if err := writeOutputBatch(batch, outputSchema, outputArrow, writer); err != nil { - return totalRows, fmt.Errorf("write output batch: %w", err) + return totalRows, merr.Wrap(err, "write output batch") } totalRows += int64(batch.GetRowNum()) } @@ -404,13 +405,13 @@ func accumulateBM25Stats(batch *storage.InsertData, acc map[int64]*storage.BM25S for outID, stats := range acc { raw, present := batch.Data[outID] if !present || raw == nil { - return fmt.Errorf( + return merr.WrapErrFunctionFailedMsg( "BM25 output field %d missing from batch; executeFunctions did not populate it", outID) } fd, ok := raw.(*storage.SparseFloatVectorFieldData) if !ok { - return fmt.Errorf( + return merr.WrapErrFunctionFailedMsg( "BM25 output field %d has wrong type %T (want *SparseFloatVectorFieldData)", outID, raw) } @@ -431,18 +432,18 @@ func appendBM25Stats( } log := log.Ctx(ctx) if updates == nil { - return fmt.Errorf("manifest updates is nil") + return merr.WrapErrServiceInternalMsg("manifest updates is nil") } entries := make([]packed.StatEntry, 0, len(acc)) for outID, stats := range acc { blob, err := stats.Serialize() if err != nil { - return fmt.Errorf("serialize bm25 stats for field %d: %w", outID, err) + return merr.Wrapf(err, "serialize bm25 stats for field %d", outID) } fullPath := path.Join(basePath, fmt.Sprintf("_stats/bm25.%d/%d", outID, 0)) if err := packed.WriteFile(storageConfig, fullPath, blob); err != nil { - return fmt.Errorf("write bm25 stats file %s: %w", fullPath, err) + return merr.Wrapf(err, "write bm25 stats file %s", fullPath) } entries = append(entries, packed.StatEntry{ Key: fmt.Sprintf("bm25.%d", outID), @@ -481,7 +482,7 @@ func finalizeBM25Stats( basePath, version, err := packed.UnmarshalManifestPath(manifestPath) if err != nil { - return "", fmt.Errorf("parse manifest path: %w", err) + return "", merr.Wrap(err, "parse manifest path") } updates := &packed.ManifestUpdates{} diff --git a/internal/datanode/external/manager.go b/internal/datanode/external/manager.go index dd1552a2fc..b463d3ab77 100644 --- a/internal/datanode/external/manager.go +++ b/internal/datanode/external/manager.go @@ -29,6 +29,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/datapb" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" "github.com/milvus-io/milvus/pkg/v3/util/conc" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // TaskKey uniquely identifies an external collection task. @@ -259,7 +260,8 @@ func (m *ExternalCollectionManager) SubmitTask( zap.ByteString("stack", stack)) reason := fmt.Sprintf("task panicked: %v", r) m.UpdateResult(clusterID, taskID, indexpb.JobState_JobStateFailed, reason, info.KeptSegments, nil) - retErr = fmt.Errorf("%s", reason) + // A recovered panic is a server-side failure, never caller input. + retErr = merr.WrapErrServiceInternalMsg("%s", reason) } }() log.Info("executing external collection task in pool", diff --git a/internal/datanode/external/task_update.go b/internal/datanode/external/task_update.go index 1fe6bd0622..15a8b91fda 100644 --- a/internal/datanode/external/task_update.go +++ b/internal/datanode/external/task_update.go @@ -164,22 +164,22 @@ func (t *RefreshExternalCollectionTask) PreExecute(ctx context.Context) error { zap.Int64("collectionID", t.req.GetCollectionID())) if t.req == nil { - return fmt.Errorf("request is nil") + return merr.WrapErrParameterInvalidMsg("request is nil") } if t.req.GetSchema() == nil { - return fmt.Errorf("schema is nil in request") + return merr.WrapErrParameterInvalidMsg("schema is nil in request") } if t.req.GetStorageConfig() == nil { - return fmt.Errorf("storage config is nil in request") + return merr.WrapErrParameterInvalidMsg("storage config is nil in request") } if t.req.GetExternalSource() == "" { - return fmt.Errorf("external source is empty in request") + return merr.WrapErrParameterInvalidMsg("external source is empty in request") } // Parse and cache external spec for reuse during Execute spec, err := externalspec.ParseExternalSpec(t.req.GetExternalSpec()) if err != nil { - return fmt.Errorf("failed to parse external spec: %w", err) + return merr.Wrap(err, "failed to parse external spec") } t.parsedSpec = spec t.columns = packed.GetColumnNamesFromSchema(t.req.GetSchema()) @@ -198,7 +198,7 @@ func (t *RefreshExternalCollectionTask) Execute(ctx context.Context) error { // Initialize pre-allocated segment IDs from request if t.req.GetPreAllocatedSegmentIds() == nil { - return fmt.Errorf("pre-allocated segment IDs not provided in request") + return merr.WrapErrParameterInvalidMsg("pre-allocated segment IDs not provided in request") } t.preallocatedIDRange = t.req.GetPreAllocatedSegmentIds() @@ -212,13 +212,13 @@ func (t *RefreshExternalCollectionTask) Execute(ctx context.Context) error { // Fetch fragments from external source newFragments, err := t.fetchFragmentsFromExternalSource(ctx) if err != nil { - return fmt.Errorf("failed to fetch fragments: %w", err) + return merr.Wrap(err, "failed to fetch fragments") } // Build current segment -> fragments mapping currentSegmentFragments, err := t.buildCurrentSegmentFragments() if err != nil { - return fmt.Errorf("failed to build current segment fragments: %w", err) + return merr.Wrap(err, "failed to build current segment fragments") } // Compare and organize segments @@ -235,7 +235,7 @@ func (t *RefreshExternalCollectionTask) Execute(ctx context.Context) error { func (t *RefreshExternalCollectionTask) fetchFragmentsFromExternalSource(ctx context.Context) ([]packed.Fragment, error) { manifestPath := t.req.GetExploreManifestPath() if manifestPath == "" { - return nil, fmt.Errorf("explore manifest path is required but not provided") + return nil, merr.WrapErrParameterMissingMsg("explore manifest path is required but not provided") } log.Ctx(ctx).Info("reading file list from explore manifest", @@ -329,7 +329,7 @@ func (t *RefreshExternalCollectionTask) organizeSegments( var err error outputColumns, err = functionOutputColumnNames(t.req.GetSchema()) if err != nil { - return nil, fmt.Errorf("resolve function output columns: %w", err) + return nil, merr.Wrap(err, "resolve function output columns") } } @@ -455,7 +455,7 @@ func (t *RefreshExternalCollectionTask) segmentHasFunctionOutputColumns(seg *dat } hasColumns, err := packed.ManifestHasColumns(seg.GetManifestPath(), t.req.GetStorageConfig(), outputColumns) if err != nil { - return false, fmt.Errorf("check function output columns for segment %d: %w", seg.GetID(), err) + return false, merr.Wrapf(err, "check function output columns for segment %d", seg.GetID()) } return hasColumns, nil } @@ -557,7 +557,7 @@ func (t *RefreshExternalCollectionTask) patchSegmentForMissingColumns( t.req.GetStorageConfig(), ) if err != nil { - return nil, fmt.Errorf("failed to append manifest columns for segment %d: %w", seg.GetID(), err) + return nil, merr.Wrapf(err, "failed to append manifest columns for segment %d", seg.GetID()) } sampleRows := paramtable.Get().QueryNodeCfg.ExternalCollectionSampleRows.GetAsInt() @@ -574,7 +574,7 @@ func (t *RefreshExternalCollectionTask) patchSegmentForMissingColumns( t.req.GetStorageConfig(), ) if err != nil { - return nil, fmt.Errorf("failed to sample external field sizes for segment %d: %w", seg.GetID(), err) + return nil, merr.Wrapf(err, "failed to sample external field sizes for segment %d", seg.GetID()) } externalAvgBytes := sumFieldSizes(fieldSizes, schema) if externalAvgBytes <= 0 { @@ -690,7 +690,7 @@ func (t *RefreshExternalCollectionTask) balanceFragmentsToSegments(ctx context.C } // Each segment needs 2 IDs: one for segment, one for fake binlog logID if t.nextAllocID+1 >= t.preallocatedIDRange.End { - return nil, fmt.Errorf("insufficient pre-allocated IDs: need 2 more but only have %d IDs in range [%d, %d)", + return nil, merr.WrapErrParameterInvalidMsg("insufficient pre-allocated IDs: need 2 more but only have %d IDs in range [%d, %d)", t.preallocatedIDRange.End-t.nextAllocID, t.preallocatedIDRange.Begin, t.preallocatedIDRange.End) @@ -739,7 +739,7 @@ func (t *RefreshExternalCollectionTask) balanceFragmentsToSegments(ctx context.C manifestPath, err = t.createManifestForSegment(ctx, work.segmentID, work.fragments) } if err != nil { - return "", fmt.Errorf("failed to create manifest for segment %d: %w", work.segmentID, err) + return "", merr.Wrapf(err, "failed to create manifest for segment %d", work.segmentID) } return manifestPath, nil }) @@ -834,7 +834,7 @@ func (t *RefreshExternalCollectionTask) balanceFragmentsToSegments(ctx context.C log.Warn("external field size sample produced non-positive total", zap.String("manifestPath", manifestPath), zap.Int64("total", total)) - recordErr(fmt.Errorf("sampled field sizes sum to %d (schema may have no external_field mappings, or sampled rows are empty)", total)) + recordErr(merr.WrapErrParameterInvalidMsg("sampled field sizes sum to %d (schema may have no external_field mappings, or sampled rows are empty)", total)) return 0, false } return total, true @@ -882,9 +882,9 @@ func (t *RefreshExternalCollectionTask) balanceFragmentsToSegments(ctx context.C if strings.Contains(rootCause, "not found in schema") { hint = "; check external_field mappings in collection schema against actual parquet columns" } - return nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf( + return nil, merr.WrapErrParameterInvalidMsg( "external field size sampling failed for all %d segment(s): %s%s", - len(manifestPaths), rootCause, hint)) + len(manifestPaths), rootCause, hint) } // Fill any zero slots (sampling failed mid-loop) with the first // successful average so every segment gets a non-zero MemorySize. @@ -1032,7 +1032,7 @@ func estimateFunctionOutputBytesPerRow(schema *schemapb.CollectionSchema) (int64 Fields: []*schemapb.FieldSchema{field}, }) if err != nil { - return 0, fmt.Errorf("estimate function output field %s: %w", field.GetName(), err) + return 0, merr.Wrapf(err, "estimate function output field %s", field.GetName()) } total += int64(size) } diff --git a/internal/datanode/importv2/copy_segment_utils.go b/internal/datanode/importv2/copy_segment_utils.go index 23441e38cd..1a300c89c3 100644 --- a/internal/datanode/importv2/copy_segment_utils.go +++ b/internal/datanode/importv2/copy_segment_utils.go @@ -18,7 +18,6 @@ package importv2 import ( "context" - "fmt" "path" "strconv" "strings" @@ -34,6 +33,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/datapb" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/metautil" ) @@ -114,12 +114,12 @@ func transformManifestPath( ) (string, error) { basePath, version, err := packed.UnmarshalManifestPath(manifestPath) if err != nil { - return "", fmt.Errorf("failed to unmarshal manifest path: %w", err) + return "", merr.Wrap(err, "failed to unmarshal manifest path") } targetBasePath, err := generateTargetPath(basePath, source, target) if err != nil { - return "", fmt.Errorf("failed to generate target base path: %w", err) + return "", merr.Wrap(err, "failed to generate target base path") } targetManifestPath := packed.MarshalManifestPath(targetBasePath, version) @@ -227,18 +227,18 @@ func collectSegmentFiles( // StorageV3+: binlog paths MUST come from manifest manifestPath := source.GetManifestPath() if manifestPath == "" { - return nil, fmt.Errorf("storage_version=%d requires manifest_path but it is empty (segmentID=%d)", + return nil, merr.WrapErrParameterInvalidMsg("storage_version=%d requires manifest_path but it is empty (segmentID=%d)", source.GetStorageVersion(), source.GetSegmentId()) } basePath, _, err := packed.UnmarshalManifestPath(manifestPath) if err != nil { - return nil, fmt.Errorf("failed to unmarshal manifest path %q for segment %d: %w", manifestPath, source.GetSegmentId(), err) + return nil, merr.Wrapf(err, "failed to unmarshal manifest path %q for segment %d", manifestPath, source.GetSegmentId()) } allFiles, listErr := listAllFiles(ctx, cm, basePath) if listErr != nil { - return nil, fmt.Errorf("failed to list files from manifest base path %q for segment %d: %w", basePath, source.GetSegmentId(), listErr) + return nil, merr.Wrapf(listErr, "failed to list files from manifest base path %q for segment %d", basePath, source.GetSegmentId()) } // Empty file list is OK for V3 — segment may have only deltas and no insert binlogs @@ -319,7 +319,7 @@ func generateMappingsFromFiles( } if err != nil { - return fmt.Errorf("failed to generate target path for %s file %s: %w", fileType, srcPath, err) + return merr.Wrapf(err, "failed to generate target path for %s file %s", fileType, srcPath) } mappings[srcPath] = dstPath } @@ -379,13 +379,13 @@ func CopySegmentAndIndexFiles( // Step 1: Collect all files to copy files, err := collectSegmentFiles(ctx, cm, source) if err != nil { - return nil, nil, fmt.Errorf("failed to collect segment files: %w", err) + return nil, nil, merr.Wrap(err, "failed to collect segment files") } // Step 2: Generate src->dst mappings for file copying mappings, err := generateMappingsFromFiles(files, source, target) if err != nil { - return nil, nil, fmt.Errorf("failed to generate file mappings: %w", err) + return nil, nil, merr.Wrap(err, "failed to generate file mappings") } // Step 3: Execute all copy operations @@ -400,7 +400,7 @@ func CopySegmentAndIndexFiles( fields = append(fields, logFields...) fields = append(fields, zap.String("src", src), zap.String("dst", dst), zap.Error(err)) log.Warn("failed to copy file", fields...) - return nil, copiedFiles, fmt.Errorf("failed to copy file from %s to %s: %w", src, dst, err) + return nil, copiedFiles, merr.Wrapf(err, "failed to copy file from %s to %s", src, dst) } copiedFiles = append(copiedFiles, dst) } @@ -418,7 +418,7 @@ func CopySegmentAndIndexFiles( if _, exists := mappings[srcPath]; !exists { dstPath, pathErr := generateTargetPath(srcPath, source, target) if pathErr != nil { - return nil, copiedFiles, fmt.Errorf("failed to generate target path for pb insert binlog %s: %w", srcPath, pathErr) + return nil, copiedFiles, merr.Wrapf(pathErr, "failed to generate target path for pb insert binlog %s", srcPath) } mappings[srcPath] = dstPath } @@ -430,20 +430,20 @@ func CopySegmentAndIndexFiles( // Step 4: Build index metadata from source indexInfos, textIndexInfos, jsonKeyIndexInfos, err := buildIndexInfoFromSource(source, target, mappings) if err != nil { - return nil, copiedFiles, fmt.Errorf("failed to build index info: %w", err) + return nil, copiedFiles, merr.Wrap(err, "failed to build index info") } // Step 5: Generate segment metadata with path mappings segmentInfo, err := generateSegmentInfoFromSource(source, target, mappings) if err != nil { - return nil, copiedFiles, fmt.Errorf("failed to generate segment info: %w", err) + return nil, copiedFiles, merr.Wrap(err, "failed to generate segment info") } // Step 6: Compress paths err = binlog.CompressBinLogs(segmentInfo.GetBinlogs(), segmentInfo.GetStatslogs(), segmentInfo.GetDeltalogs(), segmentInfo.GetBm25Logs()) if err != nil { - return nil, copiedFiles, fmt.Errorf("failed to compress binlog paths: %w", err) + return nil, copiedFiles, merr.Wrap(err, "failed to compress binlog paths") } for _, indexInfo := range indexInfos { @@ -474,7 +474,7 @@ func CopySegmentAndIndexFiles( if useManifest { targetManifestPath, err := transformManifestPath(source.GetManifestPath(), source, target) if err != nil { - return nil, copiedFiles, fmt.Errorf("failed to transform manifest path: %w", err) + return nil, copiedFiles, merr.Wrap(err, "failed to transform manifest path") } result.ManifestPath = targetManifestPath } @@ -526,7 +526,7 @@ func transformFieldBinlogs( } dstPath, ok := mappings[srcPath] if !ok { - return nil, 0, fmt.Errorf("no mapping found for source path: %s", srcPath) + return nil, 0, merr.WrapErrServiceInternalMsg("no mapping found for source path: %s", srcPath) } dstBinlog.LogPath = dstPath } @@ -583,7 +583,7 @@ func generateSegmentInfoFromSource( // Process insert binlogs (count rows) binlogs, totalRows, err := transformFieldBinlogs(source.GetInsertBinlogs(), mappings, true, source.GetIsExternalCollection()) if err != nil { - return nil, fmt.Errorf("failed to transform insert binlogs: %w", err) + return nil, merr.Wrap(err, "failed to transform insert binlogs") } segmentInfo.Binlogs = binlogs segmentInfo.ImportedRows = totalRows @@ -591,21 +591,21 @@ func generateSegmentInfoFromSource( // Process stats binlogs (no row counting) statslogs, _, err := transformFieldBinlogs(source.GetStatsBinlogs(), mappings, false, false) if err != nil { - return nil, fmt.Errorf("failed to transform stats binlogs: %w", err) + return nil, merr.Wrap(err, "failed to transform stats binlogs") } segmentInfo.Statslogs = statslogs // Process delta binlogs (no row counting) deltalogs, _, err := transformFieldBinlogs(source.GetDeltaBinlogs(), mappings, false, false) if err != nil { - return nil, fmt.Errorf("failed to transform delta binlogs: %w", err) + return nil, merr.Wrap(err, "failed to transform delta binlogs") } segmentInfo.Deltalogs = deltalogs // Process BM25 binlogs (no row counting) bm25logs, _, err := transformFieldBinlogs(source.GetBm25Binlogs(), mappings, false, false) if err != nil { - return nil, fmt.Errorf("failed to transform BM25 binlogs: %w", err) + return nil, merr.Wrap(err, "failed to transform BM25 binlogs") } segmentInfo.Bm25Logs = bm25logs @@ -635,7 +635,7 @@ func generateTargetPath(sourcePath string, source *datapb.CopySegmentSource, tar } if logTypeIndex == -1 || logTypeIndex+3 >= len(parts) { - return "", fmt.Errorf("invalid binlog path structure: %s (expected log_type at a valid position)", sourcePath) + return "", merr.WrapErrParameterInvalidMsg("invalid binlog path structure: %s (expected log_type at a valid position)", sourcePath) } // Replace IDs in order: collectionID, partitionID, segmentID @@ -667,7 +667,7 @@ func generateTargetLOBPath(sourcePath string, source *datapb.CopySegmentSource, // Path: .../{insert_log}/{coll}/{part}/lobs/... // Need at least logTypeIndex + 2 (coll and part) after insert_log if logTypeIndex == -1 || logTypeIndex+2 >= len(parts) { - return "", fmt.Errorf("invalid LOB path structure: %s", sourcePath) + return "", merr.WrapErrParameterInvalidMsg("invalid LOB path structure: %s", sourcePath) } parts[logTypeIndex+1] = strconv.FormatInt(target.GetCollectionId(), 10) @@ -709,7 +709,7 @@ func buildIndexInfoFromSource( for _, srcPath := range srcIndex.GetIndexFilePaths() { targetPath, ok := mappings[srcPath] if !ok { - return nil, nil, nil, fmt.Errorf("no mapping found for index file: %s", srcPath) + return nil, nil, nil, merr.WrapErrServiceInternalMsg("no mapping found for index file: %s", srcPath) } targetPaths = append(targetPaths, targetPath) } @@ -753,7 +753,7 @@ func buildIndexInfoFromSource( for _, srcFile := range srcText.GetFiles() { targetFile, ok := mappings[srcFile] if !ok { - return nil, nil, nil, fmt.Errorf("no mapping found for text index file: %s", srcFile) + return nil, nil, nil, merr.WrapErrServiceInternalMsg("no mapping found for text index file: %s", srcFile) } targetFiles = append(targetFiles, targetFile) } @@ -785,7 +785,7 @@ func buildIndexInfoFromSource( for _, srcFile := range srcJSON.GetFiles() { targetFile, ok := mappings[srcFile] if !ok { - return nil, nil, nil, fmt.Errorf("no mapping found for JSON index file: %s", srcFile) + return nil, nil, nil, merr.WrapErrServiceInternalMsg("no mapping found for JSON index file: %s", srcFile) } targetFiles = append(targetFiles, targetFile) } @@ -896,7 +896,7 @@ func generateTargetIndexPath( } if keywordIdx == -1 { - return "", fmt.Errorf("keyword '%s' not found in path: %s", keyword, sourcePath) + return "", merr.WrapErrServiceInternalMsg("keyword '%s' not found in path: %s", keyword, sourcePath) } // Set offsets based on index type @@ -928,13 +928,13 @@ func generateTargetIndexPath( segmentOffset = 6 buildIDOffset = 2 default: - return "", fmt.Errorf("unsupported index type: %s (expected '%s', '%s', '%s', or '%s')", + return "", merr.WrapErrParameterInvalidMsg("unsupported index type: %s (expected '%s', '%s', '%s', or '%s')", indexType, IndexTypeVectorScalarV0, IndexTypeText, IndexTypeJSONKey, IndexTypeJSONStats) } // Validate path structure has enough components if keywordIdx+segmentOffset >= len(parts) { - return "", fmt.Errorf("invalid %s path structure: %s (expected '%s' with at least %d components after it)", + return "", merr.WrapErrParameterInvalidMsg("invalid %s path structure: %s (expected '%s' with at least %d components after it)", indexType, sourcePath, indexType, segmentOffset+1) } diff --git a/internal/datanode/importv2/task_copy_segment.go b/internal/datanode/importv2/task_copy_segment.go index a2a272f752..8b40961b87 100644 --- a/internal/datanode/importv2/task_copy_segment.go +++ b/internal/datanode/importv2/task_copy_segment.go @@ -22,7 +22,6 @@ import ( "sync" "time" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" @@ -30,6 +29,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/datapb" "github.com/milvus-io/milvus/pkg/v3/util/conc" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -382,7 +382,7 @@ func (t *CopySegmentTask) copySingleSegment(source *datapb.CopySegmentSource, ta reason := "no insert/delete binlogs for segment" log.Error(reason, logFields...) t.manager.Update(t.GetTaskID(), UpdateState(datapb.ImportTaskStateV2_Failed), UpdateReason(reason)) - return nil, errors.New(reason) + return nil, merr.WrapErrParameterInvalidMsg(reason) } // Step 2: Copy all segment files (binlogs + indexes) together diff --git a/internal/datanode/importv2/task_preimport.go b/internal/datanode/importv2/task_preimport.go index 5553876eb8..7e2b410959 100644 --- a/internal/datanode/importv2/task_preimport.go +++ b/internal/datanode/importv2/task_preimport.go @@ -35,6 +35,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/datapb" "github.com/milvus-io/milvus/pkg/v3/proto/internalpb" "github.com/milvus-io/milvus/pkg/v3/util/conc" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -192,9 +193,9 @@ func (t *PreImportTask) readFileStat(reader importutilv2.Reader, fileIdx int) er } maxSize := paramtable.Get().DataNodeCfg.MaxImportFileSizeInGB.GetAsFloat() * 1024 * 1024 * 1024 if fileSize > int64(maxSize) { - return errors.New(fmt.Sprintf( + return merr.WrapErrParameterInvalidMsg( "The import file size has reached the maximum limit allowed for importing, "+ - "fileSize=%d, maxSize=%d", fileSize, int64(maxSize))) + "fileSize=%d, maxSize=%d", fileSize, int64(maxSize)) } totalRows := 0 diff --git a/internal/datanode/importv2/util.go b/internal/datanode/importv2/util.go index 52a489a473..92df85219d 100644 --- a/internal/datanode/importv2/util.go +++ b/internal/datanode/importv2/util.go @@ -50,7 +50,7 @@ import ( ) func WrapTaskNotFoundError(taskID int64) error { - return merr.WrapErrImportFailed(fmt.Sprintf("cannot find import task with id %d", taskID)) + return merr.WrapErrImportSysFailedMsg("cannot find import task with id %d", taskID) } func NewSyncTask(ctx context.Context, @@ -151,7 +151,7 @@ func PickSegment(segments []*datapb.ImportRequestSegment, vchannel string, parti }) if len(candidates) == 0 { - return 0, fmt.Errorf("no candidate segments found for channel %s and partition %d", + return 0, merr.WrapErrServiceInternalMsg("no candidate segments found for channel %s and partition %d", vchannel, partitionID) } @@ -392,7 +392,7 @@ func AppendNullableDefaultFieldsData(schema *schemapb.CollectionSchema, data *st } } default: - return fmt.Errorf("unexpected data type: %d, cannot be filled with default value", dataType) + return merr.WrapErrServiceInternalMsg("unexpected data type: %d, cannot be filled with default value", dataType) } if err != nil { @@ -409,7 +409,7 @@ func FillDynamicData(schema *schemapb.CollectionSchema, data *storage.InsertData } dynamicField := typeutil.GetDynamicField(schema) if dynamicField == nil { - return merr.WrapErrImportFailed("collection schema is illegal, enable_dynamic_field is true but the dynamic field doesn't exist") + return merr.WrapErrImportSysFailed("collection schema is illegal, enable_dynamic_field is true but the dynamic field doesn't exist") } tempData, ok := data.Data[dynamicField.GetFieldID()] diff --git a/internal/datanode/index/scheduler.go b/internal/datanode/index/scheduler.go index 5e4dbb0aba..ced5340ba1 100644 --- a/internal/datanode/index/scheduler.go +++ b/internal/datanode/index/scheduler.go @@ -80,7 +80,7 @@ func (queue *IndexTaskQueue) addUnissuedTask(t Task) error { defer queue.utLock.Unlock() if queue.utFull() { - return errors.New("index task queue is full") + return merr.Wrap(merr.ErrServiceResourceInsufficient, "index task queue is full") } queue.unissuedTasks.PushBack(t) select { diff --git a/internal/datanode/index/task_stats.go b/internal/datanode/index/task_stats.go index eaa5dbd042..ec0ae6fcf9 100644 --- a/internal/datanode/index/task_stats.go +++ b/internal/datanode/index/task_stats.go @@ -52,6 +52,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" "github.com/milvus-io/milvus/pkg/v3/proto/workerpb" _ "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/metautil" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/timerecord" @@ -292,7 +293,7 @@ func (st *statsTask) sort(ctx context.Context) ([]*datapb.FieldBinlog, error) { newManifest, err := packed.AddStatsToManifest( st.manifestPath, st.req.GetStorageConfig(), statEntries) if err != nil { - return nil, fmt.Errorf("failed to add stats to manifest: %w", err) + return nil, merr.Wrap(err, "failed to add stats to manifest") } st.manifestPath = newManifest } @@ -496,7 +497,7 @@ func (st *statsTask) createTextIndex(ctx context.Context, } binlogs, ok := fieldBinlogs[fieldID] if !ok && !enableNull { - return nil, fmt.Errorf("field binlog not found for field %d", fieldID) + return nil, merr.WrapErrServiceInternalMsg("field binlog not found for field %d", fieldID) } result := make([]string, 0, len(binlogs)) for _, binlog := range binlogs { @@ -618,7 +619,7 @@ func (st *statsTask) createTextIndex(ctx context.Context, newManifest, err := packed.AddStatsToManifest( st.manifestPath, st.req.GetStorageConfig(), statEntries) if err != nil { - return fmt.Errorf("failed to add text index stats to manifest: %w", err) + return merr.Wrap(err, "failed to add text index stats to manifest") } st.manifestPath = newManifest // Dual-write: keep textIndexLogs populated so it is stored in segment metadata as a @@ -685,7 +686,7 @@ func (st *statsTask) createJSONKeyStats(ctx context.Context, } binlogs, ok := fieldBinlogs[fieldID] if !ok && !enableNull { - return nil, fmt.Errorf("field binlog not found for field %d", fieldID) + return nil, merr.WrapErrServiceInternalMsg("field binlog not found for field %d", fieldID) } result := make([]string, 0, len(binlogs)) for _, binlog := range binlogs { @@ -796,7 +797,7 @@ func (st *statsTask) createJSONKeyStats(ctx context.Context, newManifest, err := packed.AddStatsToManifest( st.manifestPath, st.req.GetStorageConfig(), statEntries) if err != nil { - return fmt.Errorf("failed to add JSON key stats to manifest: %w", err) + return merr.Wrap(err, "failed to add JSON key stats to manifest") } st.manifestPath = newManifest // Dual-write: keep jsonKeyIndexStats populated so it is stored in segment metadata as a @@ -829,7 +830,7 @@ func computeStatsBasePath(req *workerpb.CreateStatsRequest, manifestPath string, if req.GetStorageVersion() == storage.StorageV3 { basePath, _, err := packed.UnmarshalManifestPath(manifestPath) if err != nil { - return "", fmt.Errorf("failed to unmarshal manifest path for %s basePath: %w", statsType, err) + return "", merr.Wrapf(err, "failed to unmarshal manifest path for %s basePath", statsType) } return fmt.Sprintf("%s/_stats/%s.%d", basePath, statsType, fieldID), nil } @@ -845,7 +846,7 @@ func computeStatsBasePath(req *workerpb.CreateStatsRequest, manifestPath string, req.GetTaskID(), req.GetTaskVersion(), req.GetCollectionID(), req.GetPartitionID(), req.GetTargetSegmentID(), fieldID), nil } - return "", fmt.Errorf("unknown stats type: %s", statsType) + return "", merr.WrapErrParameterInvalidMsg("unknown stats type: %s", statsType) } func buildIndexParams( diff --git a/internal/datanode/index_services.go b/internal/datanode/index_services.go index f73522d181..7f1a838948 100644 --- a/internal/datanode/index_services.go +++ b/internal/datanode/index_services.go @@ -21,7 +21,6 @@ import ( "fmt" "strconv" - "github.com/cockroachdb/errors" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -82,7 +81,9 @@ func (node *DataNode) CreateJob(ctx context.Context, req *workerpb.CreateJobRequ Cancel: taskCancel, State: commonpb.IndexState_InProgress, }); oldInfo != nil { - err := merr.WrapErrIndexDuplicate(req.GetIndexName(), "building index task existed") + // Node-internal dedup of a coordinator-dispatched build task: a duplicate + // is a scheduling race, not the user re-creating an index. + err := merr.WrapErrServiceInternalMsg("building index task existed, index=%s, buildID=%d", req.GetIndexName(), req.GetBuildID()) log.Warn("duplicated index build task", zap.Error(err)) metrics.DataNodeBuildIndexTaskCounter.WithLabelValues(paramtable.GetStringNodeID(), metrics.FailLabel).Inc() return merr.Status(err), nil @@ -254,7 +255,7 @@ func (node *DataNode) CreateJobV2(ctx context.Context, req *workerpb.CreateJobV2 return node.createStatsTask(ctx, statsRequest) default: log.Warn("DataNode receive unknown type job") - return merr.Status(fmt.Errorf("DataNode receive unknown type job with TaskID: %d", req.GetTaskID())), nil + return merr.Status(merr.WrapErrServiceInternalMsg("DataNode receive unknown type job with TaskID: %d", req.GetTaskID())), nil } } @@ -454,7 +455,7 @@ func (node *DataNode) QueryJobsV2(ctx context.Context, req *workerpb.QueryJobsV2 default: log.Warn("DataNode receive querying unknown type jobs") return &workerpb.QueryJobsV2Response{ - Status: merr.Status(errors.New("DataNode receive querying unknown type jobs")), + Status: merr.Status(merr.WrapErrServiceInternalMsg("DataNode receive querying unknown type jobs")), }, nil } } @@ -479,7 +480,7 @@ func (node *DataNode) queryIndexTask(ctx context.Context, req *workerpb.QueryJob log.Debug("query index jobs result success", zap.Any("results", results)) if len(results) == 0 { return &workerpb.QueryJobsV2Response{ - Status: merr.Status(fmt.Errorf("tasks '%v' not found", req.GetTaskIDs())), + Status: merr.Status(merr.WrapErrServiceInternalMsg("tasks '%v' not found", req.GetTaskIDs())), }, nil } return &workerpb.QueryJobsV2Response{ @@ -508,7 +509,7 @@ func (node *DataNode) queryStatsTask(ctx context.Context, req *workerpb.QueryJob log.Debug("query stats job result success", zap.Any("results", results)) if len(results) == 0 { return &workerpb.QueryJobsV2Response{ - Status: merr.Status(fmt.Errorf("tasks '%v' not found", req.GetTaskIDs())), + Status: merr.Status(merr.WrapErrServiceInternalMsg("tasks '%v' not found", req.GetTaskIDs())), }, nil } return &workerpb.QueryJobsV2Response{ @@ -542,7 +543,7 @@ func (node *DataNode) queryAnalyzeTask(ctx context.Context, req *workerpb.QueryJ log.Debug("query analyze jobs result success", zap.Any("results", results)) if len(results) == 0 { return &workerpb.QueryJobsV2Response{ - Status: merr.Status(fmt.Errorf("tasks '%v' not found", req.GetTaskIDs())), + Status: merr.Status(merr.WrapErrServiceInternalMsg("tasks '%v' not found", req.GetTaskIDs())), }, nil } return &workerpb.QueryJobsV2Response{ @@ -613,6 +614,6 @@ func (node *DataNode) DropJobsV2(ctx context.Context, req *workerpb.DropJobsV2Re return merr.Success(), nil default: log.Warn("DataNode receive dropping unknown type jobs") - return merr.Status(errors.New("DataNode receive dropping unknown type jobs")), nil + return merr.Status(merr.WrapErrServiceInternalMsg("DataNode receive dropping unknown type jobs")), nil } } diff --git a/internal/datanode/services.go b/internal/datanode/services.go index 88dda5d06e..9641919f66 100644 --- a/internal/datanode/services.go +++ b/internal/datanode/services.go @@ -194,11 +194,11 @@ func (node *DataNode) CompactionV2(ctx context.Context, req *datapb.CompactionPl } if req.GetBeginLogID() == 0 { - return merr.Status(merr.WrapErrParameterInvalidMsg("invalid beginLogID")), nil + return merr.Status(merr.WrapErrServiceInternalMsg("invalid beginLogID")), nil } if req.GetPreAllocatedLogIDs().GetBegin() == 0 || req.GetPreAllocatedLogIDs().GetEnd() == 0 { - return merr.Status(merr.WrapErrParameterInvalidMsg(fmt.Sprintf("invalid beginID %d or invalid endID %d", req.GetPreAllocatedLogIDs().GetBegin(), req.GetPreAllocatedLogIDs().GetEnd()))), nil + return merr.Status(merr.WrapErrServiceInternalMsg(fmt.Sprintf("invalid beginID %d or invalid endID %d", req.GetPreAllocatedLogIDs().GetBegin(), req.GetPreAllocatedLogIDs().GetEnd()))), nil } /* @@ -233,7 +233,7 @@ func (node *DataNode) CompactionV2(ctx context.Context, req *datapb.CompactionPl ) case datapb.CompactionType_MixCompaction: if req.GetPreAllocatedSegmentIDs() == nil || req.GetPreAllocatedSegmentIDs().GetBegin() == 0 { - return merr.Status(merr.WrapErrParameterInvalidMsg("invalid pre-allocated segmentID range")), nil + return merr.Status(merr.WrapErrServiceInternalMsg("invalid pre-allocated segmentID range")), nil } pk, err := typeutil.GetPrimaryFieldSchema(req.GetSchema()) if err != nil { @@ -256,7 +256,7 @@ func (node *DataNode) CompactionV2(ctx context.Context, req *datapb.CompactionPl ) case datapb.CompactionType_ClusteringCompaction: if req.GetPreAllocatedSegmentIDs() == nil || req.GetPreAllocatedSegmentIDs().GetBegin() == 0 { - return merr.Status(merr.WrapErrParameterInvalidMsg("invalid pre-allocated segmentID range")), nil + return merr.Status(merr.WrapErrServiceInternalMsg("invalid pre-allocated segmentID range")), nil } if namespaceEnabled { var sortFields []int64 @@ -281,7 +281,7 @@ func (node *DataNode) CompactionV2(ctx context.Context, req *datapb.CompactionPl } case datapb.CompactionType_SortCompaction: if req.GetPreAllocatedSegmentIDs() == nil || req.GetPreAllocatedSegmentIDs().GetBegin() == 0 { - return merr.Status(merr.WrapErrParameterInvalidMsg("invalid pre-allocated segmentID range")), nil + return merr.Status(merr.WrapErrServiceInternalMsg("invalid pre-allocated segmentID range")), nil } pk, err := typeutil.GetPrimaryFieldSchema(req.GetSchema()) if err != nil { @@ -306,7 +306,7 @@ func (node *DataNode) CompactionV2(ctx context.Context, req *datapb.CompactionPl task = compactor.NewBumpSchemaVersionCompactionTask(taskCtx, cm, req, compactionParams) default: log.Warn("Unknown compaction type", zap.String("type", req.GetType().String())) - return merr.Status(merr.WrapErrParameterInvalidMsg("Unknown compaction type: %v", req.GetType().String())), nil + return merr.Status(merr.WrapErrServiceInternalMsg("Unknown compaction type: %v", req.GetType().String())), nil } succeed, err := node.compactionExecutor.Enqueue(task) @@ -790,7 +790,7 @@ func (node *DataNode) CreateTask(ctx context.Context, request *workerpb.CreateTa } return node.CopySegment(ctx, req) default: - err := fmt.Errorf("unrecognized task type '%s', properties=%v", taskType, request.GetProperties()) + err := merr.WrapErrServiceInternalMsg("unrecognized task type '%s', properties=%v", taskType, request.GetProperties()) log.Ctx(ctx).Warn("CreateTask failed", zap.Error(err)) return merr.Status(err), nil } @@ -807,7 +807,7 @@ func wrapQueryTaskResult[Resp proto.Message](resp Resp, properties taskcommon.Pr } statusResp, ok := any(resp).(ResponseWithStatus) if !ok { - return &workerpb.QueryTaskResponse{Status: merr.Status(fmt.Errorf("response does not implement GetStatus"))}, nil + return &workerpb.QueryTaskResponse{Status: merr.Status(merr.WrapErrServiceInternalMsg("response does not implement GetStatus"))}, nil } return &workerpb.QueryTaskResponse{ Status: statusResp.GetStatus(), @@ -937,7 +937,7 @@ func (node *DataNode) QueryTask(ctx context.Context, request *workerpb.QueryTask resProperties.AppendReason(resp.GetReason()) return wrapQueryTaskResult(resp, resProperties) default: - err := fmt.Errorf("unrecognized task type '%s', properties=%v", taskType, request.GetProperties()) + err := merr.WrapErrServiceInternalMsg("unrecognized task type '%s', properties=%v", taskType, request.GetProperties()) log.Ctx(ctx).Warn("QueryTask failed", zap.Error(err)) return &workerpb.QueryTaskResponse{ Status: merr.Status(err), @@ -997,7 +997,7 @@ func (node *DataNode) DropTask(ctx context.Context, request *workerpb.DropTaskRe zap.String("clusterID", clusterID)) return merr.Success(), nil default: - err := fmt.Errorf("unrecognized task type '%s', properties=%v", taskType, request.GetProperties()) + err := merr.WrapErrServiceInternalMsg("unrecognized task type '%s', properties=%v", taskType, request.GetProperties()) log.Ctx(ctx).Warn("DropTask failed", zap.Error(err)) return merr.Status(err), nil } diff --git a/internal/datanode/services_test.go b/internal/datanode/services_test.go index 01142f01a0..d836c4c863 100644 --- a/internal/datanode/services_test.go +++ b/internal/datanode/services_test.go @@ -594,7 +594,10 @@ func (s *DataNodeServicesSuite) TestCreateTask() { } status, err := s.node.CreateTask(s.ctx, req) s.NoError(err) - s.Equal(commonpb.ErrorCode_UnexpectedError, status.GetErrorCode()) + // taskcommon.GetTaskType classifies an unrecognized task type as + // ServiceInternal: task types are coordinator-assigned, so a mismatch is + // an internal protocol violation, not user input. + s.Equal(merr.Code(merr.ErrServiceInternal), status.GetCode()) }) } @@ -685,7 +688,10 @@ func (s *DataNodeServicesSuite) TestQueryTask() { } resp, err := s.node.QueryTask(s.ctx, req) s.NoError(err) - s.Equal(commonpb.ErrorCode_UnexpectedError, resp.GetStatus().GetErrorCode()) + // taskcommon.GetTaskType classifies an unrecognized task type as + // ServiceInternal: task types are coordinator-assigned, so a mismatch is + // an internal protocol violation, not user input. + s.Equal(merr.Code(merr.ErrServiceInternal), resp.GetStatus().GetCode()) }) } @@ -771,7 +777,10 @@ func (s *DataNodeServicesSuite) TestDropTask() { } status, err := s.node.DropTask(s.ctx, req) s.NoError(err) - s.Equal(commonpb.ErrorCode_UnexpectedError, status.GetErrorCode()) + // taskcommon.GetTaskType classifies an unrecognized task type as + // ServiceInternal: task types are coordinator-assigned, so a mismatch is + // an internal protocol violation, not user input. + s.Equal(merr.Code(merr.ErrServiceInternal), status.GetCode()) }) } diff --git a/internal/distributed/datanode/client/client.go b/internal/distributed/datanode/client/client.go index b24d82cb8f..9bfebd7957 100644 --- a/internal/distributed/datanode/client/client.go +++ b/internal/distributed/datanode/client/client.go @@ -20,7 +20,6 @@ import ( "context" "fmt" - "github.com/cockroachdb/errors" "go.uber.org/zap" "google.golang.org/grpc" @@ -36,6 +35,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/workerpb" "github.com/milvus-io/milvus/pkg/v3/util/commonpbutil" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -57,11 +57,11 @@ type Client struct { // NewClient creates a client for DataNode. func NewClient(ctx context.Context, addr string, serverID int64, encryption bool) (types.DataNodeClient, error) { if addr == "" { - return nil, errors.New("address is empty") + return nil, merr.WrapErrParameterInvalidMsg("address is empty") } sess := sessionutil.NewSession(context.Background()) if sess == nil { - err := errors.New("new session error, maybe can not connect to etcd") + err := merr.WrapErrServiceUnavailable("new session error, maybe can not connect to etcd") log.Ctx(ctx).Debug("DataNodeClient New Etcd Session failed", zap.Error(err)) return nil, err } diff --git a/internal/distributed/mixcoord/client/client.go b/internal/distributed/mixcoord/client/client.go index b1d03d206e..2c9b184934 100644 --- a/internal/distributed/mixcoord/client/client.go +++ b/internal/distributed/mixcoord/client/client.go @@ -69,7 +69,7 @@ type Client struct { func NewClient(ctx context.Context) (types.MixCoordClient, error) { sess := sessionutil.NewSession(context.Background()) if sess == nil { - err := errors.New("new session error, maybe can not connect to etcd") + err := merr.WrapErrServiceUnavailable("new session error, maybe can not connect to etcd") log.Ctx(ctx).Debug("New MixCoord Client failed", zap.Error(err)) return nil, err } @@ -120,7 +120,7 @@ func (c *Client) getMixCoordAddr() (string, error) { return c.getCompatibleMixCoordAddr() } else { log.Warn("MixCoordClient mess key not exist", zap.Any("key", key)) - return "", errors.New("find no available mixcoord, check mixcoord state") + return "", merr.WrapErrNodeNotFound(0, "find no available mixcoord, check mixcoord state") } } log.Debug("MixCoordClient GetSessions success", @@ -137,12 +137,12 @@ func (c *Client) getCompatibleMixCoordAddr() (string, error) { msess, _, err := c.sess.GetSessions(c.ctx, typeutil.RootCoordRole) if err != nil { log.Debug("mixCoordClient getSessions failed", zap.Any("key", typeutil.RootCoordRole), zap.Error(err)) - return "", errors.New("find no available mixcoord, check mixcoord state") + return "", merr.WrapErrNodeNotFound(0, "find no available mixcoord, check mixcoord state") } ms, ok := msess[typeutil.RootCoordRole] if !ok { log.Warn("MixCoordClient mess key not exist", zap.Any("key", typeutil.RootCoordRole)) - return "", errors.New("find no available mixcoord, check mixcoord state") + return "", merr.WrapErrNodeNotFound(0, "find no available mixcoord, check mixcoord state") } log.Debug("MixCoordClient GetSessions use rootCoord", zap.Any("key", typeutil.RootCoordRole)) c.grpcClient.SetNodeID(ms.ServerID) diff --git a/internal/distributed/proxy/client/client.go b/internal/distributed/proxy/client/client.go index dfb9425ecd..912952628a 100644 --- a/internal/distributed/proxy/client/client.go +++ b/internal/distributed/proxy/client/client.go @@ -20,7 +20,6 @@ import ( "context" "fmt" - "github.com/cockroachdb/errors" "go.uber.org/zap" "google.golang.org/grpc" @@ -35,6 +34,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/proxypb" "github.com/milvus-io/milvus/pkg/v3/util/commonpbutil" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -50,11 +50,11 @@ type Client struct { // NewClient creates a new client instance func NewClient(ctx context.Context, addr string, nodeID int64) (types.ProxyClient, error) { if addr == "" { - return nil, errors.New("address is empty") + return nil, merr.WrapErrParameterInvalidMsg("address is empty") } sess := sessionutil.NewSession(context.Background()) if sess == nil { - err := errors.New("new session error, maybe can not connect to etcd") + err := merr.WrapErrServiceUnavailable("new session error, maybe can not connect to etcd") log.Ctx(ctx).Debug("Proxy client new session failed", zap.Error(err)) return nil, err } diff --git a/internal/distributed/proxy/httpserver/handler.go b/internal/distributed/proxy/httpserver/handler.go index cf614592d1..b4d945467b 100644 --- a/internal/distributed/proxy/httpserver/handler.go +++ b/internal/distributed/proxy/httpserver/handler.go @@ -17,13 +17,12 @@ package httpserver import ( - "fmt" - "github.com/gin-gonic/gin" "google.golang.org/protobuf/proto" "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb" "github.com/milvus-io/milvus/internal/types" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // Handlers handles http requests @@ -107,7 +106,7 @@ func (h *Handlers) handleDummy(c *gin.Context) (interface{}, error) { // use ShouldBind to supports binding JSON, XML, YAML, and protobuf. err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.Dummy(c, &req) } @@ -116,11 +115,11 @@ func (h *Handlers) handleCreateCollection(c *gin.Context) (interface{}, error) { wrappedReq := WrappedCreateCollectionRequest{} err := shouldBind(c, &wrappedReq) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } schemaProto, err := proto.Marshal(&wrappedReq.Schema) if err != nil { - return nil, fmt.Errorf("%w: marshal schema failed: %v", errBadRequest, err) + return nil, badRequestf(err, "marshal schema failed") } req := &milvuspb.CreateCollectionRequest{ Base: wrappedReq.Base, @@ -138,7 +137,7 @@ func (h *Handlers) handleDropCollection(c *gin.Context) (interface{}, error) { req := milvuspb.DropCollectionRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.DropCollection(c, &req) } @@ -147,7 +146,7 @@ func (h *Handlers) handleHasCollection(c *gin.Context) (interface{}, error) { req := milvuspb.HasCollectionRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.HasCollection(c, &req) } @@ -156,7 +155,7 @@ func (h *Handlers) handleDescribeCollection(c *gin.Context) (interface{}, error) req := milvuspb.DescribeCollectionRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.DescribeCollection(c, &req) } @@ -165,7 +164,7 @@ func (h *Handlers) handleLoadCollection(c *gin.Context) (interface{}, error) { req := milvuspb.LoadCollectionRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.LoadCollection(c, &req) } @@ -174,7 +173,7 @@ func (h *Handlers) handleReleaseCollection(c *gin.Context) (interface{}, error) req := milvuspb.ReleaseCollectionRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.ReleaseCollection(c, &req) } @@ -183,7 +182,7 @@ func (h *Handlers) handleGetCollectionStatistics(c *gin.Context) (interface{}, e req := milvuspb.GetCollectionStatisticsRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.GetCollectionStatistics(c, &req) } @@ -192,7 +191,7 @@ func (h *Handlers) handleShowCollections(c *gin.Context) (interface{}, error) { req := milvuspb.ShowCollectionsRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.ShowCollections(c, &req) } @@ -201,7 +200,7 @@ func (h *Handlers) handleCreatePartition(c *gin.Context) (interface{}, error) { req := milvuspb.CreatePartitionRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.CreatePartition(c, &req) } @@ -210,7 +209,7 @@ func (h *Handlers) handleDropPartition(c *gin.Context) (interface{}, error) { req := milvuspb.DropPartitionRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.DropPartition(c, &req) } @@ -219,7 +218,7 @@ func (h *Handlers) handleHasPartition(c *gin.Context) (interface{}, error) { req := milvuspb.HasPartitionRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.HasPartition(c, &req) } @@ -228,7 +227,7 @@ func (h *Handlers) handleLoadPartitions(c *gin.Context) (interface{}, error) { req := milvuspb.LoadPartitionsRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.LoadPartitions(c, &req) } @@ -237,7 +236,7 @@ func (h *Handlers) handleReleasePartitions(c *gin.Context) (interface{}, error) req := milvuspb.ReleasePartitionsRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.ReleasePartitions(c, &req) } @@ -246,7 +245,7 @@ func (h *Handlers) handleGetPartitionStatistics(c *gin.Context) (interface{}, er req := milvuspb.GetPartitionStatisticsRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.GetPartitionStatistics(c, &req) } @@ -255,7 +254,7 @@ func (h *Handlers) handleShowPartitions(c *gin.Context) (interface{}, error) { req := milvuspb.ShowPartitionsRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.ShowPartitions(c, &req) } @@ -264,7 +263,7 @@ func (h *Handlers) handleCreateAlias(c *gin.Context) (interface{}, error) { req := milvuspb.CreateAliasRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.CreateAlias(c, &req) } @@ -273,7 +272,7 @@ func (h *Handlers) handleDropAlias(c *gin.Context) (interface{}, error) { req := milvuspb.DropAliasRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.DropAlias(c, &req) } @@ -282,7 +281,7 @@ func (h *Handlers) handleAlterAlias(c *gin.Context) (interface{}, error) { req := milvuspb.AlterAliasRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.AlterAlias(c, &req) } @@ -291,7 +290,7 @@ func (h *Handlers) handleCreateIndex(c *gin.Context) (interface{}, error) { req := milvuspb.CreateIndexRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.CreateIndex(c, &req) } @@ -300,7 +299,7 @@ func (h *Handlers) handleDescribeIndex(c *gin.Context) (interface{}, error) { req := milvuspb.DescribeIndexRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.DescribeIndex(c, &req) } @@ -309,7 +308,7 @@ func (h *Handlers) handleGetIndexState(c *gin.Context) (interface{}, error) { req := milvuspb.GetIndexStateRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.GetIndexState(c, &req) } @@ -318,7 +317,7 @@ func (h *Handlers) handleGetIndexBuildProgress(c *gin.Context) (interface{}, err req := milvuspb.GetIndexBuildProgressRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.GetIndexBuildProgress(c, &req) } @@ -327,7 +326,7 @@ func (h *Handlers) handleDropIndex(c *gin.Context) (interface{}, error) { req := milvuspb.DropIndexRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.DropIndex(c, &req) } @@ -336,11 +335,11 @@ func (h *Handlers) handleInsert(c *gin.Context) (interface{}, error) { wrappedReq := WrappedInsertRequest{} err := shouldBind(c, &wrappedReq) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } req, err := wrappedReq.AsInsertRequest() if err != nil { - return nil, fmt.Errorf("%w: convert body to pb failed: %v", errBadRequest, err) + return nil, badRequestf(err, "convert body to pb failed") } return h.proxy.Insert(c, req) } @@ -349,7 +348,7 @@ func (h *Handlers) handleDelete(c *gin.Context) (interface{}, error) { req := milvuspb.DeleteRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.Delete(c, &req) } @@ -358,10 +357,10 @@ func (h *Handlers) handleSearch(c *gin.Context) (interface{}, error) { wrappedReq := SearchRequest{} err := shouldBind(c, &wrappedReq) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } if wrappedReq.HasSearchAggregation() { - return nil, fmt.Errorf("%w: searchAggregation is not supported for low-level REST search", errBadRequest) + return nil, badRequestf(merr.WrapErrParameterInvalidMsg("searchAggregation is not supported for low-level REST search"), "invalid request") } req := milvuspb.SearchRequest{ Base: wrappedReq.Base, @@ -392,7 +391,7 @@ func (h *Handlers) handleQuery(c *gin.Context) (interface{}, error) { req := milvuspb.QueryRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.Query(c, &req) } @@ -401,7 +400,7 @@ func (h *Handlers) handleFlush(c *gin.Context) (interface{}, error) { req := milvuspb.FlushRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.Flush(c, &req) } @@ -410,7 +409,7 @@ func (h *Handlers) handleCalcDistance(c *gin.Context) (interface{}, error) { wrappedReq := WrappedCalcDistanceRequest{} err := shouldBind(c, &wrappedReq) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } req := milvuspb.CalcDistanceRequest{ @@ -426,7 +425,7 @@ func (h *Handlers) handleGetFlushState(c *gin.Context) (interface{}, error) { req := milvuspb.GetFlushStateRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.GetFlushState(c, &req) } @@ -435,7 +434,7 @@ func (h *Handlers) handleGetPersistentSegmentInfo(c *gin.Context) (interface{}, req := milvuspb.GetPersistentSegmentInfoRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.GetPersistentSegmentInfo(c, &req) } @@ -444,7 +443,7 @@ func (h *Handlers) handleGetQuerySegmentInfo(c *gin.Context) (interface{}, error req := milvuspb.GetQuerySegmentInfoRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.GetQuerySegmentInfo(c, &req) } @@ -453,7 +452,7 @@ func (h *Handlers) handleGetReplicas(c *gin.Context) (interface{}, error) { req := milvuspb.GetReplicasRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.GetReplicas(c, &req) } @@ -462,7 +461,7 @@ func (h *Handlers) handleGetMetrics(c *gin.Context) (interface{}, error) { req := milvuspb.GetMetricsRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.GetMetrics(c, &req) } @@ -471,7 +470,7 @@ func (h *Handlers) handleLoadBalance(c *gin.Context) (interface{}, error) { req := milvuspb.LoadBalanceRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.LoadBalance(c, &req) } @@ -480,7 +479,7 @@ func (h *Handlers) handleGetCompactionState(c *gin.Context) (interface{}, error) req := milvuspb.GetCompactionStateRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.GetCompactionState(c, &req) } @@ -489,7 +488,7 @@ func (h *Handlers) handleGetCompactionStateWithPlans(c *gin.Context) (interface{ req := milvuspb.GetCompactionPlansRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.GetCompactionStateWithPlans(c, &req) } @@ -498,7 +497,7 @@ func (h *Handlers) handleManualCompaction(c *gin.Context) (interface{}, error) { req := milvuspb.ManualCompactionRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.ManualCompaction(c, &req) } @@ -507,7 +506,7 @@ func (h *Handlers) handleImport(c *gin.Context) (interface{}, error) { req := milvuspb.ImportRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.Import(c, &req) } @@ -516,7 +515,7 @@ func (h *Handlers) handleGetImportState(c *gin.Context) (interface{}, error) { req := milvuspb.GetImportStateRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.GetImportState(c, &req) } @@ -525,7 +524,7 @@ func (h *Handlers) handleListImportTasks(c *gin.Context) (interface{}, error) { req := milvuspb.ListImportTasksRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.ListImportTasks(c, &req) } @@ -534,7 +533,7 @@ func (h *Handlers) handleCreateCredential(c *gin.Context) (interface{}, error) { req := milvuspb.CreateCredentialRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.CreateCredential(c, &req) } @@ -543,7 +542,7 @@ func (h *Handlers) handleUpdateCredential(c *gin.Context) (interface{}, error) { req := milvuspb.UpdateCredentialRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.UpdateCredential(c, &req) } @@ -552,7 +551,7 @@ func (h *Handlers) handleDeleteCredential(c *gin.Context) (interface{}, error) { req := milvuspb.DeleteCredentialRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.DeleteCredential(c, &req) } @@ -561,7 +560,7 @@ func (h *Handlers) handleListCredUsers(c *gin.Context) (interface{}, error) { req := milvuspb.ListCredUsersRequest{} err := shouldBind(c, &req) if err != nil { - return nil, fmt.Errorf("%w: parse body failed: %v", errBadRequest, err) + return nil, badRequestf(err, "parse body failed") } return h.proxy.ListCredUsers(c, &req) } diff --git a/internal/distributed/proxy/httpserver/handler_v1_test.go b/internal/distributed/proxy/httpserver/handler_v1_test.go index bac32831c3..12221174be 100644 --- a/internal/distributed/proxy/httpserver/handler_v1_test.go +++ b/internal/distributed/proxy/httpserver/handler_v1_test.go @@ -1434,7 +1434,7 @@ func TestFp16Bf16VectorsV1(t *testing.T) { assert.Nil(t, err, "case %d: ", i) assert.Equal(t, testcase.errCode, returnBody.Code, "case %d: ", i, string(testcase.requestBody)) if testcase.errCode != 0 { - assert.Equal(t, testcase.errMsg, returnBody.Message, "case %d: ", i, string(testcase.requestBody)) + assert.Contains(t, returnBody.Message, testcase.errMsg, "case %d: ", i, string(testcase.requestBody)) } fmt.Println(w.Body.String()) }) diff --git a/internal/distributed/proxy/httpserver/handler_v2.go b/internal/distributed/proxy/httpserver/handler_v2.go index 82e08d2364..95d116c737 100644 --- a/internal/distributed/proxy/httpserver/handler_v2.go +++ b/internal/distributed/proxy/httpserver/handler_v2.go @@ -410,6 +410,35 @@ func wrapperPost(newReq newReqFunc, v2 handlerFuncV2) gin.HandlerFunc { dbName, collectionName, ).Inc() + + // Mirror the fail_input/fail_system metric split into the logs so a + // failed REST request can be filtered by error_type. System failures are + // logged at Warn (actionable); input failures at Info (expected user + // mistakes — keeping them at Warn would spam the logs). + if label == metrics.FailSystemLabel || label == metrics.FailInputLabel { + var status *commonpb.Status + switch r := resp.(type) { + case interface{ GetStatus() *commonpb.Status }: + status = r.GetStatus() + case *commonpb.Status: + status = r + } + errType := merr.SystemError + if label == metrics.FailInputLabel { + errType = merr.InputError + } + logger := log.Ctx(ctx).With( + zap.String("method", methodTag), + zap.String("error_type", errType.String()), + zap.Int32("code", status.GetCode()), + zap.String("reason", status.GetReason()), + ) + if errType == merr.InputError { + logger.Info("restful request returned an input error") + } else { + logger.Warn("restful request returned a system error") + } + } } } @@ -543,6 +572,9 @@ func wrapperProxyWithLimit(ctx context.Context, ginCtx *gin.Context, req any, ch } if err != nil { + // Expose the exact classification (incl. boundary InputError marks) to + // the REST access log; key must match accesslog/info.ContextErrorType. + ginCtx.Set("error_type", merr.GetErrorType(err).String()) 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()}) @@ -1025,7 +1057,7 @@ func (h *HandlersV2) waitForFlush(ctx context.Context, dbName string, collection segmentIDs := flushResp.GetCollSegIDs()[collectionName].GetData() flushTs, ok := flushResp.GetCollFlushTs()[collectionName] if !ok { - return merr.WrapErrServiceInternal(fmt.Sprintf("failed to get flush timestamp for collection %s", collectionName)) + return merr.WrapErrServiceInternalMsg("failed to get flush timestamp for collection %s", collectionName) } ticker := time.NewTicker(500 * time.Millisecond) @@ -1521,7 +1553,7 @@ func generatePlaceholderGroup(ctx context.Context, body string, collSchema *sche fieldName = field.Name vectorField = field } else { - return nil, errors.New("search without annsField, but already found multiple vector fields: [" + fieldName + ", " + field.Name + ",,,]") + return nil, merr.WrapErrParameterInvalidMsg("search without annsField, but already found multiple vector fields: [%s, %s,,,]", fieldName, field.Name) } } } @@ -1547,7 +1579,7 @@ func generatePlaceholderGroup(ctx context.Context, body string, collSchema *sche } } if vectorField == nil { - return nil, errors.New("cannot find a vector field named: " + fieldName) + return nil, merr.WrapErrFieldNotFound(fieldName, "cannot find a vector field") } dim := int64(0) if !typeutil.IsSparseFloatVectorType(vectorField.DataType) { diff --git a/internal/distributed/proxy/httpserver/handler_v2_test.go b/internal/distributed/proxy/httpserver/handler_v2_test.go index 52c9430f59..1fc444f6c1 100644 --- a/internal/distributed/proxy/httpserver/handler_v2_test.go +++ b/internal/distributed/proxy/httpserver/handler_v2_test.go @@ -3352,13 +3352,13 @@ func TestSearchV2(t *testing.T) { queryTestCases = append(queryTestCases, requestBodyTestCase{ path: SearchAction, requestBody: []byte(`{"collectionName": "book", "data": [[0.1, 0.2]], "annsField": "word_count", "filter": "book_id in [2, 4, 6, 8]", "limit": 4, "outputFields": ["word_count"], "params": {"radius":0.9, "range_filter": 0.1}, "groupingField": "test"}`), - errMsg: "can only accept json format request, error: cannot find a vector field named: word_count", + errMsg: "cannot find a vector field", errCode: 1801, }) queryTestCases = append(queryTestCases, requestBodyTestCase{ path: AdvancedSearchAction, requestBody: []byte(`{"collectionName": "hello_milvus", "search": [{"data": [[0.1, 0.2]], "annsField": "float_vector1", "metricType": "L2", "limit": 3}, {"data": [[0.1, 0.2]], "annsField": "float_vector2", "metricType": "L2", "limit": 3}], "rerank": {"strategy": "rrf", "params": {"k": 1}}}`), - errMsg: "can only accept json format request, error: cannot find a vector field named: float_vector1", + errMsg: "cannot find a vector field", errCode: 1801, }) // multiple annsFields diff --git a/internal/distributed/proxy/httpserver/timeout_middleware.go b/internal/distributed/proxy/httpserver/timeout_middleware.go index cc2bee5844..0722770b56 100644 --- a/internal/distributed/proxy/httpserver/timeout_middleware.go +++ b/internal/distributed/proxy/httpserver/timeout_middleware.go @@ -27,7 +27,6 @@ import ( "sync" "time" - "github.com/cockroachdb/errors" "github.com/gin-gonic/gin" "go.uber.org/zap" @@ -93,7 +92,7 @@ func (w *timeoutResponseRecorder) Write(data []byte) (int, error) { w.mu.Lock() defer w.mu.Unlock() if w.closed || w.body == nil { - return 0, errors.New("response writer closed") + return 0, merr.WrapErrServiceInternalMsg("response writer closed") } if !w.written() { w.size = 0 @@ -107,7 +106,7 @@ func (w *timeoutResponseRecorder) WriteString(s string) (int, error) { w.mu.Lock() defer w.mu.Unlock() if w.closed || w.body == nil { - return 0, errors.New("response writer closed") + return 0, merr.WrapErrServiceInternalMsg("response writer closed") } if !w.written() { w.size = 0 @@ -161,7 +160,7 @@ func (w *timeoutResponseRecorder) Written() bool { func (w *timeoutResponseRecorder) Flush() {} func (w *timeoutResponseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) { - return nil, nil, errors.New("response writer does not support hijack") + return nil, nil, merr.WrapErrServiceInternalMsg("response writer does not support hijack") } func (w *timeoutResponseRecorder) CloseNotify() <-chan bool { @@ -176,7 +175,7 @@ func (w *timeoutResponseRecorder) CommitTo(realWriter gin.ResponseWriter) error w.mu.Lock() defer w.mu.Unlock() if w.closed || w.body == nil { - return errors.New("response writer closed") + return merr.WrapErrServiceInternalMsg("response writer closed") } dst := realWriter.Header() diff --git a/internal/distributed/proxy/httpserver/utils.go b/internal/distributed/proxy/httpserver/utils.go index 0cd67feda0..0cdb2ee3e0 100644 --- a/internal/distributed/proxy/httpserver/utils.go +++ b/internal/distributed/proxy/httpserver/utils.go @@ -27,7 +27,6 @@ import ( "strings" "time" - "github.com/cockroachdb/errors" "github.com/gin-gonic/gin" "github.com/spf13/cast" "github.com/tidwall/gjson" @@ -239,7 +238,7 @@ func convertRange(field *schemapb.FieldSchema, result gjson.Result) (string, err func checkGetPrimaryKey(coll *schemapb.CollectionSchema, idResult gjson.Result) (string, error) { primaryField, ok := getPrimaryField(coll) if !ok { - return "", fmt.Errorf("collection: %s has no primary field", coll.Name) + return "", merr.WrapErrParameterInvalidMsg("collection: %s has no primary field", coll.Name) } resultStr, err := convertRange(primaryField, idResult) if err != nil { @@ -253,7 +252,7 @@ func checkGetPrimaryKey(coll *schemapb.CollectionSchema, idResult gjson.Result) // based on the primary key field type func convertIDsToSchemapbIDs(ids []interface{}, pkField *schemapb.FieldSchema) (*schemapb.IDs, error) { if len(ids) == 0 { - return nil, errors.New("ids array cannot be empty") + return nil, merr.WrapErrParameterMissingMsg("ids array cannot be empty") } switch pkField.DataType { @@ -270,18 +269,18 @@ func convertIDsToSchemapbIDs(ids []interface{}, pkField *schemapb.FieldSchema) ( // JSON numbers are decoded as float64 // Check if the float has a fractional part if v != math.Trunc(v) { - return nil, fmt.Errorf("invalid int64 id at index %d: %v has fractional part", i, v) + return nil, merr.WrapErrParameterInvalidMsg("invalid int64 id at index %d: %v has fractional part", i, v) } int64ID = int64(v) case string: // Try to parse string as int64 parsed, err := strconv.ParseInt(v, 10, 64) if err != nil { - return nil, fmt.Errorf("invalid int64 id at index %d: %v, error: %v", i, id, err) + return nil, merr.WrapErrParameterInvalidErr(err, "invalid int64 id at index %d: %v", i, id) } int64ID = parsed default: - return nil, fmt.Errorf("invalid id type at index %d: expected int64, got %T", i, id) + return nil, merr.WrapErrParameterInvalidMsg("invalid id type at index %d: expected int64, got %T", i, id) } int64IDs = append(int64IDs, int64ID) } @@ -304,10 +303,10 @@ func convertIDsToSchemapbIDs(ids []interface{}, pkField *schemapb.FieldSchema) ( // Convert number to string stringID = fmt.Sprintf("%v", v) default: - return nil, fmt.Errorf("invalid id type at index %d: expected string, got %T", i, id) + return nil, merr.WrapErrParameterInvalidMsg("invalid id type at index %d: expected string, got %T", i, id) } if stringID == "" { - return nil, fmt.Errorf("empty string id at index %d", i) + return nil, merr.WrapErrParameterInvalidMsg("empty string id at index %d", i) } stringIDs = append(stringIDs, stringID) } @@ -320,7 +319,7 @@ func convertIDsToSchemapbIDs(ids []interface{}, pkField *schemapb.FieldSchema) ( }, nil default: - return nil, fmt.Errorf("unsupported primary key type: %s", pkField.DataType.String()) + return nil, merr.WrapErrParameterInvalidMsg("unsupported primary key type: %s", pkField.DataType.String()) } } @@ -784,7 +783,7 @@ func checkAndSetData(body []byte, collSchema *schemapb.CollectionSchema, partial if !containsString(fieldNames, mapKey) { if collSchema.EnableDynamicField { if mapKey == common.MetaFieldName { - return nil, nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("use the invalid field name(%s) when enable dynamicField", mapKey)) + return nil, nil, merr.WrapErrParameterInvalidMsg("use the invalid field name(%s) when enable dynamicField", mapKey) } mapValueStr := mapValue.String() switch mapValue.Type { @@ -1106,7 +1105,7 @@ func decodeByteVectorElement(v gjson.Result, dim, bytesPerVec int64, isFloat16 b return nil, err } if int64(len(row)) != dim { - return nil, fmt.Errorf("vector dim mismatch: expect %d, got %d", dim, len(row)) + return nil, merr.WrapErrParameterInvalidMsg("vector dim mismatch: expect %d, got %d", dim, len(row)) } if isFloat16 { return typeutil.Float32ArrayToFloat16Bytes(row), nil @@ -1114,14 +1113,14 @@ func decodeByteVectorElement(v gjson.Result, dim, bytesPerVec int64, isFloat16 b return typeutil.Float32ArrayToBFloat16Bytes(row), nil } if v.Type != gjson.String { - return nil, fmt.Errorf("expect float vector array or base64 string") + return nil, merr.WrapErrParameterInvalidMsg("expect float vector array or base64 string") } var row []byte if err := json.Unmarshal([]byte(v.Raw), &row); err != nil { return nil, err } if int64(len(row)) != bytesPerVec { - return nil, fmt.Errorf("byte length mismatch: expect %d, got %d", bytesPerVec, len(row)) + return nil, merr.WrapErrParameterInvalidMsg("byte length mismatch: expect %d, got %d", bytesPerVec, len(row)) } return row, nil } @@ -1293,7 +1292,7 @@ func encodeEmbListQuery(vecs []gjson.Result, elemType schemapb.DataType, dim int func buildStructArrayFieldData(structSchema *schemapb.StructArrayFieldSchema, perRow []structArrayRow) (*schemapb.FieldData, error) { if len(perRow) == 0 { - return nil, fmt.Errorf("struct array field %s has no rows", structSchema.GetName()) + return nil, merr.WrapErrParameterInvalidMsg("struct array field %s has no rows", structSchema.GetName()) } subs := structSchema.GetFields() subFieldData := make([]*schemapb.FieldData, 0, len(subs)) @@ -1308,12 +1307,12 @@ func buildStructArrayFieldData(structSchema *schemapb.StructArrayFieldSchema, pe for rowIdx, row := range perRow { val, ok := row[short] if !ok { - return nil, fmt.Errorf("struct %s row %d missing sub-field %s", + return nil, merr.WrapErrParameterInvalidMsg("struct %s row %d missing sub-field %s", structSchema.GetName(), rowIdx, short) } scalar, ok := val.(*schemapb.ScalarField) if !ok { - return nil, fmt.Errorf("struct %s sub-field %s row %d: unexpected payload type %T", + return nil, merr.WrapErrParameterInvalidMsg("struct %s sub-field %s row %d: unexpected payload type %T", structSchema.GetName(), short, rowIdx, val) } arrayArray.Data = append(arrayArray.Data, scalar) @@ -1341,12 +1340,12 @@ func buildStructArrayFieldData(structSchema *schemapb.StructArrayFieldSchema, pe for rowIdx, row := range perRow { val, ok := row[short] if !ok { - return nil, fmt.Errorf("struct %s row %d missing sub-field %s", + return nil, merr.WrapErrParameterInvalidMsg("struct %s row %d missing sub-field %s", structSchema.GetName(), rowIdx, short) } vf, ok := val.(*schemapb.VectorField) if !ok { - return nil, fmt.Errorf("struct %s sub-field %s row %d: unexpected payload type %T", + return nil, merr.WrapErrParameterInvalidMsg("struct %s sub-field %s row %d: unexpected payload type %T", structSchema.GetName(), short, rowIdx, val) } vecArray.Data = append(vecArray.Data, vf) @@ -1365,7 +1364,7 @@ func buildStructArrayFieldData(structSchema *schemapb.StructArrayFieldSchema, pe }, }) default: - return nil, fmt.Errorf("unsupported struct sub-field data type: %s", sub.GetDataType()) + return nil, merr.WrapErrParameterInvalidMsg("unsupported struct sub-field data type: %s", sub.GetDataType()) } } return &schemapb.FieldData{ @@ -1403,11 +1402,11 @@ func extractStructArrayRow(fd *schemapb.FieldData, rowIdx int, schema *schemapb. case schemapb.DataType_Array: rowData := sub.GetScalars().GetArrayData().GetData() if rowIdx >= len(rowData) { - return nil, fmt.Errorf("struct sub-field %s missing row %d", short, rowIdx) + return nil, merr.WrapErrParameterInvalidMsg("struct sub-field %s missing row %d", short, rowIdx) } values := scalarArrayToInterfaces(rowData[rowIdx]) if len(values) != elemCount { - return nil, fmt.Errorf("struct sub-field %s element count mismatch: expect %d got %d", + return nil, merr.WrapErrParameterInvalidMsg("struct sub-field %s element count mismatch: expect %d got %d", short, elemCount, len(values)) } for i, v := range values { @@ -1416,28 +1415,28 @@ func extractStructArrayRow(fd *schemapb.FieldData, rowIdx int, schema *schemapb. case schemapb.DataType_ArrayOfVector: va := sub.GetVectors().GetVectorArray() if va == nil { - return nil, fmt.Errorf("struct sub-field %s has no vector array", short) + return nil, merr.WrapErrParameterInvalidMsg("struct sub-field %s has no vector array", short) } if rowIdx >= len(va.GetData()) { - return nil, fmt.Errorf("struct sub-field %s missing row %d", short, rowIdx) + return nil, merr.WrapErrParameterInvalidMsg("struct sub-field %s missing row %d", short, rowIdx) } dim, ok := subDims[short] if !ok || dim <= 0 { - return nil, fmt.Errorf("schema missing dim for struct sub-field %s", short) + return nil, merr.WrapErrParameterInvalidMsg("schema missing dim for struct sub-field %s", short) } values, err := vectorFieldToInterfaces(va.GetData()[rowIdx], va.GetElementType(), dim) if err != nil { return nil, err } if len(values) != elemCount { - return nil, fmt.Errorf("struct sub-field %s vector element count mismatch: expect %d got %d", + return nil, merr.WrapErrParameterInvalidMsg("struct sub-field %s vector element count mismatch: expect %d got %d", short, elemCount, len(values)) } for i, v := range values { out[i][short] = v } default: - return nil, fmt.Errorf("unsupported struct sub-field type %s", sub.GetType()) + return nil, merr.WrapErrParameterInvalidMsg("unsupported struct sub-field type %s", sub.GetType()) } } return out, nil @@ -1455,7 +1454,7 @@ func structArraySubDims(fieldName string, schema *schemapb.CollectionSchema) (ma } dim, err := getDim(sub) if err != nil { - return nil, fmt.Errorf("schema sub-field %s has no dim: %w", sub.GetName(), err) + return nil, merr.WrapErrParameterInvalidErr(err, "schema sub-field %s has no dim", sub.GetName()) } subDims[subShortName(sub)] = dim } @@ -1469,22 +1468,22 @@ func structSubElemCount(sub *schemapb.FieldData, rowIdx int, subDims map[string] case schemapb.DataType_Array: rowData := sub.GetScalars().GetArrayData().GetData() if rowIdx >= len(rowData) { - return 0, fmt.Errorf("struct sub-field %s row %d out of range", sub.GetFieldName(), rowIdx) + return 0, merr.WrapErrParameterInvalidMsg("struct sub-field %s row %d out of range", sub.GetFieldName(), rowIdx) } return len(scalarArrayToInterfaces(rowData[rowIdx])), nil case schemapb.DataType_ArrayOfVector: va := sub.GetVectors().GetVectorArray() if va == nil || rowIdx >= len(va.GetData()) { - return 0, fmt.Errorf("struct sub-field %s row %d out of range", sub.GetFieldName(), rowIdx) + return 0, merr.WrapErrParameterInvalidMsg("struct sub-field %s row %d out of range", sub.GetFieldName(), rowIdx) } short := structFieldShortName(sub.GetFieldName()) dim, ok := subDims[short] if !ok || dim <= 0 { - return 0, fmt.Errorf("schema missing dim for struct sub-field %s", short) + return 0, merr.WrapErrParameterInvalidMsg("schema missing dim for struct sub-field %s", short) } return vectorFieldElemCount(va.GetData()[rowIdx], va.GetElementType(), dim) default: - return 0, fmt.Errorf("unsupported struct sub-field type %s", sub.GetType()) + return 0, merr.WrapErrParameterInvalidMsg("unsupported struct sub-field type %s", sub.GetType()) } } @@ -1539,7 +1538,7 @@ func scalarArrayToInterfaces(sf *schemapb.ScalarField) []interface{} { func vectorFieldElemCount(vf *schemapb.VectorField, elemType schemapb.DataType, dim int64) (int, error) { if dim <= 0 { - return 0, fmt.Errorf("invalid dim %d", dim) + return 0, merr.WrapErrParameterInvalidMsg("invalid dim %d", dim) } switch elemType { case schemapb.DataType_FloatVector: @@ -1553,13 +1552,13 @@ func vectorFieldElemCount(vf *schemapb.VectorField, elemType schemapb.DataType, case schemapb.DataType_Int8Vector: return len(vf.GetInt8Vector()) / int(dim), nil default: - return 0, fmt.Errorf("unsupported vector element type %s", elemType) + return 0, merr.WrapErrParameterInvalidMsg("unsupported vector element type %s", elemType) } } func vectorFieldToInterfaces(vf *schemapb.VectorField, elemType schemapb.DataType, dim int64) ([]interface{}, error) { if dim <= 0 { - return nil, fmt.Errorf("invalid dim %d", dim) + return nil, merr.WrapErrParameterInvalidMsg("invalid dim %d", dim) } switch elemType { case schemapb.DataType_FloatVector: @@ -1612,7 +1611,7 @@ func vectorFieldToInterfaces(vf *schemapb.VectorField, elemType schemapb.DataTyp } return out, nil default: - return nil, fmt.Errorf("unsupported vector element type %s", elemType) + return nil, merr.WrapErrParameterInvalidMsg("unsupported vector element type %s", elemType) } } @@ -1620,7 +1619,7 @@ func convertFloatVectorToArray(vector [][]float32, dim int64) ([]float32, error) floatArray := make([]float32, 0) for _, arr := range vector { if int64(len(arr)) != dim { - return nil, fmt.Errorf("[]float32 size %d doesn't equal to vector dimension %d of %s", + return nil, merr.WrapErrParameterInvalidMsg("[]float32 size %d doesn't equal to vector dimension %d of %s", len(arr), dim, schemapb.DataType_name[int32(schemapb.DataType_FloatVector)]) } for i := int64(0); i < dim; i++ { @@ -1643,7 +1642,7 @@ func convertBinaryVectorToArray(vector [][]byte, dim int64, dataType schemapb.Da binaryArray := make([]byte, 0, len(vector)*int(bytesLen)) for _, arr := range vector { if int64(len(arr)) != bytesLen { - return nil, fmt.Errorf("[]byte size %d doesn't equal to vector dimension %d of %s", + return nil, merr.WrapErrParameterInvalidMsg("[]byte size %d doesn't equal to vector dimension %d of %s", len(arr), dim, schemapb.DataType_name[int32(dataType)]) } for i := int64(0); i < bytesLen; i++ { @@ -1657,7 +1656,7 @@ func convertInt8VectorToArray(vector [][]int8, dim int64) ([]byte, error) { byteArray := make([]byte, 0) for _, arr := range vector { if int64(len(arr)) != dim { - return nil, fmt.Errorf("[]int8 size %d doesn't equal to vector dimension %d of %s", + return nil, merr.WrapErrParameterInvalidMsg("[]int8 size %d doesn't equal to vector dimension %d of %s", len(arr), dim, schemapb.DataType_name[int32(schemapb.DataType_Int8Vector)]) } for i := int64(0); i < dim; i++ { @@ -1691,7 +1690,7 @@ func reflectValueCandi(v reflect.Value) (map[string]fieldCandi, error) { } return result, nil default: - return nil, fmt.Errorf("unsupport row type: %s", v.Kind().String()) + return nil, merr.WrapErrParameterInvalidMsg("unsupport row type: %s", v.Kind().String()) } } @@ -1713,7 +1712,7 @@ func convertToIntArray(dataType schemapb.DataType, arr interface{}) []int32 { func anyToColumns(rows []map[string]interface{}, validDataMap map[string][]bool, sch *schemapb.CollectionSchema, inInsert bool, partialUpdate bool) ([]*schemapb.FieldData, error) { rowsLen := len(rows) if rowsLen == 0 { - return []*schemapb.FieldData{}, errors.New("no row need to be convert to columns") + return []*schemapb.FieldData{}, merr.WrapErrParameterInvalidMsg("no row need to be convert to columns") } isDynamic := sch.EnableDynamicField @@ -1802,7 +1801,7 @@ func anyToColumns(rows []map[string]interface{}, validDataMap map[string][]bool, dim, _ := getDim(field) nameDims[field.Name] = dim default: - return nil, fmt.Errorf("the type(%v) of field(%v) is not supported, use other sdk please", field.DataType, field.Name) + return nil, merr.WrapErrParameterInvalidMsg("the type(%v) of field(%v) is not supported, use other sdk please", field.DataType, field.Name) } nameColumns[field.Name] = data fieldData[field.Name] = &schemapb.FieldData{ @@ -1813,7 +1812,7 @@ func anyToColumns(rows []map[string]interface{}, validDataMap map[string][]bool, } } if len(nameDims) == 0 && len(sch.Functions) == 0 && !partialUpdate { - return nil, fmt.Errorf("collection: %s has no vector field or functions", sch.Name) + return nil, merr.WrapErrParameterInvalidMsg("collection: %s has no vector field or functions", sch.Name) } dynamicCol := make([][]byte, 0, rowsLen) @@ -1836,7 +1835,7 @@ func anyToColumns(rows []map[string]interface{}, validDataMap map[string][]bool, continue } if !allowInsertAutoID { - return nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("no need to pass pk field(%s) when autoid==true in insert", field.Name)) + return nil, merr.WrapErrParameterInvalidMsg("no need to pass pk field(%s) when autoid==true in insert", field.Name) } } if (field.Nullable || field.DefaultValue != nil) && !ok { @@ -1851,7 +1850,7 @@ func anyToColumns(rows []map[string]interface{}, validDataMap map[string][]bool, if partialUpdate { continue } - return nil, fmt.Errorf("row %d does not has field %s", idx, field.Name) + return nil, merr.WrapErrParameterInvalidMsg("row %d does not has field %s", idx, field.Name) } fieldLen[field.Name] += 1 switch field.DataType { @@ -1893,7 +1892,7 @@ func anyToColumns(rows []map[string]interface{}, validDataMap map[string][]bool, vec := typeutil.Float32ArrayToFloat16Bytes(candi.v.Interface().([]float32)) nameColumns[field.Name] = append(nameColumns[field.Name].([][]byte), vec) default: - return nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("invalid type(%v) of field(%v) ", field.DataType, field.Name)) + return nil, merr.WrapErrParameterInvalidMsg("invalid type(%v) of field(%v) ", field.DataType, field.Name) } case schemapb.DataType_BFloat16Vector: switch candi.v.Interface().(type) { @@ -1903,7 +1902,7 @@ func anyToColumns(rows []map[string]interface{}, validDataMap map[string][]bool, vec := typeutil.Float32ArrayToBFloat16Bytes(candi.v.Interface().([]float32)) nameColumns[field.Name] = append(nameColumns[field.Name].([][]byte), vec) default: - return nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("invalid type(%v) of field(%v) ", field.DataType, field.Name)) + return nil, merr.WrapErrParameterInvalidMsg("invalid type(%v) of field(%v) ", field.DataType, field.Name) } case schemapb.DataType_SparseFloatVector: content := candi.v.Interface().([]byte) @@ -1915,7 +1914,7 @@ func anyToColumns(rows []map[string]interface{}, validDataMap map[string][]bool, case schemapb.DataType_Int8Vector: nameColumns[field.Name] = append(nameColumns[field.Name].([][]int8), candi.v.Interface().([]int8)) default: - return nil, fmt.Errorf("the type(%v) of field(%v) is not supported, use other sdk please", field.DataType, field.Name) + return nil, merr.WrapErrParameterInvalidMsg("the type(%v) of field(%v) is not supported, use other sdk please", field.DataType, field.Name) } delete(set, field.Name) @@ -1931,7 +1930,7 @@ func anyToColumns(rows []map[string]interface{}, validDataMap map[string][]bool, } bs, err := json.Marshal(m) if err != nil { - return nil, fmt.Errorf("failed to marshal dynamic field %w", err) + return nil, merr.WrapErrParameterInvalidErr(err, "failed to marshal dynamic field") } dynamicCol = append(dynamicCol, bs) } @@ -1949,7 +1948,7 @@ func anyToColumns(rows []map[string]interface{}, validDataMap map[string][]bool, zap.String("fieldName", name), zap.Int("fieldLen", len(validData)), zap.Int("rowsLen", rowsLen)) - return nil, fmt.Errorf("column %s has length %d, expected %d", name, len(validData), rowsLen) + return nil, merr.WrapErrParameterInvalidMsg("column %s has length %d, expected %d", name, len(validData), rowsLen) } } else { log.Info("skip empty field for partial update", @@ -1963,7 +1962,7 @@ func anyToColumns(rows []map[string]interface{}, validDataMap map[string][]bool, zap.String("fieldName", name), zap.Int("fieldLen", fieldLen[name]), zap.Int("rowsLen", rowsLen)) - return nil, fmt.Errorf("column %s has length %d, expected %d", name, fieldLen[name], rowsLen) + return nil, merr.WrapErrParameterInvalidMsg("column %s has length %d, expected %d", name, fieldLen[name], rowsLen) } colData := fieldData[name] @@ -2183,7 +2182,7 @@ func anyToColumns(rows []map[string]interface{}, validDataMap map[string][]bool, }, } default: - return nil, fmt.Errorf("the type(%v) of field(%v) is not supported, use other sdk please", colData.Type, name) + return nil, merr.WrapErrParameterInvalidMsg("the type(%v) of field(%v) is not supported, use other sdk please", colData.Type, name) } colData.ValidData = validDataMap[name] columns = append(columns, colData) @@ -2212,11 +2211,11 @@ func anyToColumns(rows []map[string]interface{}, validDataMap map[string][]bool, if partialUpdate { continue } - return nil, fmt.Errorf("row %d does not has struct field %s", rowIdx, structField.GetName()) + return nil, merr.WrapErrParameterInvalidMsg("row %d does not has struct field %s", rowIdx, structField.GetName()) } sr, ok := val.(structArrayRow) if !ok { - return nil, fmt.Errorf("row %d struct field %s has unexpected payload type %T", + return nil, merr.WrapErrParameterInvalidMsg("row %d struct field %s has unexpected payload type %T", rowIdx, structField.GetName(), val) } perRow = append(perRow, sr) @@ -2437,27 +2436,27 @@ func fieldDataValueCount(fieldData *schemapb.FieldData) (int64, error) { dim := fieldData.GetVectors().GetDim() bytesPerRow := dim / 8 if bytesPerRow <= 0 { - return 0, fmt.Errorf("invalid binary vector dimension %d for field %s", dim, fieldData.GetFieldName()) + return 0, merr.WrapErrParameterInvalidMsg("invalid binary vector dimension %d for field %s", dim, fieldData.GetFieldName()) } return int64(len(fieldData.GetVectors().GetBinaryVector())) / bytesPerRow, nil case schemapb.DataType_FloatVector: dim := fieldData.GetVectors().GetDim() if dim <= 0 { - return 0, fmt.Errorf("invalid float vector dimension %d for field %s", dim, fieldData.GetFieldName()) + return 0, merr.WrapErrParameterInvalidMsg("invalid float vector dimension %d for field %s", dim, fieldData.GetFieldName()) } return int64(len(fieldData.GetVectors().GetFloatVector().GetData())) / dim, nil case schemapb.DataType_Float16Vector: dim := fieldData.GetVectors().GetDim() bytesPerRow := dim * 2 if bytesPerRow <= 0 { - return 0, fmt.Errorf("invalid float16 vector dimension %d for field %s", dim, fieldData.GetFieldName()) + return 0, merr.WrapErrParameterInvalidMsg("invalid float16 vector dimension %d for field %s", dim, fieldData.GetFieldName()) } return int64(len(fieldData.GetVectors().GetFloat16Vector())) / bytesPerRow, nil case schemapb.DataType_BFloat16Vector: dim := fieldData.GetVectors().GetDim() bytesPerRow := dim * 2 if bytesPerRow <= 0 { - return 0, fmt.Errorf("invalid bfloat16 vector dimension %d for field %s", dim, fieldData.GetFieldName()) + return 0, merr.WrapErrParameterInvalidMsg("invalid bfloat16 vector dimension %d for field %s", dim, fieldData.GetFieldName()) } return int64(len(fieldData.GetVectors().GetBfloat16Vector())) / bytesPerRow, nil case schemapb.DataType_SparseFloatVector: @@ -2465,7 +2464,7 @@ func fieldDataValueCount(fieldData *schemapb.FieldData) (int64, error) { case schemapb.DataType_Int8Vector: dim := fieldData.GetVectors().GetDim() if dim <= 0 { - return 0, fmt.Errorf("invalid int8 vector dimension %d for field %s", dim, fieldData.GetFieldName()) + return 0, merr.WrapErrParameterInvalidMsg("invalid int8 vector dimension %d for field %s", dim, fieldData.GetFieldName()) } return int64(len(fieldData.GetVectors().GetInt8Vector())) / dim, nil case schemapb.DataType_ArrayOfStruct: @@ -2479,10 +2478,10 @@ func fieldDataValueCount(fieldData *schemapb.FieldData) (int64, error) { case schemapb.DataType_ArrayOfVector: return int64(len(subs[0].GetVectors().GetVectorArray().GetData())), nil default: - return 0, fmt.Errorf("unsupported struct sub-field type %s for field %s", subs[0].GetType(), fieldData.GetFieldName()) + return 0, merr.WrapErrParameterInvalidMsg("unsupported struct sub-field type %s for field %s", subs[0].GetType(), fieldData.GetFieldName()) } default: - return 0, fmt.Errorf("the type(%v) of field(%v) is not supported, use other sdk please", fieldData.GetType(), fieldData.GetFieldName()) + return 0, merr.WrapErrParameterInvalidMsg("the type(%v) of field(%v) is not supported, use other sdk please", fieldData.GetType(), fieldData.GetFieldName()) } } @@ -2532,7 +2531,7 @@ func newFieldDataRowAccessor(fieldData *schemapb.FieldData) (*fieldDataRowAccess return accessor, nil } if valueCount != validCount { - return nil, fmt.Errorf("field %s has %d valid rows, but data length is %d", fieldData.GetFieldName(), validCount, valueCount) + return nil, merr.WrapErrParameterInvalidMsg("field %s has %d valid rows, but data length is %d", fieldData.GetFieldName(), validCount, valueCount) } accessor.compactIndices = compactIndices return accessor, nil @@ -2543,7 +2542,7 @@ func (accessor *fieldDataRowAccessor) rowIndex(rowIdx int64) (int64, bool, error return rowIdx, true, nil } if rowIdx >= int64(len(accessor.validData)) { - return 0, false, fmt.Errorf("row index %d out of range for field %s valid data length %d", rowIdx, accessor.fieldData.GetFieldName(), len(accessor.validData)) + return 0, false, merr.WrapErrParameterInvalidMsg("row index %d out of range for field %s valid data length %d", rowIdx, accessor.fieldData.GetFieldName(), len(accessor.validData)) } if !accessor.validData[rowIdx] { return 0, false, nil @@ -2575,7 +2574,7 @@ func buildQueryResp(rowsNum int64, needFields []string, fieldDataList []*schemap stringPks := ids.GetStrId().GetData() rowsNum = int64(len(stringPks)) default: - return nil, errors.New("the type of primary key(id) is not supported, use other sdk please") + return nil, merr.WrapErrParameterInvalidMsg("the type of primary key(id) is not supported, use other sdk please") } } } @@ -2716,7 +2715,7 @@ func buildQueryResp(rowsNum int64, needFields []string, fieldDataList []*schemap stringPks := ids.GetStrId().GetData() row[pkFieldName] = stringPks[i] default: - return nil, errors.New("the type of primary key(id) is not supported, use other sdk please") + return nil, merr.WrapErrParameterInvalidMsg("the type of primary key(id) is not supported, use other sdk please") } } if scores != nil && int64(len(scores)) > i { @@ -2734,29 +2733,31 @@ func hasSearchAggregationResult(results *schemapb.SearchResultData) bool { func buildSearchAggregationResp(results *schemapb.SearchResultData, enableInt64 bool, collectionSchema *schemapb.CollectionSchema) ([]gin.H, error) { if results == nil { - return nil, errors.New("search_aggregation result is nil") + // The aggregation payload is produced by the server-side reduce, never + // by the request: a malformed shape is an internal contract violation. + return nil, merr.WrapErrServiceInternalMsg("search_aggregation result is nil") } aggTopks := results.GetAggTopks() pbBuckets := results.GetAggBuckets() if len(aggTopks) == 0 { - return nil, errors.New("search_aggregation response missing agg_topks") + return nil, merr.WrapErrServiceInternalMsg("search_aggregation response missing agg_topks") } if results.GetNumQueries() <= 0 { - return nil, errors.New("search_aggregation response missing nq") + return nil, merr.WrapErrServiceInternalMsg("search_aggregation response missing nq") } if len(aggTopks) != int(results.GetNumQueries()) { - return nil, fmt.Errorf("search_aggregation agg_topks length %d does not match nq %d", len(aggTopks), results.GetNumQueries()) + return nil, merr.WrapErrServiceInternalMsg("search_aggregation agg_topks length %d does not match nq %d", len(aggTopks), results.GetNumQueries()) } total := int64(0) for _, topk := range aggTopks { if topk < 0 { - return nil, errors.New("search_aggregation agg_topks cannot contain negative values") + return nil, merr.WrapErrServiceInternalMsg("search_aggregation agg_topks cannot contain negative values") } total += topk } if total != int64(len(pbBuckets)) { - return nil, fmt.Errorf("search_aggregation agg_topks sum %d does not match bucket count %d", total, len(pbBuckets)) + return nil, merr.WrapErrServiceInternalMsg("search_aggregation agg_topks sum %d does not match bucket count %d", total, len(pbBuckets)) } output := make([]gin.H, 0, len(aggTopks)) @@ -2778,7 +2779,7 @@ func buildSearchAggregationResp(results *schemapb.SearchResultData, enableInt64 func buildAggBucketResp(pb *schemapb.AggBucket, enableInt64 bool, collectionSchema *schemapb.CollectionSchema) (gin.H, error) { if pb == nil { - return nil, errors.New("search_aggregation bucket is nil") + return nil, merr.WrapErrServiceInternalMsg("search_aggregation bucket is nil") } bucket := gin.H{ "key": buildAggBucketKeyResp(pb.GetKey(), enableInt64), @@ -2986,7 +2987,7 @@ func convertConsistencyLevel(reqConsistencyLevel string) (commonpb.ConsistencyLe if reqConsistencyLevel != "" { level, ok := commonpb.ConsistencyLevel_value[reqConsistencyLevel] if !ok { - return 0, false, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("parameter:'%s' is incorrect, please check it", reqConsistencyLevel)) + return 0, false, merr.WrapErrParameterInvalidMsg("parameter:'%s' is incorrect, please check it", reqConsistencyLevel) } return commonpb.ConsistencyLevel(level), false, nil } @@ -3096,7 +3097,7 @@ func convertDefaultValue(value interface{}, dataType schemapb.DataType) (*schema } return data, nil default: - return nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("Unexpected default value type: %s", dataType.String())) + return nil, merr.WrapErrParameterInvalidMsg("Unexpected default value type: %s", dataType.String()) } } @@ -3491,7 +3492,7 @@ func generateSearchParams(reqSearchParams map[string]interface{}) ([]*commonpb.K for key, value := range reqSearchParams { if val, ok := paramsMap[key]; ok { if !deepEqual(val, value) { - return nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("ambiguous parameter: %s, in search_param: %v, in search_param.params: %v", key, value, val)) + return nil, merr.WrapErrParameterInvalidMsg("ambiguous parameter: %s, in search_param: %v, in search_param.params: %v", key, value, val) } } else if key != Params { paramsMap[key] = value diff --git a/internal/distributed/proxy/httpserver/utils_test.go b/internal/distributed/proxy/httpserver/utils_test.go index eec2011cfe..ab9e4dbe49 100644 --- a/internal/distributed/proxy/httpserver/utils_test.go +++ b/internal/distributed/proxy/httpserver/utils_test.go @@ -25,6 +25,7 @@ import ( "strings" "testing" + "github.com/cockroachdb/errors" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -36,6 +37,7 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/json" "github.com/milvus-io/milvus/pkg/v3/common" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -3005,7 +3007,8 @@ func TestBuildQueryResps(t *testing.T) { } _, err := buildQueryResp(int64(0), outputFields, newFieldData([]*schemapb.FieldData{}, 1000), generateIDs(schemapb.DataType_Int64, 3), DefaultScores, true, nil) - assert.Equal(t, "the type(1000) of field(wrong-field-type) is not supported, use other sdk please", err.Error()) + assert.Contains(t, err.Error(), "the type(1000) of field(wrong-field-type) is not supported, use other sdk please") + assert.True(t, errors.Is(err, merr.ErrParameterInvalid)) res, err := buildQueryResp(int64(0), outputFields, []*schemapb.FieldData{}, generateIDs(schemapb.DataType_Int64, 3), DefaultScores, true, nil) assert.Equal(t, 3, len(res)) diff --git a/internal/distributed/proxy/httpserver/wrap_request.go b/internal/distributed/proxy/httpserver/wrap_request.go index 488a641f78..243469ec00 100644 --- a/internal/distributed/proxy/httpserver/wrap_request.go +++ b/internal/distributed/proxy/httpserver/wrap_request.go @@ -17,15 +17,13 @@ package httpserver import ( - "fmt" - - "github.com/cockroachdb/errors" "google.golang.org/protobuf/proto" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/json" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -70,7 +68,7 @@ type WrappedInsertRequest struct { func (w *WrappedInsertRequest) AsInsertRequest() (*milvuspb.InsertRequest, error) { fieldData, err := convertFieldDataArray(w.FieldsData) if err != nil { - return nil, fmt.Errorf("%w: convert field data failed: %v", errBadRequest, err) + return nil, badRequestf(err, "convert field data failed") } return &milvuspb.InsertRequest{ Base: w.Base, @@ -98,12 +96,12 @@ func (f *FieldData) makePbFloat16OrBfloat16Array(raw json.RawMessage, serializeF return nil, 0, newFieldDataError(f.FieldName, err) } if len(wrappedData) < 1 { - return nil, 0, errors.New("at least one row for insert") + return nil, 0, merr.WrapErrParameterInvalidMsg("at least one row for insert") } array0 := wrappedData[0] dim := len(array0) if dim < 1 { - return nil, 0, errors.New("dim must >= 1") + return nil, 0, merr.WrapErrParameterInvalidMsg("dim must >= 1") } data := make([]byte, 0, len(wrappedData)*dim*2) for _, fp32Array := range wrappedData { @@ -238,12 +236,12 @@ func (f *FieldData) AsSchemapb() (*schemapb.FieldData, error) { return nil, newFieldDataError(f.FieldName, err) } if len(wrappedData) < 1 { - return nil, errors.New("at least one row for insert") + return nil, merr.WrapErrParameterInvalidMsg("at least one row for insert") } array0 := wrappedData[0] dim := len(array0) if dim < 1 { - return nil, errors.New("dim must >= 1") + return nil, merr.WrapErrParameterInvalidMsg("dim must >= 1") } data := make([]float32, len(wrappedData)*dim) @@ -299,7 +297,7 @@ func (f *FieldData) AsSchemapb() (*schemapb.FieldData, error) { return nil, newFieldDataError(f.FieldName, err) } if len(wrappedData) < 1 { - return nil, errors.New("at least one row for insert") + return nil, merr.WrapErrParameterInvalidMsg("at least one row for insert") } data := make([][]byte, len(wrappedData)) dim := int64(0) @@ -333,12 +331,12 @@ func (f *FieldData) AsSchemapb() (*schemapb.FieldData, error) { return nil, newFieldDataError(f.FieldName, err) } if len(wrappedData) < 1 { - return nil, errors.New("at least one row for insert") + return nil, merr.WrapErrParameterInvalidMsg("at least one row for insert") } array0 := wrappedData[0] dim := len(array0) if dim < 1 { - return nil, errors.New("dim must >= 1") + return nil, merr.WrapErrParameterInvalidMsg("dim must >= 1") } data := make([]byte, len(wrappedData)*dim) @@ -358,13 +356,13 @@ func (f *FieldData) AsSchemapb() (*schemapb.FieldData, error) { }, } default: - return nil, errors.New("unsupported data type") + return nil, merr.WrapErrParameterInvalidMsg("unsupported data type") } return &ret, nil } func newFieldDataError(field string, err error) error { - return fmt.Errorf("parse field[%s]: %s", field, err.Error()) + return merr.WrapErrParameterInvalidErr(err, "parse field[%s]", field) } func convertFieldDataArray(input []*FieldData) ([]*schemapb.FieldData, error) { diff --git a/internal/distributed/proxy/httpserver/wrapper.go b/internal/distributed/proxy/httpserver/wrapper.go index 43fe875152..283b4e5b30 100644 --- a/internal/distributed/proxy/httpserver/wrapper.go +++ b/internal/distributed/proxy/httpserver/wrapper.go @@ -25,10 +25,18 @@ import ( "github.com/gin-gonic/gin/binding" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) var errBadRequest = errors.New("bad request") +// badRequestf wraps err with the package-internal errBadRequest sentinel and +// a contextual message, preserving the inner error chain. wrapHandler maps +// any error that matches errBadRequest to HTTP 400. +func badRequestf(err error, format string, args ...any) error { + return merr.Mark(merr.Wrapf(err, format, args...), errBadRequest) +} + // handlerFunc handles http request with gin context type handlerFunc func(c *gin.Context) (interface{}, error) diff --git a/internal/distributed/proxy/listener_manager.go b/internal/distributed/proxy/listener_manager.go index 56c36dbf22..d169bc05e6 100644 --- a/internal/distributed/proxy/listener_manager.go +++ b/internal/distributed/proxy/listener_manager.go @@ -29,6 +29,7 @@ import ( "github.com/milvus-io/milvus/internal/storage" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/netutil" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -85,7 +86,7 @@ func newHTTPListner(ctx context.Context, l *listenerManager) error { } tlsMode := paramtable.Get().ProxyGrpcServerCfg.TLSMode.GetAsInt() if tlsMode != 0 && tlsMode != 1 && tlsMode != 2 { - return errors.New("tls mode must be 0: no authentication, 1: one way authentication or 2: two way authentication") + return merr.WrapErrParameterInvalidMsg("tls mode must be 0: no authentication, 1: one way authentication or 2: two way authentication") } httpPortString := HTTPParams.Port.GetValue() @@ -93,7 +94,7 @@ func newHTTPListner(ctx context.Context, l *listenerManager) error { externGrpcPort := l.externalGrpcListener.Port() if len(httpPortString) == 0 || externGrpcPort == httpPort { if tlsMode != 0 { - err := errors.New("proxy server(http) and external grpc server share the same port, tls mode must be 0") + err := merr.WrapErrParameterInvalidMsg("proxy server(http) and external grpc server share the same port, tls mode must be 0") log.Warn("can not initialize http listener", zap.Error(err)) return err } @@ -138,7 +139,7 @@ func newHTTPListner(ctx context.Context, l *listenerManager) error { } if !certPool.AppendCertsFromPEM(rootBuf) { log.Warn("fail to append ca to cert") - return errors.New("fail to append ca to cert") + return merr.WrapErrParameterInvalidMsg("fail to append ca to cert") } tlsConf = &tls.Config{ ClientAuth: tls.RequireAndVerifyClientCert, diff --git a/internal/distributed/proxy/request_interceptor.go b/internal/distributed/proxy/request_interceptor.go index 5b1678aeed..9f17b12577 100644 --- a/internal/distributed/proxy/request_interceptor.go +++ b/internal/distributed/proxy/request_interceptor.go @@ -22,10 +22,14 @@ import ( "strings" "time" + "go.uber.org/zap" "google.golang.org/grpc" + "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" + "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/metrics" "github.com/milvus-io/milvus/pkg/v3/util/conc" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/requestutil" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" @@ -74,6 +78,35 @@ func UnaryRequestStatsInterceptor(ctx context.Context, req any, rpcInfo *grpc.Un collectionName, ).Inc() + // Mirror the fail_input/fail_system metric split into the logs so a failed + // request can be filtered by error_type the same way the metric is. System + // failures are logged at Warn (actionable for SRE); input failures at Info + // (expected user mistakes — keeping them at Warn would spam the logs). + if label == metrics.FailSystemLabel || label == metrics.FailInputLabel { + var status *commonpb.Status + switch r := resp.(type) { + case interface{ GetStatus() *commonpb.Status }: + status = r.GetStatus() + case *commonpb.Status: + status = r + } + errType := merr.SystemError + if label == metrics.FailInputLabel { + errType = merr.InputError + } + logger := log.Ctx(ctx).With( + zap.String("method", methodTag), + zap.String("error_type", errType.String()), + zap.Int32("code", status.GetCode()), + zap.String("reason", status.GetReason()), + ) + if errType == merr.InputError { + logger.Info("rpc returned an input error") + } else { + logger.Warn("rpc returned a system error") + } + } + // set metrics for latency metrics.ProxyGRPCLatency.WithLabelValues( strconv.FormatInt(paramtable.GetNodeID(), 10), diff --git a/internal/distributed/proxy/request_interceptor_test.go b/internal/distributed/proxy/request_interceptor_test.go index 50e3c3cf05..e9bea9a5da 100644 --- a/internal/distributed/proxy/request_interceptor_test.go +++ b/internal/distributed/proxy/request_interceptor_test.go @@ -84,7 +84,7 @@ func (suite *StatsInterceptorSuite) TestUnaryRequestStatsInterceptor() { }, expectLabels: [][]string{ {paramtable.GetStringNodeID(), "CreateCollection", metrics.TotalLabel, dbName, collection}, - {paramtable.GetStringNodeID(), "CreateCollection", metrics.FailLabel, dbName, collection}, + {paramtable.GetStringNodeID(), "CreateCollection", metrics.FailSystemLabel, dbName, collection}, }, }, { @@ -120,7 +120,8 @@ func (suite *StatsInterceptorSuite) TestUnaryRequestStatsInterceptor() { }, expectLabels: [][]string{ {paramtable.GetStringNodeID(), "CreateCollection", metrics.TotalLabel, dbName, collection}, - {paramtable.GetStringNodeID(), "CreateCollection", metrics.RejectedLabel, dbName, collection}, + // Unauthenticated is a caller-side rejection -> rejected_user (review §8). + {paramtable.GetStringNodeID(), "CreateCollection", metrics.RejectedUserLabel, dbName, collection}, }, }, } diff --git a/internal/distributed/proxy/service.go b/internal/distributed/proxy/service.go index 6b3bec22f9..3a160729d9 100644 --- a/internal/distributed/proxy/service.go +++ b/internal/distributed/proxy/service.go @@ -350,7 +350,7 @@ func (s *Server) startExternalGrpc(errChan chan error) { } if !certPool.AppendCertsFromPEM(rootBuf) { log.Warn("fail to append ca to cert") - errChan <- errors.New("fail to append ca to cert") + errChan <- merr.WrapErrParameterInvalidMsg("fail to append ca to cert") return } diff --git a/internal/distributed/proxy/service_test.go b/internal/distributed/proxy/service_test.go index 4e1ab72be4..2740bc4684 100644 --- a/internal/distributed/proxy/service_test.go +++ b/internal/distributed/proxy/service_test.go @@ -1129,6 +1129,18 @@ func Test_NewServer_TLS_FileNotExisted(t *testing.T) { server.Stop() } +// freeTCPPort grabs a random unused TCP port. The TLS HTTP-server tests +// below hardcoded port 8080 originally, which collides with anything else +// already bound to that port on the dev machine (e.g. the TEI text-embedding +// container in our compose stack). +func freeTCPPort(t *testing.T) string { + ln, err := net.Listen("tcp", "127.0.0.1:0") + assert.NoError(t, err) + port := ln.Addr().(*net.TCPAddr).Port + assert.NoError(t, ln.Close()) + return strconv.Itoa(port) +} + func Test_NewHTTPServer_TLS_TwoWay(t *testing.T) { server := getServer(t) @@ -1149,7 +1161,7 @@ func Test_NewHTTPServer_TLS_TwoWay(t *testing.T) { paramtable.Get().Save(Params.ServerKeyPath.Key, "../../../configs/cert/server.key") paramtable.Get().Save(Params.CaPemPath.Key, "../../../configs/cert/ca.pem") paramtable.Get().Save(proxy.Params.HTTPCfg.Enabled.Key, "true") - paramtable.Get().Save(proxy.Params.HTTPCfg.Port.Key, "8080") + paramtable.Get().Save(proxy.Params.HTTPCfg.Port.Key, freeTCPPort(t)) err := runAndWaitForServerReady(server) assert.Nil(t, err) @@ -1183,7 +1195,7 @@ func Test_NewHTTPServer_TLS_OneWay(t *testing.T) { paramtable.Get().Save(Params.ServerPemPath.Key, "../../../configs/cert/server.pem") paramtable.Get().Save(Params.ServerKeyPath.Key, "../../../configs/cert/server.key") paramtable.Get().Save(proxy.Params.HTTPCfg.Enabled.Key, "true") - paramtable.Get().Save(proxy.Params.HTTPCfg.Port.Key, "8080") + paramtable.Get().Save(proxy.Params.HTTPCfg.Port.Key, freeTCPPort(t)) err := runAndWaitForServerReady(server) fmt.Printf("err: %v\n", err) @@ -1242,7 +1254,7 @@ func Test_NewHTTPServer_TLS_FileNotExisted(t *testing.T) { paramtable.Get().Save(Params.ServerPemPath.Key, "../not/existed/server.pem") paramtable.Get().Save(Params.ServerKeyPath.Key, "../../../configs/cert/server.key") paramtable.Get().Save(proxy.Params.HTTPCfg.Enabled.Key, "true") - paramtable.Get().Save(proxy.Params.HTTPCfg.Port.Key, "8080") + paramtable.Get().Save(proxy.Params.HTTPCfg.Port.Key, freeTCPPort(t)) err := runAndWaitForServerReady(server) assert.NotNil(t, err) server.Stop() diff --git a/internal/distributed/querynode/client/client.go b/internal/distributed/querynode/client/client.go index 53ab13346a..e5b0720abc 100644 --- a/internal/distributed/querynode/client/client.go +++ b/internal/distributed/querynode/client/client.go @@ -20,7 +20,6 @@ import ( "context" "fmt" - "github.com/cockroachdb/errors" "go.uber.org/zap" "google.golang.org/grpc" @@ -35,6 +34,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/querypb" "github.com/milvus-io/milvus/pkg/v3/util/commonpbutil" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -51,11 +51,11 @@ type Client struct { // NewClient creates a new QueryNode client. func NewClient(ctx context.Context, addr string, nodeID int64) (types.QueryNodeClient, error) { if addr == "" { - return nil, errors.New("addr is empty") + return nil, merr.WrapErrParameterInvalidMsg("addr is empty") } sess := sessionutil.NewSession(context.Background()) if sess == nil { - err := errors.New("new session error, maybe can not connect to etcd") + err := merr.WrapErrServiceUnavailable("new session error, maybe can not connect to etcd") log.Ctx(ctx).Debug("QueryNodeClient NewClient failed", zap.Error(err)) return nil, err } diff --git a/internal/distributed/streaming/balancer.go b/internal/distributed/streaming/balancer.go index ad79e05d02..4a0889c1cf 100644 --- a/internal/distributed/streaming/balancer.go +++ b/internal/distributed/streaming/balancer.go @@ -15,6 +15,11 @@ import ( "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) +// errStopWatching is a package-internal sentinel returned from the +// WatchChannelAssignments callback to signal "target found, stop watching". +// The outer call site identifies it via errors.Is and extracts the result. +var errStopWatching = errors.New("stop watching") + type balancerImpl struct { *walAccesserImpl } @@ -54,7 +59,6 @@ func (b balancerImpl) GetWALDistribution(ctx context.Context, nodeID int64) (*ty sbalancer := snmanager.StaticStreamingNodeManager.GetBalancer() var result *types.StreamingNodeAssignment - stopErr := errors.New("stop watching") err = sbalancer.WatchChannelAssignments(ctx, func(param balancer.WatchChannelAssignmentsCallbackParam) error { for _, assignment := range param.Relations { if assignment.Node.ServerID == nodeID { @@ -67,9 +71,9 @@ func (b balancerImpl) GetWALDistribution(ctx context.Context, nodeID int64) (*ty result.Channels[assignment.Channel.Name] = assignment.Channel } } - return stopErr + return errStopWatching }) - if errors.Is(err, stopErr) { + if errors.Is(err, errStopWatching) { if result == nil { return nil, merr.ErrNodeNotFound } diff --git a/internal/distributed/streaming/internal/producer/producer_rate_limiter.go b/internal/distributed/streaming/internal/producer/producer_rate_limiter.go index e91061c68c..2bd6f035be 100644 --- a/internal/distributed/streaming/internal/producer/producer_rate_limiter.go +++ b/internal/distributed/streaming/internal/producer/producer_rate_limiter.go @@ -33,8 +33,6 @@ import ( "github.com/milvus-io/milvus/pkg/v3/util/syncutil" ) -var ErrSlowDown = merr.WrapErrServiceRateLimit(0, "reach the limit of request, please slowdown and retry later") - // getDefaultBurst returns the default burst size from configuration. func getDefaultBurst() int { return int(paramtable.Get().StreamingCfg.WALRateLimitDefaultBurst.GetAsSize()) @@ -68,7 +66,7 @@ func (arl *produceRateLimiter) RequestReservation(ctx context.Context, msgs ...m } r := arl.limiter.ReserveN(time.Now(), msgSize) if !r.OK() { - return nil, ErrSlowDown + return nil, merr.WrapErrServiceRateLimit(0, "reach the limit of request, please slowdown and retry later") } return r, nil } diff --git a/internal/distributed/streaming/internal/producer/producer_rate_limiter_test.go b/internal/distributed/streaming/internal/producer/producer_rate_limiter_test.go index 67b34da778..d5ccf98156 100644 --- a/internal/distributed/streaming/internal/producer/producer_rate_limiter_test.go +++ b/internal/distributed/streaming/internal/producer/producer_rate_limiter_test.go @@ -27,6 +27,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/mocks/streaming/util/mock_message" "github.com/milvus-io/milvus/pkg/v3/proto/streamingpb" "github.com/milvus-io/milvus/pkg/v3/streaming/util/ratelimit" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) func TestProduceRateLimiter(t *testing.T) { @@ -164,7 +165,7 @@ func TestProduceRateLimiter(t *testing.T) { res, err := rl.RequestReservation(context.Background(), msg) assert.Error(t, err) assert.Nil(t, res) - assert.Equal(t, ErrSlowDown, err) + assert.ErrorIs(t, err, merr.ErrServiceRateLimit) }) } diff --git a/internal/distributed/streamingnode/service.go b/internal/distributed/streamingnode/service.go index f2c745f222..6c480ba38a 100644 --- a/internal/distributed/streamingnode/service.go +++ b/internal/distributed/streamingnode/service.go @@ -52,6 +52,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/util/funcutil" "github.com/milvus-io/milvus/pkg/v3/util/interceptor" "github.com/milvus-io/milvus/pkg/v3/util/logutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/netutil" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/retry" @@ -259,7 +260,7 @@ func (s *Server) start() (err error) { func (s *Server) initSession() error { s.session = sessionutil.NewSession(s.ctx) if s.session == nil { - return errors.New("session is nil, the etcd client connection may have failed") + return merr.WrapErrServiceUnavailable("session is nil, the etcd client connection may have failed") } s.session.Init(typeutil.StreamingNodeRole, s.listener.Address(), false) paramtable.SetNodeID(s.session.ServerID) diff --git a/internal/distributed/utils/util.go b/internal/distributed/utils/util.go index 097e15f1a9..68df31ddf8 100644 --- a/internal/distributed/utils/util.go +++ b/internal/distributed/utils/util.go @@ -6,12 +6,12 @@ import ( "os" "time" - "github.com/cockroachdb/errors" "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -80,7 +80,7 @@ func CreateCertPoolforClient(caFile string, nodeType string) (*x509.CertPool, er if !certPool.AppendCertsFromPEM(b) { log.Error("credentials: failed to append certificates") - return nil, errors.New("failed to append certificates") // Cert pool is invalid, return nil and the error + return nil, merr.WrapErrParameterInvalidMsg("failed to append certificates") // Cert pool is invalid, return nil and the error } return certPool, err } diff --git a/internal/flushcommon/pipeline/flow_graph_time_tick_node.go b/internal/flushcommon/pipeline/flow_graph_time_tick_node.go index 5b2f6ddd59..1ccae569f3 100644 --- a/internal/flushcommon/pipeline/flow_graph_time_tick_node.go +++ b/internal/flushcommon/pipeline/flow_graph_time_tick_node.go @@ -32,6 +32,7 @@ import ( "github.com/milvus-io/milvus/internal/flushcommon/writebuffer" "github.com/milvus-io/milvus/internal/util/flowgraph" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/tsoutil" ) @@ -154,7 +155,7 @@ func (ttn *ttNode) waitForCheckpointUpdate(fgMsg *FlowGraphMsg, curTs time.Time) } if !needUpdate { // Return a temporary error to trigger retry with backoff - return fmt.Errorf("checkpoint not ready yet") + return merr.Wrap(merr.ErrServiceUnavailable, "checkpoint not ready yet") } // needUpdate is true, operation succeeded return nil diff --git a/internal/flushcommon/syncmgr/growing_source.go b/internal/flushcommon/syncmgr/growing_source.go index a71ea3f396..b480337f12 100644 --- a/internal/flushcommon/syncmgr/growing_source.go +++ b/internal/flushcommon/syncmgr/growing_source.go @@ -536,7 +536,7 @@ func (t *GrowingSourceSyncTask) Run(ctx context.Context) (err error) { } expectedRows := t.targetOffset - segment.FlushedRows() if expectedRows < 0 { - return errors.Errorf("growing source target offset is behind flushed rows, flushedRows=%d targetOffset=%d segmentID=%d", + return merr.WrapErrServiceInternalMsg("growing source target offset is behind flushed rows, flushedRows=%d targetOffset=%d segmentID=%d", segment.FlushedRows(), t.targetOffset, t.segmentID) } if t.committedManifestPath != "" { @@ -546,10 +546,10 @@ func (t *GrowingSourceSyncTask) Run(ctx context.Context) (err error) { t.manifestPath = segment.ManifestPath() } else { if t.source == nil { - return errors.New("growing flush source is nil") + return merr.WrapErrServiceInternalMsg("growing flush source is nil") } if t.source.CurrentOffset() < t.targetOffset { - return errors.Errorf("growing flush source is behind target offset, current=%d target=%d", t.source.CurrentOffset(), t.targetOffset) + return merr.WrapErrServiceInternalMsg("growing flush source is behind target offset, current=%d target=%d", t.source.CurrentOffset(), t.targetOffset) } config, err := t.buildFlushConfig(segment) if err != nil { @@ -560,10 +560,10 @@ func (t *GrowingSourceSyncTask) Run(ctx context.Context) (err error) { return errors.Wrap(err, "flush growing source data") } if result == nil || result.ManifestPath == "" { - return errors.New("growing source flush returned empty manifest") + return merr.WrapErrDataIntegrityMsg("growing source flush returned empty manifest") } if result.NumRows != expectedRows { - return errors.Errorf("growing source flush row count mismatch, expected=%d actual=%d flushedRows=%d targetOffset=%d segmentID=%d", + return merr.WrapErrDataIntegrityMsg("growing source flush row count mismatch, expected=%d actual=%d flushedRows=%d targetOffset=%d segmentID=%d", expectedRows, result.NumRows, segment.FlushedRows(), t.targetOffset, t.segmentID) } t.manifestPath = result.ManifestPath diff --git a/internal/flushcommon/syncmgr/pack_writer.go b/internal/flushcommon/syncmgr/pack_writer.go index d7d76bdcea..2c37f86dd9 100644 --- a/internal/flushcommon/syncmgr/pack_writer.go +++ b/internal/flushcommon/syncmgr/pack_writer.go @@ -18,7 +18,6 @@ package syncmgr import ( "context" - "fmt" "path" "go.uber.org/zap" @@ -288,7 +287,7 @@ func (bw *BulkPackWriter) writeDelta(ctx context.Context, pack *SyncPack) (*data pkField, err := typeutil.GetPrimaryFieldSchema(bw.schema) if err != nil { - return nil, fmt.Errorf("primary key field not found: %w", err) + return nil, merr.Wrap(err, "primary key field not found") } logID, err := bw.allocator.AllocOne() diff --git a/internal/flushcommon/syncmgr/pack_writer_v3.go b/internal/flushcommon/syncmgr/pack_writer_v3.go index b3438ec92a..bd4f80fe17 100644 --- a/internal/flushcommon/syncmgr/pack_writer_v3.go +++ b/internal/flushcommon/syncmgr/pack_writer_v3.go @@ -34,6 +34,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/datapb" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/metautil" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/retry" @@ -325,7 +326,7 @@ func (bw *BulkPackWriterV3) resolveInsertWriterFormats() (string, []string, erro if version != packed.ManifestEarliest { for _, columnGroup := range bw.columnGroups { if columnGroup.Format == "" { - return "", nil, fmt.Errorf("column group %d fields %v missing format for existing manifest %s", + return "", nil, merr.WrapErrDataIntegrityMsg("column group %d fields %v missing format for existing manifest %s", columnGroup.GroupID, columnGroup.Fields, bw.initialManifestPath) } } @@ -346,7 +347,7 @@ func (bw *BulkPackWriterV3) writeDelta(ctx context.Context, pack *SyncPack, base pkField, err := typeutil.GetPrimaryFieldSchema(bw.schema) if err != nil { - return nil, nil, fmt.Errorf("primary key field not found: %w", err) + return nil, nil, merr.Wrap(err, "primary key field not found") } logID, err := bw.allocator.AllocOne() @@ -361,20 +362,20 @@ func (bw *BulkPackWriterV3) writeDelta(ctx context.Context, pack *SyncPack, base storage.WithStorageConfig(bw.storageConfig), ) if err != nil { - return nil, nil, fmt.Errorf("failed to create deltalog writer: %w", err) + return nil, nil, merr.Wrap(err, "failed to create deltalog writer") } record, _, _, err := storage.BuildDeleteRecord(pack.deltaData.Pks, pack.deltaData.Tss) if err != nil { - return nil, nil, fmt.Errorf("failed to build delete record: %w", err) + return nil, nil, merr.Wrap(err, "failed to build delete record") } defer record.Release() if err := writer.Write(record); err != nil { - return nil, nil, fmt.Errorf("failed to write delta record: %w", err) + return nil, nil, merr.Wrap(err, "failed to write delta record") } if err := writer.Close(); err != nil { - return nil, nil, fmt.Errorf("failed to close delta writer: %w", err) + return nil, nil, merr.Wrap(err, "failed to close delta writer") } bw.sizeWritten += pack.deltaData.Size() diff --git a/internal/flushcommon/syncmgr/sync_manager.go b/internal/flushcommon/syncmgr/sync_manager.go index a3abfe4ce2..7768e473c2 100644 --- a/internal/flushcommon/syncmgr/sync_manager.go +++ b/internal/flushcommon/syncmgr/sync_manager.go @@ -6,7 +6,6 @@ import ( "strconv" "time" - "github.com/cockroachdb/errors" "github.com/hashicorp/golang-lru/v2/expirable" "go.uber.org/zap" @@ -20,6 +19,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/util/conc" "github.com/milvus-io/milvus/pkg/v3/util/hardware" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -115,7 +115,7 @@ func (mgr *syncManager) resizeHandler(evt *config.Event) { func (mgr *syncManager) SyncData(ctx context.Context, task Task, callbacks ...func(error) error) (*conc.Future[struct{}], error) { if mgr.workerPool.IsClosed() { - return nil, errors.New("sync manager is closed") + return nil, merr.WrapErrServiceInternalMsg("sync manager is closed") } switch t := task.(type) { @@ -130,7 +130,7 @@ func (mgr *syncManager) SyncData(ctx context.Context, task Task, callbacks ...fu func (mgr *syncManager) SyncDataWithChunkManager(ctx context.Context, task Task, chunkManager storage.ChunkManager, callbacks ...func(error) error) (*conc.Future[struct{}], error) { if mgr.workerPool.IsClosed() { - return nil, errors.New("sync manager is closed") + return nil, merr.WrapErrServiceInternalMsg("sync manager is closed") } switch t := task.(type) { diff --git a/internal/flushcommon/writebuffer/insert_buffer.go b/internal/flushcommon/writebuffer/insert_buffer.go index 2b09b77a95..42f238020f 100644 --- a/internal/flushcommon/writebuffer/insert_buffer.go +++ b/internal/flushcommon/writebuffer/insert_buffer.go @@ -3,13 +3,13 @@ package writebuffer import ( "math" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus-proto/go-api/v3/msgpb" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/storage" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -86,7 +86,7 @@ func NewInsertBuffer(sch *schemapb.CollectionSchema) (*InsertBuffer, error) { } if estSize == 0 { - return nil, errors.New("Invalid schema") + return nil, merr.WrapErrParameterInvalidMsg("Invalid schema") } sizeLimit := paramtable.Get().DataNodeCfg.FlushInsertBufferSize.GetAsInt64() diff --git a/internal/flushcommon/writebuffer/write_buffer.go b/internal/flushcommon/writebuffer/write_buffer.go index bfdd4ccbe4..1ed3039795 100644 --- a/internal/flushcommon/writebuffer/write_buffer.go +++ b/internal/flushcommon/writebuffer/write_buffer.go @@ -1359,7 +1359,12 @@ func (wb *writeBufferBase) getSyncTask(ctx context.Context, segmentID int64) (sy WithSchema(schema). WithSyncPack(pack). WithStorageConfig(packed.CreateStorageConfig()). - WithWriteRetryOptions(retry.AttemptAlways(), retry.MaxSleepTime(10*time.Second)) + // The flush write path must keep retrying: aborting surfaces the error + // to SyncTask.HandleError, whose default callback panics the datanode. + // retry.Do short-circuits InputError-typed errors unless an explicit + // RetryErr predicate is supplied, so AttemptAlways alone is not enough. + WithWriteRetryOptions(retry.AttemptAlways(), retry.MaxSleepTime(10*time.Second), + retry.RetryErr(func(error) bool { return true })) return task, nil } @@ -1422,7 +1427,10 @@ func (wb *writeBufferBase) getGrowingSourceSyncTask(ctx context.Context, segment WithMetaWriter(wb.metaWriter). WithSchema(wb.metaCache.GetSchema(schemaTimestamp)). WithAllocator(wb.allocator). - WithWriteRetryOptions(retry.AttemptAlways(), retry.MaxSleepTime(10*time.Second)) + // Same as above: keep the critical write path retrying despite the + // retry.Do InputError short-circuit. + WithWriteRetryOptions(retry.AttemptAlways(), retry.MaxSleepTime(10*time.Second), + retry.RetryErr(func(error) bool { return true })) if source != nil { task.WithSource(source) } @@ -1643,7 +1651,7 @@ func getBM25OutputFieldIDs(schema *schemapb.CollectionSchema) ([]int64, error) { outputField := typeutil.GetFunctionOutputField(schema, fn) if outputField == nil { - return nil, fmt.Errorf("function %s output field not found", fn.GetName()) + return nil, merr.WrapErrFunctionFailedMsg("function %s output field not found", fn.GetName()) } outputFieldIDs = append(outputFieldIDs, outputField.GetFieldID()) @@ -1655,12 +1663,12 @@ func appendBM25StatsFromInsertData(stats map[int64]*storage.BM25Stats, outputFie for _, outputFieldID := range outputFieldIDs { outputData, ok := data.Data[outputFieldID] if !ok { - return fmt.Errorf("BM25 output field %d not found in insert data", outputFieldID) + return merr.WrapErrFunctionFailedMsg("BM25 output field %d not found in insert data", outputFieldID) } sparseData, ok := outputData.(*storage.SparseFloatVectorFieldData) if !ok { - return fmt.Errorf("BM25 output field %d is not sparse vector data", outputFieldID) + return merr.WrapErrFunctionFailedMsg("BM25 output field %d is not sparse vector data", outputFieldID) } if _, ok := stats[outputFieldID]; !ok { diff --git a/internal/http/server.go b/internal/http/server.go index f471a2b35c..d2ea42d812 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -34,6 +34,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/eventlog" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/util/expr" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -331,22 +332,22 @@ func checkExprRootAuth(req *http.Request) error { } if !ok || username == "" || password == "" { - return fmt.Errorf("authentication required. Use HTTP Basic Auth with root credentials") + return merr.WrapErrParameterInvalidMsg("authentication required. Use HTTP Basic Auth with root credentials") } // Only root user can access /expr if username != "root" { log.Warn("non-root user attempted to access /expr", zap.String("username", username)) - return fmt.Errorf("only root user can access /expr endpoint") + return merr.WrapErrParameterInvalidMsg("only root user can access /expr endpoint") } // Verify root password if passwordVerifyFunc == nil { - return fmt.Errorf("password verification not available") + return merr.WrapErrServiceInternalMsg("password verification not available") } if !passwordVerifyFunc(context.Background(), username, password) { log.Warn("invalid root password for /expr access") - return fmt.Errorf("invalid root password") + return merr.WrapErrParameterInvalidMsg("invalid root password") } log.Info("root user authenticated for /expr access") diff --git a/internal/kv/etcd/embed_etcd_kv.go b/internal/kv/etcd/embed_etcd_kv.go index 29aed6a772..530c10a52a 100644 --- a/internal/kv/etcd/embed_etcd_kv.go +++ b/internal/kv/etcd/embed_etcd_kv.go @@ -22,7 +22,6 @@ import ( "sync" "time" - "github.com/cockroachdb/errors" "github.com/samber/lo" clientv3 "go.etcd.io/etcd/client/v3" "go.etcd.io/etcd/server/v3/embed" @@ -100,7 +99,7 @@ func NewEmbededEtcdKV(cfg *embed.Config, rootPath string, options ...Option) (*E return nil case <-time.After(60 * time.Second): e.Server.Stop() // trigger a shutdown - return errors.New("Embedded etcd took too long to start") + return merr.WrapErrServiceInternalMsg("Embedded etcd took too long to start") } }) if err != nil { @@ -139,7 +138,7 @@ func (kv *EmbedEtcdKV) WalkWithPrefix(ctx context.Context, prefix string, pagina for { resp, err := kv.client.Get(ctx1, key, opts...) if err != nil { - return err + return merr.WrapErrIoFailed(key, err) } for _, kv := range resp.Kvs { @@ -166,7 +165,7 @@ func (kv *EmbedEtcdKV) LoadWithPrefix(ctx context.Context, key string) ([]string resp, err := kv.client.Get(ctx1, key, clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByKey, clientv3.SortAscend)) if err != nil { - return nil, nil, err + return nil, nil, merr.WrapErrIoFailed(key, err) } keys := make([]string, 0, resp.Count) @@ -185,7 +184,7 @@ func (kv *EmbedEtcdKV) Has(ctx context.Context, key string) (bool, error) { defer cancel() resp, err := kv.client.Get(ctx1, key, clientv3.WithCountOnly()) if err != nil { - return false, err + return false, merr.WrapErrIoFailed(key, err) } return resp.Count != 0, nil } @@ -199,7 +198,7 @@ func (kv *EmbedEtcdKV) HasPrefix(ctx context.Context, prefix string) (bool, erro resp, err := kv.client.Get(ctx1, prefix, clientv3.WithPrefix(), clientv3.WithCountOnly(), clientv3.WithLimit(1)) if err != nil { - return false, err + return false, merr.WrapErrIoFailed(prefix, err) } return resp.Count != 0, nil @@ -214,7 +213,7 @@ func (kv *EmbedEtcdKV) LoadBytesWithPrefix(ctx context.Context, key string) ([]s resp, err := kv.client.Get(ctx1, key, clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByKey, clientv3.SortAscend)) if err != nil { - return nil, nil, err + return nil, nil, merr.WrapErrIoFailed(key, err) } keys := make([]string, 0, resp.Count) values := make([][]byte, 0, resp.Count) @@ -234,7 +233,7 @@ func (kv *EmbedEtcdKV) LoadBytesWithPrefix2(ctx context.Context, key string) ([] resp, err := kv.client.Get(ctx1, key, clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByKey, clientv3.SortAscend)) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, merr.WrapErrIoFailed(key, err) } keys := make([]string, 0, resp.Count) values := make([][]byte, 0, resp.Count) @@ -254,7 +253,7 @@ func (kv *EmbedEtcdKV) Load(ctx context.Context, key string) (string, error) { defer cancel() resp, err := kv.client.Get(ctx1, key) if err != nil { - return "", err + return "", merr.WrapErrIoFailed(key, err) } if resp.Count <= 0 { return "", merr.WrapErrIoKeyNotFound(key) @@ -270,7 +269,7 @@ func (kv *EmbedEtcdKV) LoadBytes(ctx context.Context, key string) ([]byte, error defer cancel() resp, err := kv.client.Get(ctx1, key) if err != nil { - return nil, err + return nil, merr.WrapErrIoFailed(key, err) } if resp.Count <= 0 { return nil, merr.WrapErrIoKeyNotFound(key) @@ -290,7 +289,7 @@ func (kv *EmbedEtcdKV) MultiLoad(ctx context.Context, keys []string) ([]string, defer cancel() resp, err := kv.client.Txn(ctx1).If().Then(ops...).Commit() if err != nil { - return nil, err + return nil, merr.WrapErrIoFailedReason("failed to execute transaction", err.Error()) } result := make([]string, 0, len(keys)) @@ -309,7 +308,7 @@ func (kv *EmbedEtcdKV) MultiLoad(ctx context.Context, keys []string) ([]string, if len(invalid) != 0 { log.Ctx(ctx).Debug("MultiLoad: there are invalid keys", zap.Strings("keys", invalid)) - err = fmt.Errorf("there are invalid keys: %s", invalid) + err = merr.WrapErrIoKeyNotFound(fmt.Sprintf("%v", invalid)) return result, err } return result, nil @@ -326,7 +325,7 @@ func (kv *EmbedEtcdKV) MultiLoadBytes(ctx context.Context, keys []string) ([][]b defer cancel() resp, err := kv.client.Txn(ctx1).If().Then(ops...).Commit() if err != nil { - return nil, err + return nil, merr.WrapErrIoFailedReason("failed to execute transaction", err.Error()) } result := make([][]byte, 0, len(keys)) @@ -345,7 +344,7 @@ func (kv *EmbedEtcdKV) MultiLoadBytes(ctx context.Context, keys []string) ([][]b if len(invalid) != 0 { log.Ctx(ctx).Debug("MultiLoadBytes: there are invalid keys", zap.Strings("keys", invalid)) - err = fmt.Errorf("there are invalid keys: %s", invalid) + err = merr.WrapErrIoKeyNotFound(fmt.Sprintf("%v", invalid)) return result, err } return result, nil @@ -360,7 +359,7 @@ func (kv *EmbedEtcdKV) LoadBytesWithRevision(ctx context.Context, key string) ([ resp, err := kv.client.Get(ctx1, key, clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByKey, clientv3.SortAscend)) if err != nil { - return nil, nil, 0, err + return nil, nil, 0, merr.WrapErrIoFailed(key, err) } keys := make([]string, 0, resp.Count) values := make([][]byte, 0, resp.Count) @@ -377,7 +376,7 @@ func (kv *EmbedEtcdKV) Save(ctx context.Context, key, value string) error { ctx1, cancel := getContextWithTimeout(ctx, kv.requestTimeout) defer cancel() _, err := kv.client.Put(ctx1, key, value) - return err + return merr.WrapErrIoFailed(key, err) } // SaveBytes saves the key-value pair. @@ -386,7 +385,7 @@ func (kv *EmbedEtcdKV) SaveBytes(ctx context.Context, key string, value []byte) ctx1, cancel := getContextWithTimeout(ctx, kv.requestTimeout) defer cancel() _, err := kv.client.Put(ctx1, key, string(value)) - return err + return merr.WrapErrIoFailed(key, err) } // SaveBytesWithLease is a function to put value in etcd with etcd lease options. @@ -395,7 +394,7 @@ func (kv *EmbedEtcdKV) SaveBytesWithLease(ctx context.Context, key string, value ctx1, cancel := getContextWithTimeout(ctx, kv.requestTimeout) defer cancel() _, err := kv.client.Put(ctx1, key, string(value), clientv3.WithLease(id)) - return err + return merr.WrapErrIoFailed(key, err) } // MultiSave saves the key-value pairs in a transaction. @@ -409,7 +408,10 @@ func (kv *EmbedEtcdKV) MultiSave(ctx context.Context, kvs map[string]string) err defer cancel() _, err := kv.client.Txn(ctx1).If().Then(ops...).Commit() - return err + if err != nil { + return merr.WrapErrIoFailedReason("failed to execute transaction", err.Error()) + } + return nil } // MultiSaveBytes saves the key-value pairs in a transaction. @@ -423,7 +425,10 @@ func (kv *EmbedEtcdKV) MultiSaveBytes(ctx context.Context, kvs map[string][]byte defer cancel() _, err := kv.client.Txn(ctx1).If().Then(ops...).Commit() - return err + if err != nil { + return merr.WrapErrIoFailedReason("failed to execute transaction", err.Error()) + } + return nil } // RemoveWithPrefix removes the keys with given prefix. @@ -433,7 +438,7 @@ func (kv *EmbedEtcdKV) RemoveWithPrefix(ctx context.Context, prefix string) erro defer cancel() _, err := kv.client.Delete(ctx1, key, clientv3.WithPrefix()) - return err + return merr.WrapErrIoFailed(key, err) } // Remove removes the key. @@ -443,7 +448,7 @@ func (kv *EmbedEtcdKV) Remove(ctx context.Context, key string) error { defer cancel() _, err := kv.client.Delete(ctx1, key) - return err + return merr.WrapErrIoFailed(key, err) } // MultiRemove removes the keys in a transaction. @@ -457,7 +462,10 @@ func (kv *EmbedEtcdKV) MultiRemove(ctx context.Context, keys []string) error { defer cancel() _, err := kv.client.Txn(ctx1).If().Then(ops...).Commit() - return err + if err != nil { + return merr.WrapErrIoFailedReason("failed to execute transaction", err.Error()) + } + return nil } // MultiSaveAndRemove saves the key-value pairs and removes the keys in a transaction. @@ -486,7 +494,7 @@ func (kv *EmbedEtcdKV) MultiSaveAndRemove(ctx context.Context, saves map[string] resp, err := kv.client.Txn(ctx1).If(cmps...).Then(ops...).Commit() if err != nil { - return err + return merr.WrapErrIoFailedReason("failed to execute transaction", err.Error()) } if !resp.Succeeded { @@ -511,7 +519,10 @@ func (kv *EmbedEtcdKV) MultiSaveBytesAndRemove(ctx context.Context, saves map[st defer cancel() _, err := kv.client.Txn(ctx1).If().Then(ops...).Commit() - return err + if err != nil { + return merr.WrapErrIoFailedReason("failed to execute transaction", err.Error()) + } + return nil } func (kv *EmbedEtcdKV) Watch(ctx context.Context, key string) clientv3.WatchChan { @@ -553,7 +564,7 @@ func (kv *EmbedEtcdKV) MultiSaveAndRemoveWithPrefix(ctx context.Context, saves m resp, err := kv.client.Txn(ctx1).If(cmps...).Then(ops...).Commit() if err != nil { - return err + return merr.WrapErrIoFailedReason("failed to execute transaction", err.Error()) } if !resp.Succeeded { @@ -577,7 +588,10 @@ func (kv *EmbedEtcdKV) MultiSaveBytesAndRemoveWithPrefix(ctx context.Context, sa defer cancel() _, err := kv.client.Txn(ctx1).If().Then(ops...).Commit() - return err + if err != nil { + return merr.WrapErrIoFailedReason("failed to execute transaction", err.Error()) + } + return nil } // CompareVersionAndSwap compares the existing key-value's version with version, and if @@ -592,7 +606,7 @@ func (kv *EmbedEtcdKV) CompareVersionAndSwap(ctx context.Context, key string, ve version)). Then(clientv3.OpPut(kv.GetPath(key), target)).Commit() if err != nil { - return false, err + return false, merr.WrapErrIoFailed(key, err) } return resp.Succeeded, nil } @@ -609,7 +623,7 @@ func (kv *EmbedEtcdKV) CompareVersionAndSwapBytes(ctx context.Context, key strin version)). Then(clientv3.OpPut(kv.GetPath(key), string(target), opts...)).Commit() if err != nil { - return false, err + return false, merr.WrapErrIoFailed(key, err) } return resp.Succeeded, nil } diff --git a/internal/kv/etcd/etcd_kv.go b/internal/kv/etcd/etcd_kv.go index 93feca9f1a..6abfb32356 100644 --- a/internal/kv/etcd/etcd_kv.go +++ b/internal/kv/etcd/etcd_kv.go @@ -274,7 +274,7 @@ func (kv *etcdKV) MultiLoad(ctx context.Context, keys []string) ([]string, error } if len(invalid) != 0 { log.Ctx(ctx).Warn("MultiLoad: there are invalid keys", zap.Strings("keys", invalid)) - err = fmt.Errorf("there are invalid keys: %s", invalid) + err = merr.WrapErrIoKeyNotFound(fmt.Sprintf("%v", invalid)) return result, err } CheckElapseAndWarn(ctx, start, "Slow etcd operation multi load", zap.Any("keys", keys)) @@ -309,7 +309,7 @@ func (kv *etcdKV) MultiLoadBytes(ctx context.Context, keys []string) ([][]byte, } if len(invalid) != 0 { log.Ctx(ctx).Warn("MultiLoad: there are invalid keys", zap.Strings("keys", invalid)) - err = fmt.Errorf("there are invalid keys: %s", invalid) + err = merr.WrapErrIoKeyNotFound(fmt.Sprintf("%v", invalid)) return result, err } CheckElapseAndWarn(ctx, start, "Slow etcd operation multi load", zap.Strings("keys", keys)) @@ -721,7 +721,10 @@ func (kv *etcdKV) getEtcdMeta(ctx context.Context, key string, opts ...clientv3. } else { metrics.MetaOpCounter.WithLabelValues(metrics.MetaGetLabel, metrics.FailLabel).Inc() } - return resp, err + // translate the raw etcd/grpc transport error into a typed merr at this + // boundary so callers never receive an untyped error (key-not-found is + // classified by the callers via resp.Count). + return resp, merr.WrapErrIoFailed(key, err) } func (kv *etcdKV) putEtcdMeta(ctx context.Context, key, val string, opts ...clientv3.OpOption) (*clientv3.PutResponse, error) { @@ -740,7 +743,7 @@ func (kv *etcdKV) putEtcdMeta(ctx context.Context, key, val string, opts ...clie metrics.MetaOpCounter.WithLabelValues(metrics.MetaPutLabel, metrics.FailLabel).Inc() } - return resp, err + return resp, merr.WrapErrIoFailed(key, err) } func (kv *etcdKV) removeEtcdMeta(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.DeleteResponse, error) { @@ -759,7 +762,7 @@ func (kv *etcdKV) removeEtcdMeta(ctx context.Context, key string, opts ...client metrics.MetaOpCounter.WithLabelValues(metrics.MetaRemoveLabel, metrics.FailLabel).Inc() } - return resp, err + return resp, merr.WrapErrIoFailed(key, err) } func (kv *etcdKV) getTxnWithCmp(ctx context.Context, cmp ...clientv3.Cmp) clientv3.Txn { @@ -799,5 +802,8 @@ func (kv *etcdKV) executeTxn(txn clientv3.Txn, ops ...clientv3.Op) (*clientv3.Tx metrics.MetaOpCounter.WithLabelValues(metrics.MetaTxnLabel, metrics.FailLabel).Inc() } + if err != nil { + err = merr.WrapErrIoFailedReason("execute etcd txn failed", err.Error()) + } return resp, err } diff --git a/internal/kv/tikv/txn_tikv.go b/internal/kv/tikv/txn_tikv.go index 682fea02ef..56e88676e0 100644 --- a/internal/kv/tikv/txn_tikv.go +++ b/internal/kv/tikv/txn_tikv.go @@ -155,7 +155,7 @@ func (kv *txnTiKV) Has(ctx context.Context, key string) (bool, error) { if errors.Is(err, merr.ErrIoKeyNotFound) { return false, nil } - loggingErr = errors.Wrap(err, fmt.Sprintf("Failed to read key: %s", key)) + loggingErr = merr.WrapErrIoFailedReason(fmt.Sprintf("Failed to read key: %s", key), err.Error()) return false, loggingErr } CheckElapseAndWarn(start, "Slow txnTiKV Has() operation", zap.String("key", key)) @@ -184,7 +184,7 @@ func (kv *txnTiKV) HasPrefix(ctx context.Context, prefix string) (bool, error) { iter, err := ss.Iter(startKey, endKey) if err != nil { - loggingErr = errors.Wrap(err, fmt.Sprintf("Failed to create iterator for prefix: %s", prefix)) + loggingErr = merr.WrapErrIoFailedReason(fmt.Sprintf("Failed to create iterator for prefix: %s", prefix), err.Error()) return false, loggingErr } defer iter.Close() @@ -211,7 +211,7 @@ func (kv *txnTiKV) Load(ctx context.Context, key string) (string, error) { if errors.Is(err, merr.ErrIoKeyNotFound) { loggingErr = err } else { - loggingErr = errors.Wrap(err, fmt.Sprintf("Failed to read key %s", key)) + loggingErr = merr.WrapErrIoFailedReason(fmt.Sprintf("Failed to read key %s", key), err.Error()) } return "", loggingErr } @@ -245,7 +245,7 @@ func (kv *txnTiKV) MultiLoad(ctx context.Context, keys []string) ([]string, erro keyMap, err := ss.BatchGet(ctx, byteKeys) if err != nil { - loggingErr = errors.Wrap(err, "Failed ss.BatchGet() for MultiLoad") + loggingErr = merr.WrapErrIoFailedReason("Failed ss.BatchGet() for MultiLoad", err.Error()) return nil, loggingErr } @@ -263,7 +263,7 @@ func (kv *txnTiKV) MultiLoad(ctx context.Context, keys []string) ([]string, erro validValues = append(validValues, strVal) } if len(missingValues) != 0 { - loggingErr = fmt.Errorf("there are invalid keys: %s", missingValues) + loggingErr = merr.WrapErrIoKeyNotFound(fmt.Sprintf("%v", missingValues)) } CheckElapseAndWarn(start, "Slow txnTiKV MultiLoad() operation", zap.Any("keys", keys)) @@ -285,7 +285,7 @@ func (kv *txnTiKV) LoadWithPrefix(ctx context.Context, prefix string) ([]string, endKey := tikv.PrefixNextKey([]byte(prefix)) iter, err := ss.Iter(startKey, endKey) if err != nil { - loggingErr = errors.Wrap(err, fmt.Sprintf("Failed to create iterater for LoadWithPrefix() for prefix: %s", prefix)) + loggingErr = merr.WrapErrIoFailedReason(fmt.Sprintf("Failed to create iterater for LoadWithPrefix() for prefix: %s", prefix), err.Error()) return nil, nil, loggingErr } defer iter.Close() @@ -302,7 +302,7 @@ func (kv *txnTiKV) LoadWithPrefix(ctx context.Context, prefix string) ([]string, values = append(values, strVal) err = iter.Next() if err != nil { - loggingErr = errors.Wrap(err, fmt.Sprintf("Failed to iterate for LoadWithPrefix() for prefix: %s", prefix)) + loggingErr = merr.WrapErrIoFailedReason(fmt.Sprintf("Failed to iterate for LoadWithPrefix() for prefix: %s", prefix), err.Error()) return nil, nil, loggingErr } } @@ -334,7 +334,7 @@ func (kv *txnTiKV) MultiSave(ctx context.Context, kvs map[string]string) error { txn, err := beginTxn(kv.txn) if err != nil { - loggingErr = errors.Wrap(err, "Failed to create txn for MultiSave") + loggingErr = merr.WrapErrIoFailedReason("Failed to create txn for MultiSave", err.Error()) return loggingErr } @@ -346,19 +346,19 @@ func (kv *txnTiKV) MultiSave(ctx context.Context, kvs map[string]string) error { // Check if value is empty or taking reserved EmptyValue byteValue, err := convertEmptyStringToByte(value) if err != nil { - loggingErr = errors.Wrap(err, fmt.Sprintf("Failed to cast to byte (%s:%s) for MultiSave()", key, value)) + loggingErr = merr.Wrap(err, fmt.Sprintf("Failed to cast to byte (%s:%s) for MultiSave()", key, value)) return loggingErr } // Save the value within a transaction err = txn.Set([]byte(key), byteValue) if err != nil { - loggingErr = errors.Wrap(err, fmt.Sprintf("Failed to set (%s:%s) for MultiSave()", key, value)) + loggingErr = merr.WrapErrIoFailedReason(fmt.Sprintf("Failed to set (%s:%s) for MultiSave()", key, value), err.Error()) return loggingErr } } err = kv.executeTxn(ctx, txn) if err != nil { - loggingErr = errors.Wrap(err, "Failed to commit for MultiSave()") + loggingErr = merr.WrapErrIoFailedReason("Failed to commit for MultiSave()", err.Error()) return loggingErr } CheckElapseAndWarn(start, "Slow txnTiKV MultiSave() operation", zap.Any("kvs", kvs)) @@ -389,7 +389,7 @@ func (kv *txnTiKV) MultiRemove(ctx context.Context, keys []string) error { txn, err := beginTxn(kv.txn) if err != nil { - loggingErr = errors.Wrap(err, "Failed to create txn for MultiRemove") + loggingErr = merr.WrapErrIoFailedReason("Failed to create txn for MultiRemove", err.Error()) return loggingErr } @@ -400,14 +400,14 @@ func (kv *txnTiKV) MultiRemove(ctx context.Context, keys []string) error { key = kv.GetPath(key) err = txn.Delete([]byte(key)) if err != nil { - loggingErr = errors.Wrap(err, fmt.Sprintf("Failed to delete %s for MultiRemove", key)) + loggingErr = merr.WrapErrIoFailedReason(fmt.Sprintf("Failed to delete %s for MultiRemove", key), err.Error()) return loggingErr } } err = kv.executeTxn(ctx, txn) if err != nil { - loggingErr = errors.Wrap(err, "Failed to commit for MultiRemove()") + loggingErr = merr.WrapErrIoFailedReason("Failed to commit for MultiRemove()", err.Error()) return loggingErr } CheckElapseAndWarn(start, "Slow txnTiKV MultiRemove() operation", zap.Strings("keys", keys)) @@ -428,7 +428,7 @@ func (kv *txnTiKV) RemoveWithPrefix(ctx context.Context, prefix string) error { endKey := tikv.PrefixNextKey(startKey) _, err := kv.txn.DeleteRange(ctx, startKey, endKey, 1) if err != nil { - loggingErr = errors.Wrap(err, "Failed to DeleteRange for RemoveWithPrefix") + loggingErr = merr.WrapErrIoFailedReason("Failed to DeleteRange for RemoveWithPrefix", err.Error()) return loggingErr } CheckElapseAndWarn(start, "Slow txnTiKV RemoveWithPrefix() operation", zap.String("prefix", prefix)) @@ -446,7 +446,7 @@ func (kv *txnTiKV) MultiSaveAndRemove(ctx context.Context, saves map[string]stri txn, err := beginTxn(kv.txn) if err != nil { - loggingErr = errors.Wrap(err, "Failed to create txn for MultiSaveAndRemove") + loggingErr = merr.WrapErrIoFailedReason("Failed to create txn for MultiSaveAndRemove", err.Error()) return loggingErr } @@ -457,7 +457,7 @@ func (kv *txnTiKV) MultiSaveAndRemove(ctx context.Context, saves map[string]stri key := kv.GetPath(pred.Key()) val, err := txn.Get(ctx, []byte(key)) if err != nil { - loggingErr = errors.Wrap(err, fmt.Sprintf("failed to read predicate target (%s:%v) for MultiSaveAndRemove", pred.Key(), pred.TargetValue())) + loggingErr = merr.WrapErrIoFailedReason(fmt.Sprintf("failed to read predicate target (%s:%v) for MultiSaveAndRemove", pred.Key(), pred.TargetValue()), err.Error()) return loggingErr } if !pred.IsTrue(val) { @@ -474,7 +474,7 @@ func (kv *txnTiKV) MultiSaveAndRemove(ctx context.Context, saves map[string]stri for _, key := range removals { key = kv.GetPath(key) if err = txn.Delete([]byte(key)); err != nil { - loggingErr = errors.Wrap(err, fmt.Sprintf("Failed to delete %s for MultiSaveAndRemove", key)) + loggingErr = merr.WrapErrIoFailedReason(fmt.Sprintf("Failed to delete %s for MultiSaveAndRemove", key), err.Error()) return loggingErr } } @@ -484,19 +484,19 @@ func (kv *txnTiKV) MultiSaveAndRemove(ctx context.Context, saves map[string]stri // Check if value is empty or taking reserved EmptyValue byteValue, err := convertEmptyStringToByte(value) if err != nil { - loggingErr = errors.Wrap(err, fmt.Sprintf("Failed to cast to byte (%s:%s) for MultiSaveAndRemove", key, value)) + loggingErr = merr.Wrap(err, fmt.Sprintf("Failed to cast to byte (%s:%s) for MultiSaveAndRemove", key, value)) return loggingErr } err = txn.Set([]byte(key), byteValue) if err != nil { - loggingErr = errors.Wrap(err, fmt.Sprintf("Failed to set (%s:%s) for MultiSaveAndRemove", key, value)) + loggingErr = merr.WrapErrIoFailedReason(fmt.Sprintf("Failed to set (%s:%s) for MultiSaveAndRemove", key, value), err.Error()) return loggingErr } } err = kv.executeTxn(ctx, txn) if err != nil { - loggingErr = errors.Wrap(err, "Failed to commit for MultiSaveAndRemove") + loggingErr = merr.WrapErrIoFailedReason("Failed to commit for MultiSaveAndRemove", err.Error()) return loggingErr } CheckElapseAndWarn(start, "Slow txnTiKV MultiSaveAndRemove() operation", zap.Any("saves", saves), zap.Strings("removals", removals)) @@ -514,7 +514,7 @@ func (kv *txnTiKV) MultiSaveAndRemoveWithPrefix(ctx context.Context, saves map[s txn, err := beginTxn(kv.txn) if err != nil { - loggingErr = errors.Wrap(err, "Failed to create txn for MultiSaveAndRemoveWithPrefix") + loggingErr = merr.WrapErrIoFailedReason("Failed to create txn for MultiSaveAndRemoveWithPrefix", err.Error()) return loggingErr } @@ -525,7 +525,7 @@ func (kv *txnTiKV) MultiSaveAndRemoveWithPrefix(ctx context.Context, saves map[s key := kv.GetPath(pred.Key()) val, err := txn.Get(ctx, []byte(key)) if err != nil { - loggingErr = errors.Wrap(err, fmt.Sprintf("failed to read predicate target (%s:%v) for MultiSaveAndRemove", pred.Key(), pred.TargetValue())) + loggingErr = merr.WrapErrIoFailedReason(fmt.Sprintf("failed to read predicate target (%s:%v) for MultiSaveAndRemove", pred.Key(), pred.TargetValue()), err.Error()) return loggingErr } if !pred.IsTrue(val) { @@ -544,7 +544,7 @@ func (kv *txnTiKV) MultiSaveAndRemoveWithPrefix(ctx context.Context, saves map[s // Use Scan to iterate over keys in the prefix range iter, err := txn.Iter(startKey, endKey) if err != nil { - loggingErr = errors.Wrap(err, fmt.Sprintf("Failed to create iterater for %s during MultiSaveAndRemoveWithPrefix()", prefix)) + loggingErr = merr.WrapErrIoFailedReason(fmt.Sprintf("Failed to create iterater for %s during MultiSaveAndRemoveWithPrefix()", prefix), err.Error()) return loggingErr } @@ -553,14 +553,14 @@ func (kv *txnTiKV) MultiSaveAndRemoveWithPrefix(ctx context.Context, saves map[s key := iter.Key() err = txn.Delete(key) if loggingErr != nil { - loggingErr = errors.Wrap(err, fmt.Sprintf("Failed to delete %s for MultiSaveAndRemoveWithPrefix", string(key))) + loggingErr = merr.WrapErrIoFailedReason(fmt.Sprintf("Failed to delete %s for MultiSaveAndRemoveWithPrefix", string(key)), err.Error()) return loggingErr } // Move the iterator to the next key err = iter.Next() if err != nil { - loggingErr = errors.Wrap(err, fmt.Sprintf("Failed to move Iterator after key %s for MultiSaveAndRemoveWithPrefix", string(key))) + loggingErr = merr.WrapErrIoFailedReason(fmt.Sprintf("Failed to move Iterator after key %s for MultiSaveAndRemoveWithPrefix", string(key)), err.Error()) return loggingErr } } @@ -572,19 +572,19 @@ func (kv *txnTiKV) MultiSaveAndRemoveWithPrefix(ctx context.Context, saves map[s // Check if value is empty or taking reserved EmptyValue byteValue, err := convertEmptyStringToByte(value) if err != nil { - loggingErr = errors.Wrap(err, fmt.Sprintf("Failed to cast to byte (%s:%s) for MultiSaveAndRemoveWithPrefix()", key, value)) + loggingErr = merr.Wrap(err, fmt.Sprintf("Failed to cast to byte (%s:%s) for MultiSaveAndRemoveWithPrefix()", key, value)) return loggingErr } err = txn.Set([]byte(key), byteValue) if err != nil { - loggingErr = errors.Wrap(err, fmt.Sprintf("Failed to set (%s:%s) for MultiSaveAndRemoveWithPrefix()", key, value)) + loggingErr = merr.WrapErrIoFailedReason(fmt.Sprintf("Failed to set (%s:%s) for MultiSaveAndRemoveWithPrefix()", key, value), err.Error()) return loggingErr } } err = kv.executeTxn(ctx, txn) if err != nil { - loggingErr = errors.Wrap(err, "Failed to commit for MultiSaveAndRemoveWithPrefix") + loggingErr = merr.WrapErrIoFailedReason("Failed to commit for MultiSaveAndRemoveWithPrefix", err.Error()) return loggingErr } CheckElapseAndWarn(start, "Slow txnTiKV MultiSaveAndRemoveWithPrefix() operation", zap.Any("saves", saves), zap.Strings("removals", removals)) @@ -607,7 +607,7 @@ func (kv *txnTiKV) WalkWithPrefix(ctx context.Context, prefix string, pagination endKey := tikv.PrefixNextKey([]byte(prefix)) iter, err := ss.Iter(startKey, endKey) if err != nil { - loggingErr = errors.Wrap(err, fmt.Sprintf("Failed to create iterater for %s during WalkWithPrefix", prefix)) + loggingErr = merr.WrapErrIoFailedReason(fmt.Sprintf("Failed to create iterater for %s during WalkWithPrefix", prefix), err.Error()) return loggingErr } defer iter.Close() @@ -622,12 +622,12 @@ func (kv *txnTiKV) WalkWithPrefix(ctx context.Context, prefix string, pagination } err = fn(iter.Key(), byteVal) if err != nil { - loggingErr = errors.Wrap(err, fmt.Sprintf("Failed to apply fn to (%s;%s)", string(iter.Key()), string(byteVal))) + loggingErr = merr.Wrap(err, fmt.Sprintf("Failed to apply fn to (%s;%s)", string(iter.Key()), string(byteVal))) return loggingErr } err = iter.Next() if err != nil { - loggingErr = errors.Wrap(err, fmt.Sprintf("Failed to move Iterator after key %s for WalkWithPrefix", string(iter.Key()))) + loggingErr = merr.WrapErrIoFailedReason(fmt.Sprintf("Failed to move Iterator after key %s for WalkWithPrefix", string(iter.Key())), err.Error()) return loggingErr } } @@ -668,7 +668,7 @@ func (kv *txnTiKV) getTiKVMeta(ctx context.Context, key string) (string, error) return "", merr.WrapErrIoKeyNotFound(key) } // If call to tikv fails - return "", errors.Wrap(err, fmt.Sprintf("Failed to get value for key %s in getTiKVMeta", key)) + return "", merr.WrapErrIoFailedReason(fmt.Sprintf("Failed to get value for key %s in getTiKVMeta", key), err.Error()) } // Check if value is the empty placeholder @@ -692,7 +692,7 @@ func (kv *txnTiKV) putTiKVMeta(ctx context.Context, key, val string) error { txn, err := beginTxn(kv.txn) if err != nil { - return errors.Wrap(err, "Failed to build transaction for putTiKVMeta") + return merr.WrapErrIoFailedReason("Failed to build transaction for putTiKVMeta", err.Error()) } // Defer a rollback only if the transaction hasn't been committed defer rollbackOnFailure(&err, txn) @@ -700,11 +700,11 @@ func (kv *txnTiKV) putTiKVMeta(ctx context.Context, key, val string) error { // Check if the value being written needs to be empty placeholder byteValue, err := convertEmptyStringToByte(val) if err != nil { - return errors.Wrap(err, fmt.Sprintf("Failed to cast to byte (%s:%s) for putTiKVMeta", key, val)) + return merr.Wrap(err, fmt.Sprintf("Failed to cast to byte (%s:%s) for putTiKVMeta", key, val)) } err = txn.Set([]byte(key), byteValue) if err != nil { - return errors.Wrap(err, fmt.Sprintf("Failed to set value for key %s in putTiKVMeta", key)) + return merr.WrapErrIoFailedReason(fmt.Sprintf("Failed to set value for key %s in putTiKVMeta", key), err.Error()) } err = commitTxn(ctx1, txn) @@ -729,14 +729,14 @@ func (kv *txnTiKV) removeTiKVMeta(ctx context.Context, key string) error { txn, err := beginTxn(kv.txn) if err != nil { - return errors.Wrap(err, "Failed to build transaction for removeTiKVMeta") + return merr.WrapErrIoFailedReason("Failed to build transaction for removeTiKVMeta", err.Error()) } // Defer a rollback only if the transaction hasn't been committed defer rollbackOnFailure(&err, txn) err = txn.Delete([]byte(key)) if err != nil { - return errors.Wrap(err, fmt.Sprintf("Failed to remove key %s in removeTiKVMeta", key)) + return merr.WrapErrIoFailedReason(fmt.Sprintf("Failed to remove key %s in removeTiKVMeta", key), err.Error()) } err = commitTxn(ctx1, txn) @@ -754,7 +754,7 @@ func (kv *txnTiKV) removeTiKVMeta(ctx context.Context, key string) error { } func (kv *txnTiKV) CompareVersionAndSwap(ctx context.Context, key string, version int64, target string) (bool, error) { - err := errors.New("Unimplemented! CompareVersionAndSwap is under deprecation") + err := errors.Wrap(merr.ErrServiceUnimplemented, "CompareVersionAndSwap is under deprecation") logWarnOnFailure(&err, "Unimplemented") return false, err } @@ -789,7 +789,7 @@ func convertEmptyStringToByte(value string) ([]byte, error) { if len(value) == 0 { return EmptyValueByte, nil } else if value == EmptyValueString { - return nil, fmt.Errorf("value for key is reserved by EmptyValue: %s", EmptyValueString) + return nil, merr.WrapErrParameterInvalidMsg("value for key is reserved by EmptyValue: %s", EmptyValueString) } return []byte(value), nil } diff --git a/internal/metastore/kv/binlog/binlog.go b/internal/metastore/kv/binlog/binlog.go index 37583d6b53..1d5119e604 100644 --- a/internal/metastore/kv/binlog/binlog.go +++ b/internal/metastore/kv/binlog/binlog.go @@ -17,7 +17,6 @@ package binlog import ( - "fmt" "strconv" "strings" @@ -223,7 +222,7 @@ func GetLogIDFromBingLogPath(logPath string) (int64, error) { var logID int64 idx := strings.LastIndex(logPath, "/") if idx == -1 { - return 0, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("invalid binlog path: %s", logPath)) + return 0, merr.WrapErrParameterInvalidMsg("invalid binlog path: %s", logPath) } var err error logPathStr := logPath[(idx + 1):] diff --git a/internal/metastore/kv/datacoord/kv_catalog.go b/internal/metastore/kv/datacoord/kv_catalog.go index a87967138f..ba322cae68 100644 --- a/internal/metastore/kv/datacoord/kv_catalog.go +++ b/internal/metastore/kv/datacoord/kv_catalog.go @@ -162,11 +162,14 @@ func (kc *Catalog) parseBinlogKey(key string) (int64, error) { // ---------------------------------|collectionID |partitionID |segmentID |fieldID keyWordGroup := strings.Split(key, "/") if len(keyWordGroup) < 3 { - return 0, fmt.Errorf("parse key: %s failed, key:%s", key, key) + // A malformed key read back from the metastore during recovery is corrupt + // stored data, not a caller's bad parameter (cf. the unmarshal sibling + // below) — classify it as DataIntegrity. + return 0, merr.WrapErrDataIntegrityMsg("parse binlog key failed, key:%s", key) } segmentID, err := strconv.ParseInt(keyWordGroup[len(keyWordGroup)-2], 10, 64) if err != nil { - return 0, fmt.Errorf("parse key failed, key:%s, %w", key, err) + return 0, merr.WrapErrDataIntegrity(err, "parse binlog key failed, key:%s", key) } return segmentID, nil } @@ -186,7 +189,7 @@ func (kc *Catalog) listBinlogs(ctx context.Context, binlogType storage.BinlogTyp case storage.BM25Binlog: logPathPrefix = fmt.Sprintf("%s/%d", SegmentBM25logPathPrefix, collectionID) default: - err = fmt.Errorf("invalid binlog type: %d", binlogType) + err = merr.WrapErrServiceInternalMsg("invalid binlog type: %d", binlogType) } if err != nil { return nil, err @@ -196,12 +199,12 @@ func (kc *Catalog) listBinlogs(ctx context.Context, binlogType storage.BinlogTyp fieldBinlog := &datapb.FieldBinlog{} err := proto.Unmarshal(value, fieldBinlog) if err != nil { - return fmt.Errorf("failed to unmarshal datapb.FieldBinlog: %d, err:%w", fieldBinlog.FieldID, err) + return merr.WrapErrDataIntegrity(err, "failed to unmarshal datapb.FieldBinlog: %d", fieldBinlog.FieldID) } segmentID, err := kc.parseBinlogKey(string(key)) if err != nil { - return fmt.Errorf("prefix:%s, %w", kc.metaRootpath+"/"+logPathPrefix, err) + return merr.Wrapf(err, "prefix:%s", kc.metaRootpath+"/"+logPathPrefix) } // set log size to memory size if memory size is zero for old segment before v2.4.3 @@ -426,7 +429,7 @@ func (kc *Catalog) SaveDroppedSegmentsInBatch(ctx context.Context, segments []*d segmentutil.ReCalcRowCount(s, noBinlogsSegment) segBytes, err := marshalSegmentInfo(noBinlogsSegment) if err != nil { - return fmt.Errorf("failed to marshal segment: %d, err: %w", s.GetID(), err) + return merr.WrapErrSerializationFailed(err, "marshal segment: %d", s.GetID()) } kvs[key] = segBytes } @@ -563,7 +566,7 @@ func (kc *Catalog) getBinlogsWithPrefix(ctx context.Context, binlogType storage. case storage.StatsBinlog: binlogPrefix = buildFieldStatslogPathPrefix(collectionID, partitionID, segmentID) default: - return nil, nil, fmt.Errorf("invalid binlog type: %d", binlogType) + return nil, nil, merr.WrapErrServiceInternalMsg("invalid binlog type: %d", binlogType) } keys, values, err := kc.MetaKv.LoadWithPrefix(ctx, binlogPrefix) if err != nil { diff --git a/internal/metastore/kv/datacoord/util.go b/internal/metastore/kv/datacoord/util.go index 9a09f09141..179c6bb18d 100644 --- a/internal/metastore/kv/datacoord/util.go +++ b/internal/metastore/kv/datacoord/util.go @@ -27,6 +27,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/datapb" "github.com/milvus-io/milvus/pkg/v3/util" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/metautil" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -46,7 +47,7 @@ func ValidateSegment(segment *datapb.SegmentInfo) error { zap.Any("binlogs", segment.GetBinlogs()), zap.Any("stats", segment.GetBinlogs()), ) - return fmt.Errorf("segment can not be saved because of L0 segment get more than delta logs: collection %v, segment %v", + return merr.WrapErrServiceInternalMsg("segment can not be saved because of L0 segment get more than delta logs: collection %v, segment %v", segment.GetCollectionID(), segment.GetID()) } return nil @@ -62,7 +63,7 @@ func ValidateSegment(segment *datapb.SegmentInfo) error { zap.Any("binlogs", segment.GetBinlogs()), zap.Any("stats", segment.GetBinlogs()), ) - return fmt.Errorf("segment can not be saved because of binlog file or stat log file lack: collection %v, segment %v", + return merr.WrapErrServiceInternalMsg("segment can not be saved because of binlog file or stat log file lack: collection %v, segment %v", segment.GetCollectionID(), segment.GetID()) } @@ -76,7 +77,7 @@ func ValidateSegment(segment *datapb.SegmentInfo) error { zap.Any("binlogs", segment.GetBinlogs()), zap.Any("stats", segment.GetStatslogs()), ) - return fmt.Errorf("segment can not be saved because of binlog file not match stat log number: collection %v, segment %v", + return merr.WrapErrServiceInternalMsg("segment can not be saved because of binlog file not match stat log number: collection %v, segment %v", segment.GetCollectionID(), segment.GetID()) } @@ -147,10 +148,10 @@ func buildBinlogKvs(collectionID, partitionID, segmentID typeutil.UniqueID, binl checkLogID := func(fieldBinlog *datapb.FieldBinlog) error { for _, binlog := range fieldBinlog.GetBinlogs() { if binlog.GetLogID() == 0 { - return fmt.Errorf("invalid log id, binlog:%v", binlog) + return merr.WrapErrServiceInternalMsg("invalid log id, binlog:%v", binlog) } if binlog.GetLogPath() != "" { - return fmt.Errorf("fieldBinlog no need to store logpath, binlog:%v", binlog) + return merr.WrapErrServiceInternalMsg("fieldBinlog no need to store logpath, binlog:%v", binlog) } } return nil @@ -163,7 +164,7 @@ func buildBinlogKvs(collectionID, partitionID, segmentID typeutil.UniqueID, binl } binlogBytes, err := proto.Marshal(binlog) if err != nil { - return nil, fmt.Errorf("marshal binlogs failed, collectionID:%d, segmentID:%d, fieldID:%d, error:%w", collectionID, segmentID, binlog.FieldID, err) + return nil, merr.WrapErrSerializationFailed(err, "marshal binlogs failed, collectionID:%d, segmentID:%d, fieldID:%d", collectionID, segmentID, binlog.FieldID) } key := buildFieldBinlogPath(collectionID, partitionID, segmentID, binlog.FieldID) kv[key] = string(binlogBytes) @@ -176,7 +177,7 @@ func buildBinlogKvs(collectionID, partitionID, segmentID typeutil.UniqueID, binl } binlogBytes, err := proto.Marshal(deltalog) if err != nil { - return nil, fmt.Errorf("marshal deltalogs failed, collectionID:%d, segmentID:%d, fieldID:%d, error:%w", collectionID, segmentID, deltalog.FieldID, err) + return nil, merr.WrapErrSerializationFailed(err, "marshal deltalogs failed, collectionID:%d, segmentID:%d, fieldID:%d", collectionID, segmentID, deltalog.FieldID) } key := buildFieldDeltalogPath(collectionID, partitionID, segmentID, deltalog.FieldID) kv[key] = string(binlogBytes) @@ -189,7 +190,7 @@ func buildBinlogKvs(collectionID, partitionID, segmentID typeutil.UniqueID, binl } binlogBytes, err := proto.Marshal(statslog) if err != nil { - return nil, fmt.Errorf("marshal statslogs failed, collectionID:%d, segmentID:%d, fieldID:%d, error:%w", collectionID, segmentID, statslog.FieldID, err) + return nil, merr.WrapErrSerializationFailed(err, "marshal statslogs failed, collectionID:%d, segmentID:%d, fieldID:%d", collectionID, segmentID, statslog.FieldID) } key := buildFieldStatslogPath(collectionID, partitionID, segmentID, statslog.FieldID) kv[key] = string(binlogBytes) @@ -202,7 +203,7 @@ func buildBinlogKvs(collectionID, partitionID, segmentID typeutil.UniqueID, binl } binlogBytes, err := proto.Marshal(bm25log) if err != nil { - return nil, fmt.Errorf("marshal bm25log failed, collectionID:%d, segmentID:%d, fieldID:%d, error:%w", collectionID, segmentID, bm25log.FieldID, err) + return nil, merr.WrapErrSerializationFailed(err, "marshal bm25log failed, collectionID:%d, segmentID:%d, fieldID:%d", collectionID, segmentID, bm25log.FieldID) } key := buildFieldBM25StatslogPath(collectionID, partitionID, segmentID, bm25log.FieldID) kv[key] = string(binlogBytes) @@ -232,7 +233,7 @@ func marshalSegmentInfo(segment *datapb.SegmentInfo) (string, error) { segBytes, err := proto.Marshal(segment) if err != nil { - return "", fmt.Errorf("failed to marshal segment: %d, err: %w", segment.ID, err) + return "", merr.WrapErrSerializationFailed(err, "marshal segment: %d", segment.ID) } return string(segBytes), nil @@ -250,7 +251,7 @@ func buildSegmentKv(segment *datapb.SegmentInfo) (string, string, error) { func buildCompactionTaskKV(task *datapb.CompactionTask) (string, string, error) { valueBytes, err := proto.Marshal(task) if err != nil { - return "", "", fmt.Errorf("failed to marshal CompactionTask: %d/%d/%d, err: %w", task.TriggerID, task.PlanID, task.CollectionID, err) + return "", "", merr.WrapErrSerializationFailed(err, "marshal CompactionTask: %d/%d/%d", task.TriggerID, task.PlanID, task.CollectionID) } key := buildCompactionTaskPath(task) return key, string(valueBytes), nil @@ -263,7 +264,7 @@ func buildCompactionTaskPath(task *datapb.CompactionTask) string { func buildPartitionStatsInfoKv(info *datapb.PartitionStatsInfo) (string, string, error) { valueBytes, err := proto.Marshal(info) if err != nil { - return "", "", fmt.Errorf("failed to marshal collection clustering compaction info: %d, err: %w", info.CollectionID, err) + return "", "", merr.WrapErrSerializationFailed(err, "marshal collection clustering compaction info: %d", info.CollectionID) } key := buildPartitionStatsInfoPath(info) return key, string(valueBytes), nil diff --git a/internal/metastore/kv/rootcoord/kv_catalog.go b/internal/metastore/kv/rootcoord/kv_catalog.go index f5f9a215ab..cb83b730e6 100644 --- a/internal/metastore/kv/rootcoord/kv_catalog.go +++ b/internal/metastore/kv/rootcoord/kv_catalog.go @@ -176,14 +176,14 @@ func (kc *Catalog) ListDatabases(ctx context.Context, ts typeutil.Timestamp) ([] func (kc *Catalog) CreateCollection(ctx context.Context, coll *model.Collection, ts typeutil.Timestamp) error { if coll.State != pb.CollectionState_CollectionCreated { - return fmt.Errorf("collection state should be created, collection name: %s, collection id: %d, state: %s", coll.Name, coll.CollectionID, coll.State) + return merr.WrapErrServiceInternalMsg("collection state should be created, collection name: %s, collection id: %d, state: %s", coll.Name, coll.CollectionID, coll.State) } k1 := BuildCollectionKey(coll.DBID, coll.CollectionID) collInfo := model.MarshalCollectionModel(coll) v1, err := proto.Marshal(collInfo) if err != nil { - return fmt.Errorf("failed to marshal collection info: %s", err.Error()) + return merr.WrapErrSerializationFailed(err, "marshal collection info") } kvs := map[string]string{} @@ -323,11 +323,11 @@ func (kc *Catalog) CreatePartition(ctx context.Context, dbID int64, partition *m } if partitionExistByID(collMeta, partition.PartitionID) { - return fmt.Errorf("partition already exist: %d", partition.PartitionID) + return merr.WrapErrServiceInternalMsg("partition already exist: %d", partition.PartitionID) } if partitionExistByName(collMeta, partition.PartitionName) { - return fmt.Errorf("partition already exist: %s", partition.PartitionName) + return merr.WrapErrServiceInternalMsg("partition already exist: %s", partition.PartitionName) } // keep consistent with older version, otherwise it's hard to judge where to find partitions. @@ -671,7 +671,7 @@ func (kc *Catalog) GetCredential(ctx context.Context, username string) (*model.C credentialInfo := internalpb.CredentialInfo{} err = json.Unmarshal([]byte(v), &credentialInfo) if err != nil { - return nil, fmt.Errorf("unmarshal credential info err:%w", err) + return nil, merr.WrapErrDataIntegrity(err, "unmarshal credential info") } // we don't save the username in the credential info, so we need to set it manually from path. credentialInfo.Username = username @@ -725,10 +725,10 @@ func (kc *Catalog) DropCollection(ctx context.Context, collectionInfo *model.Col func (kc *Catalog) alterModifyCollection(ctx context.Context, oldColl *model.Collection, newColl *model.Collection, ts typeutil.Timestamp, fieldModify bool) error { if oldColl.TenantID != newColl.TenantID || oldColl.CollectionID != newColl.CollectionID { - return errors.New("altering tenant id or collection id is forbidden") + return merr.WrapErrParameterInvalidMsg("altering tenant id or collection id is forbidden") } if oldColl.DBID != newColl.DBID { - return errors.New("altering dbID should use `AlterCollectionDB` interface") + return merr.WrapErrParameterInvalidMsg("altering dbID should use `AlterCollectionDB` interface") } oldCollClone := oldColl.Clone() oldCollClone.DBID = newColl.DBID @@ -831,13 +831,13 @@ func (kc *Catalog) AlterCollection(ctx context.Context, oldColl *model.Collectio case metastore.MODIFY: return kc.alterModifyCollection(ctx, oldColl, newColl, ts, fieldModify) default: - return fmt.Errorf("altering collection doesn't support %s", alterType.String()) + return merr.WrapErrParameterInvalidMsg("altering collection doesn't support %s", alterType.String()) } } func (kc *Catalog) AlterCollectionDB(ctx context.Context, oldColl *model.Collection, newColl *model.Collection, ts typeutil.Timestamp) error { if oldColl.TenantID != newColl.TenantID || oldColl.CollectionID != newColl.CollectionID { - return errors.New("altering tenant id or collection id is forbidden") + return merr.WrapErrParameterInvalidMsg("altering tenant id or collection id is forbidden") } oldKey := BuildCollectionKey(oldColl.DBID, oldColl.CollectionID) newKey := BuildCollectionKey(newColl.DBID, newColl.CollectionID) @@ -853,7 +853,7 @@ func (kc *Catalog) AlterCollectionDB(ctx context.Context, oldColl *model.Collect func (kc *Catalog) alterModifyPartition(ctx context.Context, oldPart *model.Partition, newPart *model.Partition, ts typeutil.Timestamp) error { if oldPart.CollectionID != newPart.CollectionID || oldPart.PartitionID != newPart.PartitionID { - return errors.New("altering collection id or partition id is forbidden") + return merr.WrapErrParameterInvalidMsg("altering collection id or partition id is forbidden") } oldPartClone := oldPart.Clone() newPartClone := newPart.Clone() @@ -872,7 +872,7 @@ func (kc *Catalog) AlterPartition(ctx context.Context, dbID int64, oldPart *mode if alterType == metastore.MODIFY { return kc.alterModifyPartition(ctx, oldPart, newPart, ts) } - return fmt.Errorf("altering partition doesn't support %s", alterType.String()) + return merr.WrapErrParameterInvalidMsg("altering partition doesn't support %s", alterType.String()) } func dropPartition(collMeta *pb.CollectionInfo, partitionID typeutil.UniqueID) { @@ -1169,7 +1169,7 @@ func (kc *Catalog) remove(ctx context.Context, k string) error { } if err != nil && errors.Is(err, merr.ErrIoKeyNotFound) { log.Ctx(ctx).Debug("the key isn't existed", zap.String("key", k)) - return common.NewIgnorableError(fmt.Errorf("the key[%s] isn't existed", k)) + return common.NewIgnorableErrorf("the key[%s] isn't existed", k) } return kc.Txn.Remove(ctx, k) } @@ -1214,7 +1214,7 @@ func (kc *Catalog) AlterUserRole(ctx context.Context, tenant string, userEntity case milvuspb.OperateUserRoleType_RemoveUserFromRole: return kc.Txn.Remove(ctx, k) } - return fmt.Errorf("invalid operate user role type, operate type: %d", operateType) + return merr.WrapErrParameterInvalidMsg("invalid operate user role type, operate type: %d", operateType) } func (kc *Catalog) ListRole(ctx context.Context, tenant string, entity *milvuspb.RoleEntity, includeUserInfo bool) ([]*milvuspb.RoleResult, error) { @@ -1269,7 +1269,7 @@ func (kc *Catalog) ListRole(ctx context.Context, tenant string, entity *milvuspb } } else { if funcutil.IsEmptyString(entity.Name) { - return results, errors.New("role name in the role entity is empty") + return results, merr.WrapErrParameterInvalidMsg("role name in the role entity is empty") } roleKey := RolePrefix + "/" + entity.Name _, err := kc.Txn.Load(ctx, roleKey) @@ -1344,7 +1344,7 @@ func (kc *Catalog) ListUser(ctx context.Context, tenant string, entity *milvuspb } } else { if funcutil.IsEmptyString(entity.Name) { - return results, errors.New("username in the user entity is empty") + return results, merr.WrapErrParameterInvalidMsg("username in the user entity is empty") } _, err = kc.GetCredential(ctx, entity.Name) if err != nil { @@ -1555,7 +1555,7 @@ func (kc *Catalog) AlterGrant(ctx context.Context, tenant string, entity *milvus log.Ctx(ctx).Warn("fail to load grant privilege entity", zap.String("key", granteeKey), zap.Any("type", operateType), zap.Error(err)) if funcutil.IsRevoke(operateType) { if errors.Is(err, merr.ErrIoKeyNotFound) { - return common.NewIgnorableError(fmt.Errorf("the grant[%s] isn't existed", granteeKey)) + return common.NewIgnorableErrorf("the grant[%s] isn't existed", granteeKey) } return err } @@ -1588,7 +1588,7 @@ func (kc *Catalog) AlterGrant(ctx context.Context, tenant string, entity *milvus } log.Ctx(ctx).Debug("not found the grantee id", zap.String("key", k)) if funcutil.IsRevoke(operateType) { - return common.NewIgnorableError(fmt.Errorf("the grantee-id[%s] isn't existed", k)) + return common.NewIgnorableErrorf("the grantee-id[%s] isn't existed", k) } if funcutil.IsGrant(operateType) { if err = kc.Txn.Save(ctx, k, entity.Grantor.User.Name); err != nil { @@ -1605,7 +1605,7 @@ func (kc *Catalog) AlterGrant(ctx context.Context, tenant string, entity *milvus } return err } - return common.NewIgnorableError(fmt.Errorf("the privilege[%s] has been granted", privilegeName)) + return common.NewIgnorableErrorf("the privilege[%s] has been granted", privilegeName) } func (kc *Catalog) ListGrant(ctx context.Context, tenant string, entity *milvuspb.GrantEntity) ([]*milvuspb.GrantEntity, error) { @@ -2101,7 +2101,7 @@ func (kc *Catalog) GetPrivilegeGroup(ctx context.Context, groupName string) (*mi val, err := kc.Txn.Load(ctx, k) if err != nil { if errors.Is(err, merr.ErrIoKeyNotFound) { - return nil, fmt.Errorf("privilege group [%s] does not exist", groupName) + return nil, merr.WrapErrParameterInvalidMsg("privilege group [%s] does not exist", groupName) } log.Ctx(ctx).Error("failed to load privilege group", zap.String("group", groupName), zap.Error(err)) return nil, err diff --git a/internal/metastore/kv/rootcoord/legacy_snapshot_gc.go b/internal/metastore/kv/rootcoord/legacy_snapshot_gc.go index 68d7d51ec4..858e36f67c 100644 --- a/internal/metastore/kv/rootcoord/legacy_snapshot_gc.go +++ b/internal/metastore/kv/rootcoord/legacy_snapshot_gc.go @@ -2,7 +2,6 @@ package rootcoord import ( "context" - "fmt" "strings" "sync" "time" @@ -22,7 +21,7 @@ const ( ) // errBatchFull is a sentinel error to stop WalkWithPrefix after collecting enough keys. -var errBatchFull = fmt.Errorf("legacy GC batch full") +var errBatchFull = errors.New("legacy GC batch full") var legacyGCOnce sync.Once diff --git a/internal/metastore/kv/streamingcoord/kv_catalog.go b/internal/metastore/kv/streamingcoord/kv_catalog.go index 130bf76539..1c1e24bb5b 100644 --- a/internal/metastore/kv/streamingcoord/kv_catalog.go +++ b/internal/metastore/kv/streamingcoord/kv_catalog.go @@ -101,7 +101,7 @@ func (c *catalog) GetVersion(ctx context.Context) (*streamingpb.StreamingVersion // SaveVersion saves the streaming version func (c *catalog) SaveVersion(ctx context.Context, version *streamingpb.StreamingVersion) error { if version == nil { - return errors.New("version is nil") + return merr.WrapErrServiceInternalMsg("version is nil") } v, err := proto.Marshal(version) if err != nil { diff --git a/internal/parser/planparserv2/convert_field_data_to_generic_value.go b/internal/parser/planparserv2/convert_field_data_to_generic_value.go index 105fbe6db5..4c00bec882 100644 --- a/internal/parser/planparserv2/convert_field_data_to_generic_value.go +++ b/internal/parser/planparserv2/convert_field_data_to_generic_value.go @@ -2,13 +2,11 @@ package planparserv2 import ( "bytes" - "fmt" - - "github.com/cockroachdb/errors" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/json" "github.com/milvus-io/milvus/pkg/v3/proto/planpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) func convertArrayValue(templateName string, templateValue *schemapb.TemplateArrayValue) (*planpb.GenericValue, error) { @@ -93,7 +91,7 @@ func convertArrayValue(templateName string, templateValue *schemapb.TemplateArra } elementType = schemapb.DataType_JSON default: - return nil, fmt.Errorf("unknown template variable value type: %v", templateValue.GetData()) + return nil, merr.WrapErrQueryPlanMsg("unknown template variable value type: %v", templateValue.GetData()) } return &planpb.GenericValue{ Val: &planpb.GenericValue_ArrayVal{ @@ -108,7 +106,7 @@ func convertArrayValue(templateName string, templateValue *schemapb.TemplateArra func ConvertToGenericValue(templateName string, templateValue *schemapb.TemplateValue) (*planpb.GenericValue, error) { if templateValue == nil { - return nil, fmt.Errorf("expression template variable value is nil, template name: {%s}", templateName) + return nil, merr.WrapErrQueryPlanMsg("expression template variable value is nil, template name: {%s}", templateName) } switch templateValue.GetVal().(type) { case *schemapb.TemplateValue_BoolVal: @@ -138,7 +136,7 @@ func ConvertToGenericValue(templateName string, templateValue *schemapb.Template case *schemapb.TemplateValue_ArrayVal: return convertArrayValue(templateName, templateValue.GetArrayVal()) default: - return nil, errors.New("expression elements can only be scalars") + return nil, merr.WrapErrQueryPlanMsg("expression elements can only be scalars") } } diff --git a/internal/parser/planparserv2/error_listener.go b/internal/parser/planparserv2/error_listener.go index 008b18ca67..e0ed06672c 100644 --- a/internal/parser/planparserv2/error_listener.go +++ b/internal/parser/planparserv2/error_listener.go @@ -1,9 +1,9 @@ package planparserv2 import ( - "fmt" - "github.com/antlr4-go/antlr/v4" + + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type errorListener interface { @@ -17,7 +17,7 @@ type errorListenerImpl struct { } func (l *errorListenerImpl) SyntaxError(recognizer antlr.Recognizer, offendingSymbol interface{}, line, column int, msg string, e antlr.RecognitionException) { - l.err = fmt.Errorf("line %d:%d %s", line, column, msg) + l.err = merr.WrapErrQueryPlanMsg("line %d:%d %s", line, column, msg) } func (l *errorListenerImpl) Error() error { diff --git a/internal/parser/planparserv2/fill_expression_value.go b/internal/parser/planparserv2/fill_expression_value.go index 1a095b6a27..9ca677d97e 100644 --- a/internal/parser/planparserv2/fill_expression_value.go +++ b/internal/parser/planparserv2/fill_expression_value.go @@ -1,12 +1,9 @@ package planparserv2 import ( - "fmt" - - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/pkg/v3/proto/planpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -51,18 +48,18 @@ func FillExpressionValue(expr *planpb.Expr, templateValues map[string]*planpb.Ge } return nil default: - return fmt.Errorf("this expression no need to fill placeholder with expr type: %T", e) + return merr.WrapErrQueryPlanMsg("this expression no need to fill placeholder with expr type: %T", e) } } func FillTermExpressionValue(expr *planpb.TermExpr, templateValues map[string]*planpb.GenericValue) error { value, ok := templateValues[expr.GetTemplateVariableName()] if !ok && expr.GetValues() == nil { - return fmt.Errorf("the value of expression template variable name {%s} is not found", expr.GetTemplateVariableName()) + return merr.WrapErrQueryPlanMsg("the value of expression template variable name {%s} is not found", expr.GetTemplateVariableName()) } if value == nil || value.GetArrayVal() == nil { - return fmt.Errorf("the value of term expression template variable {%s} is not array", expr.GetTemplateVariableName()) + return merr.WrapErrQueryPlanMsg("the value of term expression template variable {%s} is not array", expr.GetTemplateVariableName()) } dataType := expr.GetColumnInfo().GetDataType() if typeutil.IsArrayType(dataType) { @@ -102,15 +99,15 @@ func isRegexMatchOp(op planpb.OpType) bool { func FillUnaryRangeExpressionValue(expr *planpb.UnaryRangeExpr, templateValues map[string]*planpb.GenericValue) error { value, ok := templateValues[expr.GetTemplateVariableName()] if !ok { - return fmt.Errorf("the value of expression template variable name {%s} is not found", expr.GetTemplateVariableName()) + return merr.WrapErrQueryPlanMsg("the value of expression template variable name {%s} is not found", expr.GetTemplateVariableName()) } if value == nil { - return fmt.Errorf("the value of expression template variable {%s} is nil", expr.GetTemplateVariableName()) + return merr.WrapErrQueryPlanMsg("the value of expression template variable {%s} is nil", expr.GetTemplateVariableName()) } if isLikeMatchOp(expr.GetOp()) { if !IsString(value) { - return fmt.Errorf("the value of like expression template variable {%s} is not string", expr.GetTemplateVariableName()) + return merr.WrapErrQueryPlanMsg("the value of like expression template variable {%s} is not string", expr.GetTemplateVariableName()) } op, operand, err := translatePatternMatch(value.GetStringVal()) if err != nil { @@ -123,7 +120,7 @@ func FillUnaryRangeExpressionValue(expr *planpb.UnaryRangeExpr, templateValues m if isRegexMatchOp(expr.GetOp()) { if !IsString(value) { - return fmt.Errorf("the value of regex expression template variable {%s} is not string", expr.GetTemplateVariableName()) + return merr.WrapErrQueryPlanMsg("the value of regex expression template variable {%s} is not string", expr.GetTemplateVariableName()) } op, operand, err := validateAndOptimizeRegexPattern(value.GetStringVal()) if err != nil { @@ -154,10 +151,10 @@ func FillGISFunctionFilterExpressionValue(expr *planpb.GISFunctionFilterExpr, te templateVariableName := expr.GetWktString() value, ok := templateValues[templateVariableName] if !ok { - return fmt.Errorf("the value of expression template variable name {%s} is not found", templateVariableName) + return merr.WrapErrQueryPlanMsg("the value of expression template variable name {%s} is not found", templateVariableName) } if value == nil || !IsString(value) { - return fmt.Errorf("the value of GIS WKT template variable {%s} is not string", templateVariableName) + return merr.WrapErrQueryPlanMsg("the value of GIS WKT template variable {%s} is not string", templateVariableName) } wktString := value.GetStringVal() @@ -185,7 +182,7 @@ func FillBinaryRangeExpressionValue(expr *planpb.BinaryRangeExpr, templateValues if lowerValue == nil || expr.GetLowerTemplateVariableName() != "" { lowerValue, ok = templateValues[expr.GetLowerTemplateVariableName()] if !ok { - return fmt.Errorf("the lower value of expression template variable name {%s} is not found", expr.GetLowerTemplateVariableName()) + return merr.WrapErrQueryPlanMsg("the lower value of expression template variable name {%s} is not found", expr.GetLowerTemplateVariableName()) } castedLowerValue, err := castValue(dataType, lowerValue) if err != nil { @@ -198,7 +195,7 @@ func FillBinaryRangeExpressionValue(expr *planpb.BinaryRangeExpr, templateValues if upperValue == nil || expr.GetUpperTemplateVariableName() != "" { upperValue, ok = templateValues[expr.GetUpperTemplateVariableName()] if !ok { - return fmt.Errorf("the upper value of expression template variable name {%s} is not found", expr.GetUpperTemplateVariableName()) + return merr.WrapErrQueryPlanMsg("the upper value of expression template variable name {%s} is not found", expr.GetUpperTemplateVariableName()) } castedUpperValue, err := castValue(dataType, upperValue) @@ -228,11 +225,11 @@ func FillBinaryRangeExpressionValue(expr *planpb.BinaryRangeExpr, templateValues if !expr.GetLowerInclusive() || !expr.GetUpperInclusive() { if getGenericValue(GreaterEqual(lowerValue, upperValue)).GetBoolVal() { - return errors.New("invalid range: lowerbound is greater than upperbound") + return merr.WrapErrQueryPlanMsg("invalid range: lowerbound is greater than upperbound") } } else { if getGenericValue(Greater(lowerValue, upperValue)).GetBoolVal() { - return errors.New("invalid range: lowerbound is greater than upperbound") + return merr.WrapErrQueryPlanMsg("invalid range: lowerbound is greater than upperbound") } } @@ -251,7 +248,7 @@ func FillBinaryArithOpEvalRangeExpressionValue(expr *planpb.BinaryArithOpEvalRan if operand == nil || expr.GetOperandTemplateVariableName() != "" { operand, ok = templateValues[expr.GetOperandTemplateVariableName()] if !ok { - return fmt.Errorf("the right operand value of expression template variable name {%s} is not found", expr.GetOperandTemplateVariableName()) + return merr.WrapErrQueryPlanMsg("the right operand value of expression template variable name {%s} is not found", expr.GetOperandTemplateVariableName()) } } @@ -267,7 +264,7 @@ func FillBinaryArithOpEvalRangeExpressionValue(expr *planpb.BinaryArithOpEvalRan } if operand.GetArrayVal() != nil { - return errors.New("can not comparisons array directly") + return merr.WrapErrQueryPlanMsg("can not comparisons array directly") } dataType, err = getTargetType(lDataType, rDataType) @@ -284,7 +281,7 @@ func FillBinaryArithOpEvalRangeExpressionValue(expr *planpb.BinaryArithOpEvalRan if expr.ArithOp == planpb.ArithOpType_Div || expr.ArithOp == planpb.ArithOpType_Mod { if (IsInteger(castedOperand) && castedOperand.GetInt64Val() == 0) || (IsFloating(castedOperand) && castedOperand.GetFloatVal() == 0) { - return errors.New("division or modulus by zero") + return merr.WrapErrQueryPlanMsg("division or modulus by zero") } } @@ -295,7 +292,7 @@ func FillBinaryArithOpEvalRangeExpressionValue(expr *planpb.BinaryArithOpEvalRan if expr.GetValue() == nil || expr.GetValueTemplateVariableName() != "" { value, ok = templateValues[expr.GetValueTemplateVariableName()] if !ok { - return fmt.Errorf("the value of expression template variable name {%s} is not found", expr.GetValueTemplateVariableName()) + return merr.WrapErrQueryPlanMsg("the value of expression template variable name {%s} is not found", expr.GetValueTemplateVariableName()) } } castedValue, err := castValue(dataType, value) @@ -313,7 +310,7 @@ func FillJSONContainsExpressionValue(expr *planpb.JSONContainsExpr, templateValu } value, ok := templateValues[expr.GetTemplateVariableName()] if !ok { - return fmt.Errorf("the value of expression template variable name {%s} is not found", expr.GetTemplateVariableName()) + return merr.WrapErrQueryPlanMsg("the value of expression template variable name {%s} is not found", expr.GetTemplateVariableName()) } if err := checkContainsElement(toColumnExpr(expr.GetColumnInfo()), expr.GetOp(), value); err != nil { return err diff --git a/internal/parser/planparserv2/operators.go b/internal/parser/planparserv2/operators.go index a36be4e4f9..deeb3aecc9 100644 --- a/internal/parser/planparserv2/operators.go +++ b/internal/parser/planparserv2/operators.go @@ -3,11 +3,10 @@ package planparserv2 import ( "math" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" parser "github.com/milvus-io/milvus/internal/parser/planparserv2/generated" "github.com/milvus-io/milvus/pkg/v3/proto/planpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) var arithExprMap = map[int]planpb.ArithOpType{ @@ -72,11 +71,11 @@ func Add(a, b *planpb.GenericValue) (*ExprWithType, error) { } if IsBool(a) || IsBool(b) { - return nil, errors.New("add cannot apply on bool field") + return nil, merr.WrapErrQueryPlanMsg("add cannot apply on bool field") } if IsString(a) || IsString(b) { - return nil, errors.New("add cannot apply on string field") + return nil, merr.WrapErrQueryPlanMsg("add cannot apply on string field") } aFloat, bFloat, aInt, bInt := IsFloating(a), IsFloating(b), IsInteger(a), IsInteger(b) @@ -109,11 +108,11 @@ func Subtract(a, b *planpb.GenericValue) (*ExprWithType, error) { } if IsBool(a) || IsBool(b) { - return nil, errors.New("subtract cannot apply on bool field") + return nil, merr.WrapErrQueryPlanMsg("subtract cannot apply on bool field") } if IsString(a) || IsString(b) { - return nil, errors.New("subtract cannot apply on string field") + return nil, merr.WrapErrQueryPlanMsg("subtract cannot apply on string field") } aFloat, bFloat, aInt, bInt := IsFloating(a), IsFloating(b), IsInteger(a), IsInteger(b) @@ -146,11 +145,11 @@ func Multiply(a, b *planpb.GenericValue) (*ExprWithType, error) { } if IsBool(a) || IsBool(b) { - return nil, errors.New("multiply cannot apply on bool field") + return nil, merr.WrapErrQueryPlanMsg("multiply cannot apply on bool field") } if IsString(a) || IsString(b) { - return nil, errors.New("multiply cannot apply on string field") + return nil, merr.WrapErrQueryPlanMsg("multiply cannot apply on string field") } aFloat, bFloat, aInt, bInt := IsFloating(a), IsFloating(b), IsInteger(a), IsInteger(b) @@ -183,21 +182,21 @@ func Divide(a, b *planpb.GenericValue) (*ExprWithType, error) { } if IsBool(a) || IsBool(b) { - return nil, errors.New("divide cannot apply on bool field") + return nil, merr.WrapErrQueryPlanMsg("divide cannot apply on bool field") } if IsString(a) || IsString(b) { - return nil, errors.New("divide cannot apply on string field") + return nil, merr.WrapErrQueryPlanMsg("divide cannot apply on string field") } aFloat, bFloat, aInt, bInt := IsFloating(a), IsFloating(b), IsInteger(a), IsInteger(b) if bFloat && b.GetFloatVal() == 0 { - return nil, errors.New("cannot divide by zero") + return nil, merr.WrapErrQueryPlanMsg("cannot divide by zero") } if bInt && b.GetInt64Val() == 0 { - return nil, errors.New("cannot divide by zero") + return nil, merr.WrapErrQueryPlanMsg("cannot divide by zero") } if aFloat && bFloat { @@ -229,12 +228,12 @@ func Modulo(a, b *planpb.GenericValue) (*ExprWithType, error) { aInt, bInt := IsInteger(a), IsInteger(b) if !aInt || !bInt { - return nil, errors.New("modulo can only apply on integer") + return nil, merr.WrapErrQueryPlanMsg("modulo can only apply on integer") } // aInt && bInt if b.GetInt64Val() == 0 { - return nil, errors.New("cannot modulo by zero") + return nil, merr.WrapErrQueryPlanMsg("cannot modulo by zero") } ret.dataType = schemapb.DataType_Int64 @@ -288,29 +287,29 @@ func Power(a, b *planpb.GenericValue) *ExprWithType { } func BitAnd(a, b *planpb.GenericValue) (*ExprWithType, error) { - return nil, errors.New("todo: unsupported") + return nil, merr.WrapErrQueryPlanMsg("todo: unsupported") } func BitOr(a, b *planpb.GenericValue) (*ExprWithType, error) { - return nil, errors.New("todo: unsupported") + return nil, merr.WrapErrQueryPlanMsg("todo: unsupported") } func BitXor(a, b *planpb.GenericValue) (*ExprWithType, error) { - return nil, errors.New("todo: unsupported") + return nil, merr.WrapErrQueryPlanMsg("todo: unsupported") } func ShiftLeft(a, b *planpb.GenericValue) (*ExprWithType, error) { - return nil, errors.New("todo: unsupported") + return nil, merr.WrapErrQueryPlanMsg("todo: unsupported") } func ShiftRight(a, b *planpb.GenericValue) (*ExprWithType, error) { - return nil, errors.New("todo: unsupported") + return nil, merr.WrapErrQueryPlanMsg("todo: unsupported") } func And(a, b *planpb.GenericValue) (*ExprWithType, error) { aBool, bBool := IsBool(a), IsBool(b) if !aBool || !bBool { - return nil, errors.New("and can only apply on boolean") + return nil, merr.WrapErrQueryPlanMsg("and can only apply on boolean") } return &ExprWithType{ dataType: schemapb.DataType_Bool, @@ -327,7 +326,7 @@ func And(a, b *planpb.GenericValue) (*ExprWithType, error) { func Or(a, b *planpb.GenericValue) (*ExprWithType, error) { aBool, bBool := IsBool(a), IsBool(b) if !aBool || !bBool { - return nil, errors.New("or can only apply on boolean") + return nil, merr.WrapErrQueryPlanMsg("or can only apply on boolean") } return &ExprWithType{ dataType: schemapb.DataType_Bool, @@ -342,7 +341,7 @@ func Or(a, b *planpb.GenericValue) (*ExprWithType, error) { } func BitNot(a *planpb.GenericValue) (*ExprWithType, error) { - return nil, errors.New("todo: unsupported") + return nil, merr.WrapErrQueryPlanMsg("todo: unsupported") } func Negative(a *planpb.GenericValue) *ExprWithType { @@ -375,7 +374,7 @@ func Negative(a *planpb.GenericValue) *ExprWithType { func Not(a *planpb.GenericValue) (*ExprWithType, error) { if !IsBool(a) { - return nil, errors.New("not can only apply on boolean") + return nil, merr.WrapErrQueryPlanMsg("not can only apply on boolean") } return &ExprWithType{ dataType: schemapb.DataType_Bool, @@ -426,7 +425,7 @@ func less() relationalFn { return a.GetInt64Val() < b.GetInt64Val(), nil } - return false, errors.New("incompatible data type") + return false, merr.WrapErrQueryPlanMsg("incompatible data type") } } diff --git a/internal/parser/planparserv2/parser_visitor.go b/internal/parser/planparserv2/parser_visitor.go index 5836d563d0..b6108f1eba 100644 --- a/internal/parser/planparserv2/parser_visitor.go +++ b/internal/parser/planparserv2/parser_visitor.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/antlr4-go/antlr/v4" - "github.com/cockroachdb/errors" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" parser "github.com/milvus-io/milvus/internal/parser/planparserv2/generated" @@ -241,7 +240,7 @@ func (v *ParserVisitor) parseRegexPatternOrTemplate(ctx parser.IExprContext, arg func checkDirectComparisonBinaryField(columnInfo *planpb.ColumnInfo) error { if typeutil.IsArrayType(columnInfo.GetDataType()) && len(columnInfo.GetNestedPath()) == 0 && !columnInfo.GetIsElementLevel() { - return errors.New("can not comparisons array fields directly") + return merr.WrapErrQueryPlanMsg("can not comparisons array fields directly") } return nil } @@ -534,12 +533,12 @@ func (v *ParserVisitor) VisitLike(ctx *parser.LikeContext) interface{} { leftExpr := getExpr(left) if leftExpr == nil { - return errors.New("the left operand of like is invalid") + return merr.WrapErrQueryPlanMsg("the left operand of like is invalid") } column := toColumnInfo(leftExpr) if column == nil { - return errors.New("like operation on complicated expr is unsupported") + return merr.WrapErrQueryPlanMsg("like operation on complicated expr is unsupported") } if err := checkDirectComparisonBinaryField(column); err != nil { return err @@ -547,7 +546,7 @@ func (v *ParserVisitor) VisitLike(ctx *parser.LikeContext) interface{} { if !typeutil.IsStringType(leftExpr.dataType) && !typeutil.IsJSONType(leftExpr.dataType) && (!typeutil.IsArrayType(leftExpr.dataType) || !typeutil.IsStringType(column.GetElementType())) { - return errors.New("like operation on non-string or no-json field is unsupported") + return merr.WrapErrQueryPlanMsg("like operation on non-string or no-json field is unsupported") } pattern, placeholder, isTemplate, err := v.parseStringLiteralOrTemplate(ctx.Expr(1), "like pattern") @@ -589,11 +588,11 @@ func (v *ParserVisitor) VisitLike(ctx *parser.LikeContext) interface{} { // sequences as-is, only processing quote delimiters. func extractRegexPattern(literal string) (string, error) { if len(literal) < 2 { - return "", fmt.Errorf("invalid string literal: %s", literal) + return "", merr.WrapErrQueryPlanMsg("invalid string literal: %s", literal) } quote := literal[0] if (quote != '"' && quote != '\'') || literal[len(literal)-1] != quote { - return "", fmt.Errorf("invalid string literal: %s", literal) + return "", merr.WrapErrQueryPlanMsg("invalid string literal: %s", literal) } // Strip surrounding quotes, preserve all escape sequences as-is inner := literal[1 : len(literal)-1] @@ -706,7 +705,7 @@ func tryOptimizeRegexToLike(pattern string) (planpb.OpType, string, bool) { func validateAndOptimizeRegexPattern(pattern string) (planpb.OpType, string, error) { if _, err := regexp.Compile(pattern); err != nil { - return 0, "", fmt.Errorf("invalid regex pattern: %s", err) + return 0, "", merr.WrapErrQueryPlan(err, "invalid regex pattern") } op := planpb.OpType_RegexMatch @@ -733,19 +732,19 @@ func (v *ParserVisitor) VisitRegexMatch(ctx *parser.RegexMatchContext) interface leftExpr := getExpr(left) if leftExpr == nil { - return errors.New("the left operand of =~ is invalid") + return merr.WrapErrQueryPlanMsg("the left operand of =~ is invalid") } column := toColumnInfo(leftExpr) if column == nil { - return errors.New("regex match on complicated expr is unsupported") + return merr.WrapErrQueryPlanMsg("regex match on complicated expr is unsupported") } if err := checkDirectComparisonBinaryField(column); err != nil { return err } if !isRegexMatchSupportedType(leftExpr.dataType, column.GetElementType()) { - return errors.New("regex match on non-string or non-json field is unsupported") + return merr.WrapErrQueryPlanMsg("regex match on non-string or non-json field is unsupported") } pattern, placeholder, isTemplate, err := v.parseRegexPatternOrTemplate(ctx.Expr(1), "regex pattern") @@ -789,19 +788,19 @@ func (v *ParserVisitor) VisitRegexNotMatch(ctx *parser.RegexNotMatchContext) int leftExpr := getExpr(left) if leftExpr == nil { - return errors.New("the left operand of !~ is invalid") + return merr.WrapErrQueryPlanMsg("the left operand of !~ is invalid") } column := toColumnInfo(leftExpr) if column == nil { - return errors.New("regex match on complicated expr is unsupported") + return merr.WrapErrQueryPlanMsg("regex match on complicated expr is unsupported") } if err := checkDirectComparisonBinaryField(column); err != nil { return err } if !isRegexMatchSupportedType(leftExpr.dataType, column.GetElementType()) { - return errors.New("regex match on non-string or non-json field is unsupported") + return merr.WrapErrQueryPlanMsg("regex match on non-string or non-json field is unsupported") } pattern, placeholder, isTemplate, err := v.parseRegexPatternOrTemplate(ctx.Expr(1), "regex pattern") @@ -854,7 +853,7 @@ func (v *ParserVisitor) VisitTextMatch(ctx *parser.TextMatchContext) interface{} } columnInfo := toColumnInfo(column) if !typeutil.IsStringType(column.dataType) { - return errors.New("text match operation on non-string is unsupported") + return merr.WrapErrQueryPlanMsg("text match operation on non-string is unsupported") } if !v.schema.IsFieldTextMatchEnabled(columnInfo.FieldId) { return merr.WrapErrParameterInvalidMsg("field \"%s\" does not enable match", identifier) @@ -929,7 +928,7 @@ func (v *ParserVisitor) VisitPhraseMatch(ctx *parser.PhraseMatchContext) interfa columnInfo := toColumnInfo(column) if !typeutil.IsStringType(column.dataType) { - return errors.New("phrase match operation on non-string is unsupported") + return merr.WrapErrQueryPlanMsg("phrase match operation on non-string is unsupported") } if !v.schema.IsFieldTextMatchEnabled(columnInfo.FieldId) { return merr.WrapErrParameterInvalidMsg("field \"%s\" does not enable match", identifier) @@ -1331,11 +1330,11 @@ func (v *ParserVisitor) VisitRange(ctx *parser.RangeContext) interface{} { if !isTemplateExpr(lowerValueExpr) && !isTemplateExpr(upperValueExpr) { if !lowerInclusive || !upperInclusive { if getGenericValue(GreaterEqual(lowerValue, upperValue)).GetBoolVal() { - return errors.New("invalid range: lowerbound is greater than upperbound") + return merr.WrapErrQueryPlanMsg("invalid range: lowerbound is greater than upperbound") } } else { if getGenericValue(Greater(lowerValue, upperValue)).GetBoolVal() { - return errors.New("invalid range: lowerbound is greater than upperbound") + return merr.WrapErrQueryPlanMsg("invalid range: lowerbound is greater than upperbound") } } } @@ -1418,11 +1417,11 @@ func (v *ParserVisitor) VisitReverseRange(ctx *parser.ReverseRangeContext) inter if !isTemplateExpr(lowerValueExpr) && !isTemplateExpr(upperValueExpr) { if !lowerInclusive || !upperInclusive { if getGenericValue(GreaterEqual(lowerValue, upperValue)).GetBoolVal() { - return errors.New("invalid range: lowerbound is greater than upperbound") + return merr.WrapErrQueryPlanMsg("invalid range: lowerbound is greater than upperbound") } } else { if getGenericValue(Greater(lowerValue, upperValue)).GetBoolVal() { - return errors.New("invalid range: lowerbound is greater than upperbound") + return merr.WrapErrQueryPlanMsg("invalid range: lowerbound is greater than upperbound") } } } @@ -1491,13 +1490,13 @@ func (v *ParserVisitor) VisitUnary(ctx *parser.UnaryContext) interface{} { childExpr := getExpr(child) if childExpr == nil { - return errors.New("failed to parse unary expressions") + return merr.WrapErrQueryPlanMsg("failed to parse unary expressions") } if isRandomSampleExpr(childExpr) { - return errors.New("random sample expression cannot be used in unary expression") + return merr.WrapErrQueryPlanMsg("random sample expression cannot be used in unary expression") } if isElementFilterExpr(childExpr) { - return errors.New("element filter expression cannot be used in unary expression") + return merr.WrapErrQueryPlanMsg("element filter expression cannot be used in unary expression") } if err := checkDirectComparisonBinaryField(toColumnInfo(childExpr)); err != nil { @@ -1557,7 +1556,7 @@ func (v *ParserVisitor) VisitLogicalOr(ctx *parser.LogicalOrContext) interface{} otherExpr = getExpr(left) } if !IsBool(boolLiteral) { - return errors.New("'or' can only be used between boolean expressions") + return merr.WrapErrQueryPlanMsg("'or' can only be used between boolean expressions") } if boolLiteral.GetBoolVal() { // true or expr → always true @@ -1568,7 +1567,7 @@ func (v *ParserVisitor) VisitLogicalOr(ctx *parser.LogicalOrContext) interface{} } // false or expr → expr if otherExpr == nil || !canBeExecuted(otherExpr) { - return errors.New("'or' can only be used between boolean expressions") + return merr.WrapErrQueryPlanMsg("'or' can only be used between boolean expressions") } return otherExpr } @@ -1578,15 +1577,15 @@ func (v *ParserVisitor) VisitLogicalOr(ctx *parser.LogicalOrContext) interface{} leftExpr = getExpr(left) rightExpr = getExpr(right) if isRandomSampleExpr(leftExpr) || isRandomSampleExpr(rightExpr) { - return errors.New("random sample expression cannot be used in logical and expression") + return merr.WrapErrQueryPlanMsg("random sample expression cannot be used in logical and expression") } if isElementFilterExpr(leftExpr) { - return errors.New("element filter expression can only be the last expression in the logical or expression") + return merr.WrapErrQueryPlanMsg("element filter expression can only be the last expression in the logical or expression") } if !canBeExecuted(leftExpr) || !canBeExecuted(rightExpr) { - return errors.New("'or' can only be used between boolean expressions") + return merr.WrapErrQueryPlanMsg("'or' can only be used between boolean expressions") } expr := &planpb.Expr{ Expr: &planpb.Expr_BinaryExpr{ @@ -1635,7 +1634,7 @@ func (v *ParserVisitor) VisitLogicalAnd(ctx *parser.LogicalAndContext) interface otherExpr = getExpr(left) } if !IsBool(boolLiteral) { - return errors.New("'and' can only be used between boolean expressions") + return merr.WrapErrQueryPlanMsg("'and' can only be used between boolean expressions") } if !boolLiteral.GetBoolVal() { // false and expr → always false @@ -1646,7 +1645,7 @@ func (v *ParserVisitor) VisitLogicalAnd(ctx *parser.LogicalAndContext) interface } // true and expr → expr if otherExpr == nil || !canBeExecuted(otherExpr) { - return errors.New("'and' can only be used between boolean expressions") + return merr.WrapErrQueryPlanMsg("'and' can only be used between boolean expressions") } return otherExpr } @@ -1656,15 +1655,15 @@ func (v *ParserVisitor) VisitLogicalAnd(ctx *parser.LogicalAndContext) interface leftExpr = getExpr(left) rightExpr = getExpr(right) if isRandomSampleExpr(leftExpr) { - return errors.New("random sample expression can only be the last expression in the logical and expression") + return merr.WrapErrQueryPlanMsg("random sample expression can only be the last expression in the logical and expression") } if isElementFilterExpr(leftExpr) { - return errors.New("element filter expression can only be the last expression in the logical and expression") + return merr.WrapErrQueryPlanMsg("element filter expression can only be the last expression in the logical and expression") } if !canBeExecuted(leftExpr) || !canBeExecuted(rightExpr) { - return errors.New("'and' can only be used between boolean expressions") + return merr.WrapErrQueryPlanMsg("'and' can only be used between boolean expressions") } var expr *planpb.Expr @@ -1803,7 +1802,7 @@ func (v *ParserVisitor) getColumnInfoFromJSONIdentifier(identifier string) (*pla return nil, merr.WrapErrParameterInvalidMsg("invalid identifier: %s", identifier) } if typeutil.IsArrayType(field.DataType) { - return nil, errors.New("can only access array field with integer index") + return nil, merr.WrapErrQueryPlanMsg("can only access array field with integer index") } } else if _, err := strconv.ParseInt(path, 10, 64); err != nil { return nil, merr.WrapErrParameterInvalidMsg("json key must be enclosed in double quotes or single quotes: \"%s\"", path) diff --git a/internal/parser/planparserv2/plan_parser_v2.go b/internal/parser/planparserv2/plan_parser_v2.go index 35829d0b6a..892c5fb7a1 100644 --- a/internal/parser/planparserv2/plan_parser_v2.go +++ b/internal/parser/planparserv2/plan_parser_v2.go @@ -60,7 +60,7 @@ func handleInternal(exprStr string) (ast planparserv2.IExprContext, err error) { case error: return nil, v default: - return nil, fmt.Errorf("unknown cache error: %v", v) + return nil, merr.WrapErrQueryPlanMsg("unknown cache error: %v", v) } } @@ -91,7 +91,7 @@ func handleInternal(exprStr string) (ast planparserv2.IExprContext, err error) { if parser.GetCurrentToken().GetTokenType() != antlr.TokenEOF { log.Info("invalid expression", zap.String("expr", exprStr)) - err = fmt.Errorf("invalid expression: %s", exprStr) + err = merr.WrapErrQueryPlanMsg("invalid expression: %s", exprStr) return } @@ -106,7 +106,7 @@ func handleInternal(exprStr string) (ast planparserv2.IExprContext, err error) { func handleExprInternal(schema *typeutil.SchemaHelper, exprStr string, visitorArgs *ParserVisitorArgs) (result interface{}) { defer func() { if r := recover(); r != nil { - result = fmt.Errorf("unsupported expression: %s", exprStr) + result = merr.WrapErrQueryPlanMsg("unsupported expression: %s", exprStr) } }() @@ -130,12 +130,12 @@ func parseExprInner(schema *typeutil.SchemaHelper, exprStr string, exprTemplateV ret := handleExprInternal(schema, exprStr, visitorArgs) if err := getError(ret); err != nil { - return nil, fmt.Errorf("cannot parse expression: %s, error: %s", exprStr, err) + return nil, merr.WrapErrQueryPlan(err, "cannot parse expression: %s", exprStr) } predicate := getExpr(ret) if predicate == nil { - return nil, fmt.Errorf("cannot parse expression: %s", exprStr) + return nil, merr.WrapErrQueryPlanMsg("cannot parse expression: %s", exprStr) } if !canBeExecuted(predicate) { // Handle standalone boolean literals: true → AlwaysTrueExpr, false → AlwaysFalseExpr. @@ -147,7 +147,7 @@ func parseExprInner(schema *typeutil.SchemaHelper, exprStr string, exprTemplateV } predicate.nodeDependent = false } else { - return nil, fmt.Errorf("predicate is not a boolean expression: %s, data type: %s", exprStr, predicate.dataType) + return nil, merr.WrapErrQueryPlanMsg("predicate is not a boolean expression: %s, data type: %s", exprStr, predicate.dataType) } } @@ -172,15 +172,15 @@ func parseIdentifierInner(schema *typeutil.SchemaHelper, identifier string, chec ret := handleExprInternal(schema, identifier, visitorArgs) if err := getError(ret); err != nil { - return fmt.Errorf("cannot parse identifier: %s, error: %s", identifier, err) + return merr.WrapErrQueryPlan(err, "cannot parse identifier: %s", identifier) } predicate := getExpr(ret) if predicate == nil { - return fmt.Errorf("cannot parse identifier: %s", identifier) + return merr.WrapErrQueryPlanMsg("cannot parse identifier: %s", identifier) } if predicate.expr.GetColumnExpr() == nil { - return fmt.Errorf("cannot parse identifier: %s", identifier) + return merr.WrapErrQueryPlanMsg("cannot parse identifier: %s", identifier) } return checkFunc(predicate.expr) @@ -242,7 +242,7 @@ func CreateSearchPlanArgs(schema *typeutil.SchemaHelper, exprStr string, vectorF var vectorType planpb.VectorType if !typeutil.IsVectorType(dataType) { - return nil, fmt.Errorf("field (%s) to search is not of vector data type", vectorFieldName) + return nil, merr.WrapErrQueryPlanMsg("field (%s) to search is not of vector data type", vectorFieldName) } switch dataType { case schemapb.DataType_BinaryVector: @@ -271,12 +271,12 @@ func CreateSearchPlanArgs(schema *typeutil.SchemaHelper, exprStr string, vectorF vectorType = planpb.VectorType_EmbListInt8Vector default: log.Error("Invalid elementType for ArrayOfVector", zap.Any("elementType", elementType)) - return nil, fmt.Errorf("unsupported element type for ArrayOfVector: %v", elementType) + return nil, merr.WrapErrQueryPlanMsg("unsupported element type for ArrayOfVector: %v", elementType) } default: log.Error("Invalid dataType", zap.Any("dataType", dataType)) - return nil, fmt.Errorf("unsupported vector data type: %v", dataType) + return nil, merr.WrapErrQueryPlanMsg("unsupported vector data type: %v", dataType) } scorers, options, err := CreateSearchScorers(schema, functionScorer, exprTemplateValues) @@ -285,7 +285,7 @@ func CreateSearchPlanArgs(schema *typeutil.SchemaHelper, exprStr string, vectorF } if len(scorers) != 0 && (queryInfo.GroupByFieldId != -1 || queryInfo.SearchIteratorV2Info != nil) { - return nil, fmt.Errorf("don't support use segment scorer with group_by or search_iterator") + return nil, merr.WrapErrQueryPlanMsg("don't support use segment scorer with group_by or search_iterator") } exprParams := ParseExprParams(exprTemplateValues) @@ -379,19 +379,19 @@ func CreateSearchScorer(schema *typeutil.SchemaHelper, function *schemapb.Functi if ok { expr, err := ParseExpr(schema, filter, exprTemplateValues) if err != nil { - return nil, fmt.Errorf("parse expr failed with error: {%v}", err) + return nil, merr.WrapErrQueryPlan(err, "parse expr failed") } scorer.Filter = expr } weightStr, ok := funcutil.TryGetAttrByKeyFromRepeatedKV(rerank.WeightKey, function.GetParams()) if !ok { - return nil, fmt.Errorf("must set weight params for weight scorer") + return nil, merr.WrapErrQueryPlanMsg("must set weight params for weight scorer") } weight, err := strconv.ParseFloat(weightStr, 32) if err != nil { - return nil, fmt.Errorf("parse function scorer weight params failed with error: {%v}", err) + return nil, merr.WrapErrQueryPlan(err, "parse function scorer weight params failed") } scorer.Weight = float32(weight) diff --git a/internal/parser/planparserv2/utils.go b/internal/parser/planparserv2/utils.go index d0e9a9e411..816d01ffe0 100644 --- a/internal/parser/planparserv2/utils.go +++ b/internal/parser/planparserv2/utils.go @@ -7,13 +7,13 @@ import ( "strings" "unicode" - "github.com/cockroachdb/errors" "github.com/twpayne/go-geom" "github.com/twpayne/go-geom/encoding/wkt" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/json" "github.com/milvus-io/milvus/pkg/v3/proto/planpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -207,7 +207,7 @@ func getTargetType(lDataType, rDataType schemapb.DataType) (schemapb.DataType, e } } - return schemapb.DataType_None, fmt.Errorf("incompatible data type, %s, %s", lDataType.String(), rDataType.String()) + return schemapb.DataType_None, merr.WrapErrQueryPlanMsg("incompatible data type, %s, %s", lDataType.String(), rDataType.String()) } func getSameType(left, right *ExprWithType) (schemapb.DataType, error) { @@ -243,7 +243,7 @@ func reverseOrder(op planpb.OpType) (planpb.OpType, error) { case planpb.OpType_NotEqual: return planpb.OpType_NotEqual, nil default: - return planpb.OpType_Invalid, fmt.Errorf("cannot reverse order: %s", op) + return planpb.OpType_Invalid, merr.WrapErrQueryPlanMsg("cannot reverse order: %s", op) } } @@ -282,7 +282,7 @@ func castValue(dataType schemapb.DataType, value *planpb.GenericValue) (*planpb. return value, nil } - return nil, fmt.Errorf("cannot cast value to %s, value: %s", dataType.String(), value) + return nil, merr.WrapErrQueryPlanMsg("cannot cast value to %s, value: %s", dataType.String(), value) } func combineBinaryArithExpr(op planpb.OpType, arithOp planpb.ArithOpType, arithExprDataType schemapb.DataType, columnInfo *planpb.ColumnInfo, operandExpr, valueExpr *planpb.ValueExpr) (*planpb.Expr, error) { @@ -297,7 +297,7 @@ func combineBinaryArithExpr(op planpb.OpType, arithOp planpb.ArithOpType, arithE if arithOp == planpb.ArithOpType_Div || arithOp == planpb.ArithOpType_Mod { if (IsInteger(operand) && operand.GetInt64Val() == 0) || (IsFloating(operand) && operand.GetFloatVal() == 0) { - return nil, errors.New("division or modulus by zero") + return nil, merr.WrapErrQueryPlanMsg("division or modulus by zero") } } @@ -342,12 +342,12 @@ func handleBinaryArithExpr(op planpb.OpType, arithExpr *planpb.BinaryArithExpr, if leftExpr != nil && rightExpr != nil { // a + b == 3 - return nil, errors.New("not supported to do arithmetic operations between multiple fields") + return nil, merr.WrapErrQueryPlanMsg("not supported to do arithmetic operations between multiple fields") } if leftValue != nil && rightValue != nil { // 2 + 1 == 3 - return nil, errors.New("unexpected, should be optimized already") + return nil, merr.WrapErrQueryPlanMsg("unexpected, should be optimized already") } if leftExpr != nil && rightValue != nil { @@ -368,11 +368,11 @@ func handleBinaryArithExpr(op planpb.OpType, arithExpr *planpb.BinaryArithExpr, case planpb.ArithOpType_Add, planpb.ArithOpType_Mul: return combineBinaryArithExpr(op, arithOp, arithExprDataType, rightExpr.GetInfo(), leftValue, valueExpr) default: - return nil, errors.New("module field is not yet supported") + return nil, merr.WrapErrQueryPlanMsg("module field is not yet supported") } } else { // (a + b) / 2 == 3 - return nil, errors.New("complicated arithmetic operations are not supported") + return nil, merr.WrapErrQueryPlanMsg("complicated arithmetic operations are not supported") } } @@ -401,7 +401,7 @@ func handleCompareRightValue(op planpb.OpType, left *ExprWithType, right *planpb } if columnInfo == nil { - return nil, errors.New("not supported to combine multiple fields") + return nil, merr.WrapErrQueryPlanMsg("not supported to combine multiple fields") } expr := &planpb.Expr{ Expr: &planpb.Expr_UnaryRangeExpr{ @@ -417,7 +417,7 @@ func handleCompareRightValue(op planpb.OpType, left *ExprWithType, right *planpb switch op { case planpb.OpType_Invalid: - return nil, fmt.Errorf("unsupported op type: %s", op) + return nil, merr.WrapErrQueryPlanMsg("unsupported op type: %s", op) default: return expr, nil } @@ -441,12 +441,12 @@ func handleCompare(op planpb.OpType, left *ExprWithType, right *ExprWithType) (* } if leftColumnInfo == nil || rightColumnInfo == nil { - return nil, errors.New("only comparison between two fields is supported") + return nil, merr.WrapErrQueryPlanMsg("only comparison between two fields is supported") } // Check if both left and right are non-JSON types if typeutil.IsJSONType(leftColumnInfo.GetDataType()) || typeutil.IsJSONType(rightColumnInfo.GetDataType()) { - return nil, errors.New("two column comparison with JSON type is not supported") + return nil, merr.WrapErrQueryPlanMsg("two column comparison with JSON type is not supported") } expr := &planpb.Expr{ @@ -461,7 +461,7 @@ func handleCompare(op planpb.OpType, left *ExprWithType, right *ExprWithType) (* switch op { case planpb.OpType_Invalid: - return nil, fmt.Errorf("unsupported op type: %s", op) + return nil, merr.WrapErrQueryPlanMsg("unsupported op type: %s", op) default: return expr, nil } @@ -522,7 +522,7 @@ func getDataType(expr *ExprWithType) string { func HandleCompare(op int, left, right *ExprWithType) (*planpb.Expr, error) { if !left.expr.GetIsTemplate() && !right.expr.GetIsTemplate() { if !canBeCompared(left, right) { - return nil, fmt.Errorf("comparisons between %s and %s are not supported", + return nil, merr.WrapErrQueryPlanMsg("comparisons between %s and %s are not supported", getDataType(left), getDataType(right)) } } @@ -662,7 +662,7 @@ func canArithmetic(left, leftElement, right, rightElement schemapb.DataType, rev left, right = right, left } if !canArithmeticDataType(left, right) { - return fmt.Errorf("cannot perform arithmetic between %s field and %s", left.String(), right.String()) + return merr.WrapErrQueryPlanMsg("cannot perform arithmetic between %s field and %s", left.String(), right.String()) } return nil } @@ -703,7 +703,7 @@ func checkValidModArith(tokenType planpb.ArithOpType, leftType, leftElementType, switch tokenType { case planpb.ArithOpType_Mod: if !canConvertToIntegerType(leftType, leftElementType) || !canConvertToIntegerType(rightType, rightElementType) { - return errors.New("modulo can only apply on integer types") + return merr.WrapErrQueryPlanMsg("modulo can only apply on integer types") } default: } @@ -714,17 +714,17 @@ func castRangeValue(dataType schemapb.DataType, value *planpb.GenericValue) (*pl switch dataType { case schemapb.DataType_String, schemapb.DataType_VarChar: if !IsString(value) { - return nil, errors.New("invalid range operations") + return nil, merr.WrapErrQueryPlanMsg("invalid range operations") } case schemapb.DataType_Bool: - return nil, errors.New("invalid range operations on boolean expr") + return nil, merr.WrapErrQueryPlanMsg("invalid range operations on boolean expr") case schemapb.DataType_Int8, schemapb.DataType_Int16, schemapb.DataType_Int32, schemapb.DataType_Int64: if !IsInteger(value) { - return nil, errors.New("invalid range operations") + return nil, merr.WrapErrQueryPlanMsg("invalid range operations") } case schemapb.DataType_Float, schemapb.DataType_Double: if !IsNumber(value) { - return nil, errors.New("invalid range operations") + return nil, merr.WrapErrQueryPlanMsg("invalid range operations") } if IsInteger(value) { return NewFloat(float64(value.GetInt64Val())), nil @@ -735,7 +735,7 @@ func castRangeValue(dataType schemapb.DataType, value *planpb.GenericValue) (*pl func checkContainsElement(columnExpr *ExprWithType, op planpb.JSONContainsExpr_JSONOp, elementValue *planpb.GenericValue) error { if op != planpb.JSONContainsExpr_Contains && elementValue.GetArrayVal() == nil { - return fmt.Errorf("%s operation element must be an array", op.String()) + return merr.WrapErrQueryPlanMsg("%s operation element must be an array", op.String()) } if typeutil.IsArrayType(columnExpr.expr.GetColumnExpr().GetInfo().GetDataType()) { @@ -753,7 +753,7 @@ func checkContainsElement(columnExpr *ExprWithType, op planpb.JSONContainsExpr_J for _, value := range elements { valExpr := toValueExpr(value) if !canBeComparedDataType(arrayElementType, valExpr.dataType) { - return fmt.Errorf("%s operation can't compare between array element type: %s and %s", + return merr.WrapErrQueryPlanMsg("%s operation can't compare between array element type: %s and %s", op.String(), arrayElementType, valExpr.dataType) @@ -771,7 +771,7 @@ func parseJSONValue(value interface{}) (*planpb.GenericValue, schemapb.DataType, } else if floatValue, err := v.Float64(); err == nil { return NewFloat(floatValue), schemapb.DataType_Double, nil } else { - return nil, schemapb.DataType_None, fmt.Errorf("%v is a number, but couldn't convert it", value) + return nil, schemapb.DataType_None, merr.WrapErrQueryPlanMsg("%v is a number, but couldn't convert it", value) } case string: return NewString(v), schemapb.DataType_String, nil @@ -803,7 +803,7 @@ func parseJSONValue(value interface{}) (*planpb.GenericValue, schemapb.DataType, }, }, schemapb.DataType_Array, nil default: - return nil, schemapb.DataType_None, fmt.Errorf("%v is of unknown type: %T", value, v) + return nil, schemapb.DataType_None, merr.WrapErrQueryPlanMsg("%v is of unknown type: %T", value, v) } } @@ -869,7 +869,7 @@ func checkValidPoint(wktStr string) error { return err } if _, ok := g.(*geom.Point); !ok { - return fmt.Errorf("only supports POINT geometry: %s", wktStr) + return merr.WrapErrQueryPlanMsg("only supports POINT geometry: %s", wktStr) } return nil } @@ -877,7 +877,7 @@ func checkValidPoint(wktStr string) error { func parseISODuration(durationStr string) (*planpb.Interval, error) { matches := iso8601DurationRegex.FindStringSubmatch(durationStr) if matches == nil { - return nil, fmt.Errorf("invalid ISO 8601 duration: %s", durationStr) + return nil, merr.WrapErrQueryPlanMsg("invalid ISO 8601 duration: %s", durationStr) } interval := &planpb.Interval{} @@ -898,7 +898,7 @@ func parseISODuration(durationStr string) (*planpb.Interval, error) { if matches[matchIndex] != "" { value, err := strconv.ParseInt(matches[matchIndex], 10, 64) if err != nil { - return nil, fmt.Errorf("invalid %s value '%s' in duration: %w", target.unitName, matches[matchIndex], err) + return nil, merr.WrapErrQueryPlan(err, "invalid %s value '%s' in duration", target.unitName, matches[matchIndex]) } *target.fieldPtr = value } diff --git a/internal/proxy/accesslog/info/grpc_info_test.go b/internal/proxy/accesslog/info/grpc_info_test.go index 8eec8a93d1..b95ba677d4 100644 --- a/internal/proxy/accesslog/info/grpc_info_test.go +++ b/internal/proxy/accesslog/info/grpc_info_test.go @@ -124,7 +124,11 @@ func (s *GrpcAccessInfoSuite) TestErrorType() { result = Get(s.info, "$error_type") s.Equal(merr.InputError.String(), result[0]) - s.info.err = merr.ErrParameterInvalid + // ErrServiceInternal is a SystemError-typed sentinel. + // (ErrParameterInvalid was previously SystemError but became InputError after + // the 02-storage err-std PR; keep this branch on a still-SystemError sentinel + // so the test still covers the "system_error" classification path.) + s.info.err = merr.ErrServiceInternal result = Get(s.info, "$error_type") s.Equal(merr.SystemError.String(), result[0]) } diff --git a/internal/proxy/accesslog/info/restful_info.go b/internal/proxy/accesslog/info/restful_info.go index 0eb8a6cb50..7962e620fd 100644 --- a/internal/proxy/accesslog/info/restful_info.go +++ b/internal/proxy/accesslog/info/restful_info.go @@ -28,6 +28,7 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/requestutil" ) @@ -36,7 +37,11 @@ const ( ContextReturnCode = "code" ContextReturnMessage = "message" ContextRequest = "request" - ContextToken = "token" + // ContextErrorType carries the merr classification (input_error/system_error) + // set by the REST handler when it holds the error object; must match the key + // written in internal/distributed/proxy/httpserver. + ContextErrorType = "error_type" + ContextToken = "token" ) type RestfulInfo struct { @@ -159,7 +164,20 @@ func (i *RestfulInfo) ErrorMsg() string { } func (i *RestfulInfo) ErrorType() string { - return Unknown + if et, ok := i.ctx.Get(ContextErrorType); ok { + if s, ok := et.(string); ok { + return s + } + } + // Aborts that never reached the proxy call (request binding / local + // validation) only stored the wire code; recover the sentinel's baked + // classification from it. Success rows report "" like the gRPC access log. + if code, ok := i.ctx.Get(ContextReturnCode); ok { + if c, ok := code.(int32); ok && c != 0 { + return merr.ErrorTypeOfCode(c).String() + } + } + return "" } func (i *RestfulInfo) SdkVersion() string { diff --git a/internal/proxy/accesslog/info/util.go b/internal/proxy/accesslog/info/util.go index c020f152ae..09133cdf26 100644 --- a/internal/proxy/accesslog/info/util.go +++ b/internal/proxy/accesslog/info/util.go @@ -19,10 +19,8 @@ package info import ( "context" "encoding/json" - "fmt" "strings" - "github.com/cockroachdb/errors" "go.uber.org/atomic" "google.golang.org/grpc/metadata" @@ -31,6 +29,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/util" "github.com/milvus-io/milvus/pkg/v3/util/crypto" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) var ClusterPrefix atomic.String @@ -38,20 +37,20 @@ var ClusterPrefix atomic.String func getCurUserFromContext(ctx context.Context) (string, error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { - return "", errors.New("fail to get md from the context") + return "", merr.WrapErrParameterInvalidMsg("fail to get md from the context") } authorization, ok := md[strings.ToLower(util.HeaderAuthorize)] if !ok || len(authorization) < 1 { - return "", fmt.Errorf("fail to get authorization from the md, authorize:[%s]", util.HeaderAuthorize) + return "", merr.WrapErrParameterInvalidMsg("fail to get authorization from the md, authorize:[%s]", util.HeaderAuthorize) } token := authorization[0] rawToken, err := crypto.Base64Decode(token) if err != nil { - return "", fmt.Errorf("fail to decode the token, token: %s", token) + return "", merr.WrapErrParameterInvalidMsg("fail to decode the token, token: %s", token) } secrets := strings.SplitN(rawToken, util.CredentialSeparator, 2) if len(secrets) < 2 { - return "", fmt.Errorf("fail to get user info from the raw token, raw token: %s", rawToken) + return "", merr.WrapErrParameterInvalidMsg("fail to get user info from the raw token, raw token: %s", rawToken) } username := secrets[0] return username, nil diff --git a/internal/proxy/accesslog/minio_handler.go b/internal/proxy/accesslog/minio_handler.go index 356b55a262..722367415e 100644 --- a/internal/proxy/accesslog/minio_handler.go +++ b/internal/proxy/accesslog/minio_handler.go @@ -18,7 +18,6 @@ package accesslog import ( "context" - "fmt" "os" "path" "strings" @@ -30,6 +29,7 @@ import ( "go.uber.org/zap" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/retry" ) @@ -144,7 +144,7 @@ func newMinioClient(ctx context.Context, cfg config) (*minio.Client, error) { return err } } else { - return fmt.Errorf("bucket %s not Existed", cfg.bucketName) + return merr.WrapErrParameterInvalidMsg("bucket %s not Existed", cfg.bucketName) } } return nil diff --git a/internal/proxy/accesslog/util.go b/internal/proxy/accesslog/util.go index b79f1bc1c4..05631089f9 100644 --- a/internal/proxy/accesslog/util.go +++ b/internal/proxy/accesslog/util.go @@ -21,7 +21,6 @@ import ( "strings" "time" - "github.com/cockroachdb/errors" "github.com/gin-gonic/gin" "google.golang.org/grpc" @@ -29,6 +28,7 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v3/hook" "github.com/milvus-io/milvus/internal/proxy/accesslog/info" "github.com/milvus-io/milvus/internal/util/hookutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type AccessKey struct{} @@ -82,10 +82,10 @@ func join(path1, path2 string) string { func timeFromName(filename, prefix, ext string) (time.Time, error) { if !strings.HasPrefix(filename, prefix) { - return time.Time{}, errors.New("mismatched prefix") + return time.Time{}, merr.WrapErrParameterInvalidMsg("mismatched prefix") } if !strings.HasSuffix(filename, ext) { - return time.Time{}, errors.New("mismatched extension") + return time.Time{}, merr.WrapErrParameterInvalidMsg("mismatched extension") } ts := filename[len(prefix) : len(filename)-len(ext)] return time.Parse(timeNameFormat, ts) diff --git a/internal/proxy/accesslog/writer.go b/internal/proxy/accesslog/writer.go index 60589615a9..48971b015f 100644 --- a/internal/proxy/accesslog/writer.go +++ b/internal/proxy/accesslog/writer.go @@ -19,17 +19,16 @@ package accesslog import ( "bufio" "context" - "fmt" "io" "os" "path" "sync" "time" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -79,7 +78,7 @@ func (l *CacheWriter) Write(p []byte) (n int, err error) { l.mu.Lock() defer l.mu.Unlock() if l.closed { - return 0, errors.New("write to closed writer") + return 0, merr.WrapErrParameterInvalidMsg("write to closed writer") } return l.writer.Write(p) @@ -198,12 +197,12 @@ func (l *RotateWriter) Write(p []byte) (n int, err error) { defer l.mu.Unlock() if l.closed { - return 0, errors.New("write to closed writer") + return 0, merr.WrapErrParameterInvalidMsg("write to closed writer") } writeLen := int64(len(p)) if writeLen > l.max() { - return 0, fmt.Errorf( + return 0, merr.WrapErrParameterInvalidMsg( "write length %d exceeds maximum file size %d", writeLen, l.max(), ) } @@ -270,7 +269,7 @@ func (l *RotateWriter) openFileExistingOrNew() error { return l.openNewFile() } if err != nil { - return fmt.Errorf("file to get log file info: %s", err) + return merr.WrapErrIoFailed(filename, err) } file, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0o644) @@ -286,7 +285,7 @@ func (l *RotateWriter) openFileExistingOrNew() error { func (l *RotateWriter) openNewFile() error { err := os.MkdirAll(l.dir(), 0o744) if err != nil { - return fmt.Errorf("make directories for new log file filed: %s", err) + return merr.WrapErrIoFailed(l.dir(), err) } name := l.filename() @@ -296,7 +295,7 @@ func (l *RotateWriter) openNewFile() error { mode = info.Mode() newName := l.newBackupName() if err := os.Rename(name, newName); err != nil { - return fmt.Errorf("can't rename log file: %s", err) + return merr.WrapErrIoFailed(name, err) } log.Info("seal old log to: " + newName) if l.handler != nil { @@ -311,7 +310,7 @@ func (l *RotateWriter) openNewFile() error { f, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, mode) if err != nil { - return fmt.Errorf("can't open new logfile: %s", err) + return merr.WrapErrIoFailed(name, err) } l.file = f l.size = 0 @@ -429,7 +428,7 @@ func (l *RotateWriter) newBackupName() string { func (l *RotateWriter) oldLogFiles() ([]logInfo, error) { files, err := os.ReadDir(l.dir()) if err != nil { - return nil, fmt.Errorf("can't read log file directory: %s", err) + return nil, merr.WrapErrIoFailed(l.dir(), err) } logFiles := []logInfo{} diff --git a/internal/proxy/agg_reducer.go b/internal/proxy/agg_reducer.go index 36e8e72f79..7c6794efb3 100644 --- a/internal/proxy/agg_reducer.go +++ b/internal/proxy/agg_reducer.go @@ -2,7 +2,6 @@ package proxy import ( "context" - "fmt" "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" @@ -42,7 +41,7 @@ func (reducer *MilvusAggReducer) Reduce(results []*internalpb.RetrieveResults) ( for i := 0; i < fieldCount; i++ { indices := reducer.outputMap.IndexesAt(i) if len(indices) == 0 { - return nil, fmt.Errorf("no indices found for output field at index %d", i) + return nil, merr.WrapErrParameterInvalidMsg("no indices found for output field at index %d", i) } else if len(indices) == 1 { // Single index: direct copy (non-avg aggregation or group-by field) reOrganizedFieldDatas[i] = reducedFieldDatas[indices[0]] @@ -53,12 +52,12 @@ func (reducer *MilvusAggReducer) Reduce(results []*internalpb.RetrieveResults) ( countFieldData := reducedFieldDatas[indices[1]] avgFieldData, err := agg.ComputeAvgFromSumAndCount(sumFieldData, countFieldData) if err != nil { - return nil, fmt.Errorf("failed to compute avg for field %s: %w", reducer.outputMap.NameAt(i), err) + return nil, merr.Wrapf(err, "failed to compute avg for field %s", reducer.outputMap.NameAt(i)) } avgFieldData.FieldName = reducer.outputMap.NameAt(i) reOrganizedFieldDatas[i] = avgFieldData } else { - return nil, fmt.Errorf("unexpected number of indices (%d) for output field at index %d, expected 1 or 2", len(indices), i) + return nil, merr.WrapErrParameterInvalidMsg("unexpected number of indices (%d) for output field at index %d, expected 1 or 2", len(indices), i) } } return &milvuspb.QueryResults{FieldsData: reOrganizedFieldDatas, Status: merr.Success()}, nil diff --git a/internal/proxy/channels_mgr.go b/internal/proxy/channels_mgr.go index 29732b6a50..b4e70b6c67 100644 --- a/internal/proxy/channels_mgr.go +++ b/internal/proxy/channels_mgr.go @@ -18,7 +18,6 @@ package proxy import ( "context" - "fmt" "strconv" "sync" @@ -67,7 +66,9 @@ func removeDuplicate(ss []string) []string { func newChannels(vchans []vChan, pchans []pChan) (channelInfos, error) { if len(vchans) != len(pchans) { log.Error("physical channels mismatch virtual channels", zap.Int("len(VirtualChannelNames)", len(vchans)), zap.Int("len(PhysicalChannelNames)", len(pchans))) - return channelInfos{}, fmt.Errorf("physical channels mismatch virtual channels, len(VirtualChannelNames): %v, len(PhysicalChannelNames): %v", len(vchans), len(pchans)) + // Channel lists come from DescribeCollection (coordinator-allocated + // metadata), never from the caller: a mismatch is a server-side bug. + return channelInfos{}, merr.WrapErrServiceInternalMsg("physical channels mismatch virtual channels, len(VirtualChannelNames): %v, len(PhysicalChannelNames): %v", len(vchans), len(pchans)) } /* // remove duplicate physical channels. @@ -124,7 +125,7 @@ func (mgr *singleTypeChannelsMgr) getAllChannels(collectionID UniqueID) (channel return infos.channelInfos, nil } - return channelInfos{}, fmt.Errorf("collection not found in channels manager: %d", collectionID) + return channelInfos{}, merr.WrapErrParameterInvalidMsg("collection not found in channels manager: %d", collectionID) } func (mgr *singleTypeChannelsMgr) ensureChannels(collectionID UniqueID) (channelInfos, error) { diff --git a/internal/proxy/connection/util.go b/internal/proxy/connection/util.go index 0a9547c038..920a75572c 100644 --- a/internal/proxy/connection/util.go +++ b/internal/proxy/connection/util.go @@ -2,10 +2,8 @@ package connection import ( "context" - "fmt" "strconv" - "github.com/cockroachdb/errors" "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/metadata" @@ -13,6 +11,7 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus/pkg/v3/util" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) func ZapClientInfo(info *commonpb.ClientInfo) []zap.Field { @@ -34,15 +33,15 @@ func ZapClientInfo(info *commonpb.ClientInfo) []zap.Field { func GetIdentifierFromContext(ctx context.Context) (int64, error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { - return 0, errors.New("fail to get metadata from the context") + return 0, merr.WrapErrParameterInvalidMsg("fail to get metadata from the context") } identifierContent, ok := md[util.IdentifierKey] if !ok || len(identifierContent) < 1 { - return 0, errors.New("no identifier found in metadata") + return 0, merr.WrapErrParameterInvalidMsg("no identifier found in metadata") } identifier, err := strconv.ParseInt(identifierContent[0], 10, 64) if err != nil { - return 0, fmt.Errorf("failed to parse identifier: %s, error: %s", identifierContent[0], err.Error()) + return 0, merr.WrapErrParameterInvalidMsg("failed to parse identifier: %s, error: %s", identifierContent[0], err.Error()) } return identifier, nil } diff --git a/internal/proxy/function_task.go b/internal/proxy/function_task.go index dd89968155..c0d7fb18d4 100644 --- a/internal/proxy/function_task.go +++ b/internal/proxy/function_task.go @@ -18,7 +18,6 @@ package proxy import ( "context" - "fmt" "go.uber.org/zap" "google.golang.org/protobuf/proto" @@ -95,11 +94,11 @@ func (t *addCollectionFunctionTask) Name() string { func (t *addCollectionFunctionTask) PreExecute(ctx context.Context) error { if t.FunctionSchema == nil { - return fmt.Errorf("function schema is empty") + return merr.WrapErrParameterInvalidMsg("function schema is empty") } if t.FunctionSchema.Type == schemapb.FunctionType_BM25 { - return fmt.Errorf("currently does not support adding BM25 function") + return merr.WrapErrParameterInvalidMsg("currently does not support adding BM25 function") } coll, err := getCollectionInfo(ctx, t.GetDbName(), t.GetCollectionName()) if err != nil { @@ -182,13 +181,13 @@ func (t *alterCollectionFunctionTask) Name() string { func (t *alterCollectionFunctionTask) PreExecute(ctx context.Context) error { if t.FunctionSchema == nil { - return fmt.Errorf("function schema is empty") + return merr.WrapErrParameterInvalidMsg("function schema is empty") } if t.FunctionSchema.Type == schemapb.FunctionType_BM25 { - return fmt.Errorf("currently does not support alter BM25 function") + return merr.WrapErrParameterInvalidMsg("currently does not support alter BM25 function") } if t.FunctionName != t.FunctionSchema.Name { - return fmt.Errorf("invalid function config, name not match") + return merr.WrapErrParameterInvalidMsg("invalid function config, name not match") } coll, err := getCollectionInfo(ctx, t.GetDbName(), t.GetCollectionName()) if err != nil { @@ -206,7 +205,7 @@ func (t *alterCollectionFunctionTask) PreExecute(ctx context.Context) error { for _, fSchema := range coll.schema.Functions { if t.FunctionName == fSchema.Name { if fSchema.Type == schemapb.FunctionType_BM25 { - return fmt.Errorf("currently does not support alter BM25 function") + return merr.WrapErrParameterInvalidMsg("currently does not support alter BM25 function") } newFunctions = append(newFunctions, t.FunctionSchema) funcExist = true @@ -215,7 +214,7 @@ func (t *alterCollectionFunctionTask) PreExecute(ctx context.Context) error { } } if !funcExist { - return fmt.Errorf("function %s not found", t.FunctionName) + return merr.WrapErrParameterInvalidMsg("function %s not found", t.FunctionName) } newColl := proto.Clone(coll.schema.CollectionSchema).(*schemapb.CollectionSchema) @@ -319,7 +318,7 @@ func (t *dropCollectionFunctionTask) Execute(ctx context.Context) error { } if t.fSchema.Type == schemapb.FunctionType_BM25 { - return fmt.Errorf("currently does not support droping BM25 function") + return merr.WrapErrParameterInvalidMsg("currently does not support droping BM25 function") } var err error diff --git a/internal/proxy/highlighter.go b/internal/proxy/highlighter.go index 5c11dd7669..00451fdada 100644 --- a/internal/proxy/highlighter.go +++ b/internal/proxy/highlighter.go @@ -6,7 +6,6 @@ import ( "strconv" "strings" - "github.com/cockroachdb/errors" "github.com/samber/lo" "github.com/tidwall/gjson" "go.opentelemetry.io/otel/trace" @@ -342,7 +341,7 @@ func (op *lexicalHighlightOperator) run(ctx context.Context, span trace.Span, in datas := resultData.GetFieldsData() if len(datas) == 0 { - return nil, errors.Errorf("get highlight failed, field data is empty for non-empty search result") + return nil, merr.WrapErrServiceInternalMsg("get highlight failed, field data is empty for non-empty search result") } req := &querypb.GetHighlightRequest{ @@ -353,7 +352,7 @@ func (op *lexicalHighlightOperator) run(ctx context.Context, span trace.Span, in for _, task := range req.GetTasks() { textFieldDatas, ok := lo.Find(datas, func(data *schemapb.FieldData) bool { return data.FieldId == task.GetFieldId() }) if !ok { - return nil, errors.Errorf("get highlight failed, text field not in output field %s: %d", task.GetFieldName(), task.GetFieldId()) + return nil, merr.WrapErrParameterInvalidMsg("get highlight failed, text field not in output field %s: %d", task.GetFieldName(), task.GetFieldId()) } texts := textFieldDatas.GetScalars().GetStringData().GetData() task.Texts = append(task.Texts, texts...) @@ -374,7 +373,7 @@ func (op *lexicalHighlightOperator) run(ctx context.Context, span trace.Span, in if nameFieldID > 0 { analyzerFieldDatas, ok := lo.Find(datas, func(data *schemapb.FieldData) bool { return data.FieldId == nameFieldID }) if !ok { - return nil, errors.Errorf("get highlight failed, analyzer name field: %d for multi analyzer not in output field", nameFieldID) + return nil, merr.WrapErrParameterInvalidMsg("get highlight failed, analyzer name field: %d for multi analyzer not in output field", nameFieldID) } task.AnalyzerNames = append(task.AnalyzerNames, analyzerFieldDatas.GetScalars().GetStringData().GetData()...) } @@ -486,7 +485,7 @@ func (op *semanticHighlightOperator) run(ctx context.Context, span trace.Span, i datas := resultData.GetFieldsData() if len(datas) == 0 { - return nil, errors.Errorf("get highlight failed, field data is empty for non-empty search result") + return nil, merr.WrapErrServiceInternalMsg("get highlight failed, field data is empty for non-empty search result") } highlightResults := []*commonpb.HighlightResult{} topks := result.Results.GetTopks() @@ -495,7 +494,7 @@ func (op *semanticHighlightOperator) run(ctx context.Context, span trace.Span, i for _, fieldID := range op.highlight.FieldIDs() { fieldDatas, ok := lo.Find(datas, func(data *schemapb.FieldData) bool { return data.FieldId == fieldID }) if !ok { - return nil, errors.Errorf("get highlight failed, text field not in output field %d", fieldID) + return nil, merr.WrapErrParameterInvalidMsg("get highlight failed, text field not in output field %d", fieldID) } texts := fieldDatas.GetScalars().GetStringData().GetData() fieldName := op.highlight.GetFieldName(fieldID) @@ -515,17 +514,17 @@ func (op *semanticHighlightOperator) run(ctx context.Context, span trace.Span, i return data.FieldId == dynamicFieldID || data.FieldName == common.MetaFieldName }) if !ok { - return nil, errors.Errorf("get highlight failed, dynamic field ($meta) not in output field") + return nil, merr.WrapErrParameterInvalidMsg("get highlight failed, dynamic field ($meta) not in output field") } // Safely get JSON data with nil checks scalars := metaFieldData.GetScalars() if scalars == nil { - return nil, errors.Errorf("get highlight failed, dynamic field ($meta) has no scalar data") + return nil, merr.WrapErrServiceInternalMsg("get highlight failed, dynamic field ($meta) has no scalar data") } jsonData := scalars.GetJsonData() if jsonData == nil { - return nil, errors.Errorf("get highlight failed, dynamic field ($meta) has no JSON data") + return nil, merr.WrapErrServiceInternalMsg("get highlight failed, dynamic field ($meta) has no JSON data") } jsonDataBytes := jsonData.GetData() dynFieldNames := op.highlight.DynamicFieldNames() @@ -559,7 +558,7 @@ func (op *semanticHighlightOperator) processFieldHighlight(ctx context.Context, } if len(highlights) != len(scores) { - return nil, errors.Errorf("Highlights size must equal to scores size, but got highlights size [%d], scores size [%d]", len(highlights), len(scores)) + return nil, merr.WrapErrServiceInternalMsg("Highlights size must equal to scores size, but got highlights size [%d], scores size [%d]", len(highlights), len(scores)) } result := &commonpb.HighlightResult{ @@ -587,7 +586,7 @@ func extractMultipleDynamicFieldTexts(jsonDataBytes [][]byte, fieldNames []strin } if !gjson.ValidBytes(jsonBytes) { - return nil, errors.Errorf("failed to unmarshal JSON at index %d: invalid JSON", i) + return nil, merr.WrapErrDataIntegrityMsg("failed to unmarshal JSON at index %d: invalid JSON", i) } for _, fieldName := range fieldNames { @@ -598,7 +597,7 @@ func extractMultipleDynamicFieldTexts(jsonDataBytes [][]byte, fieldNames []strin } if r.Type != gjson.String { - return nil, errors.Errorf("dynamic field %s is not a string type, got %s", fieldName, r.Type.String()) + return nil, merr.WrapErrParameterInvalidMsg("dynamic field %s is not a string type, got %s", fieldName, r.Type.String()) } result[fieldName][i] = r.String() } diff --git a/internal/proxy/hook_interceptor.go b/internal/proxy/hook_interceptor.go index addf140b56..0f01f17ecb 100644 --- a/internal/proxy/hook_interceptor.go +++ b/internal/proxy/hook_interceptor.go @@ -37,7 +37,7 @@ func HookInterceptor(ctx context.Context, req any, userName, fullMethod string, log.Info("hook mock", zap.String("user", userName), zap.String("full method", fullMethod), zap.Error(err)) metrics.ProxyHookFunc.WithLabelValues(metrics.HookMock, fullMethod).Inc() - updateProxyFunctionCallMetric(fullMethod) + updateProxyFunctionCallMetric(fullMethod, err) if err != nil { // NOTE: don't use the merr, because it will cause the wrong retry behavior in the sdk err = status.Error(codes.InvalidArgument, "detail: "+err.Error()) @@ -49,7 +49,7 @@ func HookInterceptor(ctx context.Context, req any, userName, fullMethod string, log.Warn("hook before error", zap.String("user", userName), zap.String("full method", fullMethod), zap.Any("request", req), zap.Error(err)) metrics.ProxyHookFunc.WithLabelValues(metrics.HookBefore, fullMethod).Inc() - updateProxyFunctionCallMetric(fullMethod) + updateProxyFunctionCallMetric(fullMethod, err) // NOTE: don't use the merr, because it will cause the wrong retry behavior in the sdk return nil, status.Error(codes.InvalidArgument, "detail: "+err.Error()) } @@ -58,19 +58,19 @@ func HookInterceptor(ctx context.Context, req any, userName, fullMethod string, log.Warn("hook after error", zap.String("user", userName), zap.String("full method", fullMethod), zap.Any("request", req), zap.Error(err)) metrics.ProxyHookFunc.WithLabelValues(metrics.HookAfter, fullMethod).Inc() - updateProxyFunctionCallMetric(fullMethod) + updateProxyFunctionCallMetric(fullMethod, err) // NOTE: don't use the merr, because it will cause the wrong retry behavior in the sdk return nil, status.Error(codes.InvalidArgument, "detail: "+err.Error()) } return realResp, realErr } -func updateProxyFunctionCallMetric(fullMethod string) { +func updateProxyFunctionCallMetric(fullMethod string, err error) { strs := strings.Split(fullMethod, "/") method := strs[len(strs)-1] if method == "" { return } metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.TotalLabel, "", "").Inc() - metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.FailLabel, "", "").Inc() + metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failMetricLabel(err), "", "").Inc() } diff --git a/internal/proxy/hook_interceptor_test.go b/internal/proxy/hook_interceptor_test.go index 70d82de16a..d45a076264 100644 --- a/internal/proxy/hook_interceptor_test.go +++ b/internal/proxy/hook_interceptor_test.go @@ -9,6 +9,7 @@ import ( "google.golang.org/grpc" "github.com/milvus-io/milvus/internal/util/hookutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type mockHook struct { @@ -131,8 +132,8 @@ func TestHookInterceptor(t *testing.T) { func TestUpdateProxyFunctionCallMetric(t *testing.T) { assert.NotPanics(t, func() { - updateProxyFunctionCallMetric("/milvus.proto.milvus.MilvusService/Flush") - updateProxyFunctionCallMetric("Flush") - updateProxyFunctionCallMetric("") + updateProxyFunctionCallMetric("/milvus.proto.milvus.MilvusService/Flush", errors.New("mock hook error")) + updateProxyFunctionCallMetric("Flush", merr.WrapErrParameterInvalidMsg("mock input error")) + updateProxyFunctionCallMetric("", nil) }) } diff --git a/internal/proxy/impl.go b/internal/proxy/impl.go index 0c63b1fe72..16314b14b4 100644 --- a/internal/proxy/impl.go +++ b/internal/proxy/impl.go @@ -957,7 +957,7 @@ func (node *Proxy) BatchDescribeCollection(ctx context.Context, request *milvusp collectionNames := request.GetCollectionName() if len(collectionNames) == 0 { return &milvuspb.BatchDescribeCollectionResponse{ - Status: merr.Status(merr.WrapErrParameterInvalidMsg("collection names cannot be empty")), + Status: merr.Status(merr.WrapErrParameterMissingMsg("collection names cannot be empty")), }, nil } @@ -2818,7 +2818,7 @@ func (node *Proxy) Insert(ctx context.Context, request *milvuspb.InsertRequest) if err := node.sched.dmQueue.Enqueue(it); err != nil { log.Warn("Failed to enqueue insert task: " + err.Error()) - return constructFailedResponse(merr.WrapErrAsInputErrorWhen(err, merr.ErrCollectionNotFound, merr.ErrDatabaseNotFound)), nil + return constructFailedResponse(err), nil } log.Debug("Detail of insert request in Proxy") @@ -3760,7 +3760,9 @@ func (node *Proxy) handleIfSearchByPK(ctx context.Context, request *milvuspb.Sea annField := typeutil.GetFieldByName(collectionInfo.schema.CollectionSchema, annsFieldName) if annField == nil { - return nil, merr.WrapErrFieldNotFound(annsFieldName, "vector field not found in schema") + // annsFieldName comes from the user's search request; a missing field is + // the user's input error. + return nil, merr.WrapErrAsInputError(merr.WrapErrFieldNotFound(annsFieldName, "vector field not found in schema")) } if annField.GetDataType() == schemapb.DataType_ArrayOfVector { @@ -3781,7 +3783,7 @@ func (node *Proxy) handleIfSearchByPK(ctx context.Context, request *milvuspb.Sea } else { // Vector search: validate and fetch the vector field if !typeutil.IsVectorType(annField.GetDataType()) { - return nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("field (%s) to search is not of vector data type", annsFieldName)) + return nil, merr.WrapErrParameterInvalidMsg("field (%s) to search is not of vector data type", annsFieldName) } fieldToFetch = annsFieldName } @@ -5485,7 +5487,7 @@ func (node *Proxy) CreateCredential(ctx context.Context, req *milvuspb.CreateCre if err != nil { log.Error("decode password fail", zap.Error(err)) - err = errors.Wrap(err, "decode password fail") + err = merr.WrapErrParameterInvalidErr(err, "decode password fail") return merr.Status(err), nil } if err = ValidatePassword(rawPassword); err != nil { @@ -5497,7 +5499,7 @@ func (node *Proxy) CreateCredential(ctx context.Context, req *milvuspb.CreateCre if err != nil { log.Error("encrypt password fail", zap.Error(err)) - err = errors.Wrap(err, "encrypt password failed") + err = merr.WrapErrServiceInternalErr(err, "encrypt password failed") return merr.Status(err), nil } if req.Base == nil { @@ -5535,14 +5537,14 @@ func (node *Proxy) UpdateCredential(ctx context.Context, req *milvuspb.UpdateCre if err != nil { log.Error("decode old password fail", zap.Error(err)) - err = errors.Wrap(err, "decode old password failed") + err = merr.WrapErrParameterInvalidErr(err, "decode old password failed") return merr.Status(err), nil } rawNewPassword, err := crypto.Base64Decode(req.NewPassword) if err != nil { log.Error("decode password fail", zap.Error(err)) - err = errors.Wrap(err, "decode password failed") + err = merr.WrapErrParameterInvalidErr(err, "decode password failed") return merr.Status(err), nil } // valid new password @@ -5570,7 +5572,7 @@ func (node *Proxy) UpdateCredential(ctx context.Context, req *milvuspb.UpdateCre if err != nil { log.Error("encrypt password fail", zap.Error(err)) - err = errors.Wrap(err, "encrypt password failed") + err = merr.WrapErrServiceInternalErr(err, "encrypt password failed") return merr.Status(err), nil } if req.Base == nil { @@ -5814,19 +5816,19 @@ func (node *Proxy) SelectUser(ctx context.Context, req *milvuspb.SelectUserReque func (node *Proxy) validPrivilegeParams(req *milvuspb.OperatePrivilegeRequest) error { if req.Entity == nil { - return errors.New("the entity in the request is nil") + return merr.WrapErrParameterInvalidMsg("the entity in the request is nil") } if req.Entity.Grantor == nil { - return errors.New("the grantor entity in the grant entity is nil") + return merr.WrapErrParameterInvalidMsg("the grantor entity in the grant entity is nil") } if req.Entity.Grantor.Privilege == nil { - return errors.New("the privilege entity in the grantor entity is nil") + return merr.WrapErrParameterInvalidMsg("the privilege entity in the grantor entity is nil") } if err := ValidatePrivilege(req.Entity.Grantor.Privilege.Name); err != nil { return err } if req.Entity.Object == nil { - return errors.New("the resource entity in the grant entity is nil") + return merr.WrapErrParameterInvalidMsg("the resource entity in the grant entity is nil") } if err := ValidateObjectType(req.Entity.Object.Name); err != nil { return err @@ -5835,7 +5837,7 @@ func (node *Proxy) validPrivilegeParams(req *milvuspb.OperatePrivilegeRequest) e return err } if req.Entity.Role == nil { - return errors.New("the object entity in the grant entity is nil") + return merr.WrapErrParameterInvalidMsg("the object entity in the grant entity is nil") } if err := ValidateRoleName(req.Entity.Role.Name); err != nil { return err @@ -6698,8 +6700,11 @@ func (node *Proxy) Connect(ctx context.Context, request *milvuspb.ConnectRequest if !funcutil.SliceContain(resp.GetDbNames(), db) { log.Info("connect failed, target database not exist") + // db is the caller-supplied database name; this Connect handshake builds + // the not-found directly (it does not go through GetDatabaseInfo's marking + // chokepoint), so stamp InputError here. return &milvuspb.ConnectResponse{ - Status: merr.Status(merr.WrapErrDatabaseNotFound(db)), + Status: merr.Status(merr.WrapErrAsInputError(merr.WrapErrDatabaseNotFound(db))), }, nil } @@ -7824,7 +7829,7 @@ func (node *Proxy) GetRefreshExternalCollectionProgress(ctx context.Context, req // Validate job ID if req.GetJobId() == 0 { return &milvuspb.GetRefreshExternalCollectionProgressResponse{ - Status: merr.Status(merr.WrapErrParameterInvalidMsg("job_id is required")), + Status: merr.Status(merr.WrapErrParameterMissingMsg("job_id is required")), }, nil } diff --git a/internal/proxy/meta_cache.go b/internal/proxy/meta_cache.go index 26c9c37377..0c5ea1edda 100644 --- a/internal/proxy/meta_cache.go +++ b/internal/proxy/meta_cache.go @@ -189,7 +189,7 @@ func (s *schemaInfo) IsPartitionKeyCollection() bool { func (s *schemaInfo) GetPkField() (*schemapb.FieldSchema, error) { if s.pkField == nil { - return nil, merr.WrapErrServiceInternal("pk field not found") + return nil, merr.WrapErrFieldNotFound("pk field") } return s.pkField, nil } @@ -576,7 +576,16 @@ func (m *MetaCache) UpdateByName(ctx context.Context, database, collectionName s collection, err, _ := m.sfGlobal.Do(buildSfKeyByName(database, collectionName), func() (*collectionInfo, error) { return m.update(ctx, database, collectionName, 0) }) - return collection, err + // Name resolution failed -> the caller named a collection/db that does not + // exist, which is the user's input error, not a system fault. Mark it here, + // the single name-resolution chokepoint shared by every name-based + // GetCollection* path (data-plane and control-plane proxy tasks), so the + // error_type is Input. The id-based UpdateByID path is deliberately left as + // SystemError: a by-id lookup miss is an internal/component query (e.g. + // rootcoord answering another component by collectionID), not user input. + // The sentinel itself stays SystemError so datacoord's internal retry.Do + // recovery loops still retry a transient not-found. + return collection, merr.WrapErrAsInputErrorWhen(err, merr.ErrCollectionNotFound, merr.ErrDatabaseNotFound) } func (m *MetaCache) UpdateByID(ctx context.Context, database string, collectionID UniqueID) (*collectionInfo, error) { @@ -869,7 +878,12 @@ func (m *MetaCache) GetPartitionInfo(ctx context.Context, database, collectionNa if ok && entry.state == EntryStateActive && entry.value != nil { return entry.value, nil } - return nil, merr.WrapErrPartitionNotFound(partitionName) + // partitionName is caller-supplied; a failed name resolution is the user's + // input error, not a system fault. Mark it here, the single partition-name + // chokepoint (GetPartitionID also routes through here), so the proxy reports + // InputError without per-task wrappers. ErrPartitionNotFound stays + // SystemError by default so id-based lookups (GetPartitionName) are unaffected. + return nil, merr.WrapErrAsInputError(merr.WrapErrPartitionNotFound(partitionName)) } func (m *MetaCache) GetPartitionsIndex(ctx context.Context, database, collectionName string) ([]string, error) { @@ -977,13 +991,15 @@ func (m *MetaCache) showPartitions(ctx context.Context, dbName string, collectio return nil, err } + // The response shape is produced by the coordinator, not the caller: a + // misaligned array is backend metadata inconsistency, not user input. if len(partitions.PartitionIDs) != len(partitions.PartitionNames) { - return nil, fmt.Errorf("partition ids len: %d doesn't equal Partition name len %d", + return nil, merr.WrapErrServiceInternalMsg("partition ids len: %d doesn't equal Partition name len %d", len(partitions.PartitionIDs), len(partitions.PartitionNames)) } if len(partitions.PartitionNames) != len(partitions.CreatedTimestamps) || len(partitions.PartitionNames) != len(partitions.CreatedUtcTimestamps) { - return nil, merr.WrapErrParameterInvalidMsg( + return nil, merr.WrapErrServiceInternalMsg( "partition names and timestamps number is not aligned, response: %s", partitions.String(), ) @@ -1182,7 +1198,13 @@ func (m *MetaCache) GetDatabaseInfo(ctx context.Context, database string) (*data return dbInfo, nil }) - return dbInfo, err + // Symmetric with UpdateByName: a failed database-name resolution means the + // caller named a database that does not exist — the user's input error, not a + // system fault. Mark it here, the single database-name chokepoint, so every + // caller (data-plane and control-plane proxy tasks) gets InputError without a + // per-callsite wrapper. The sentinel stays SystemError so internal id-based + // lookups and retry loops elsewhere are unaffected. + return dbInfo, merr.WrapErrAsInputErrorWhen(err, merr.ErrDatabaseNotFound) } func (m *MetaCache) safeGetDBInfo(database string) *databaseInfo { @@ -1209,7 +1231,7 @@ func (m *MetaCache) AllocID(ctx context.Context) (int64, error) { } if resp.GetStatus().GetCode() != 0 { log.Warn("Refreshing ID cache from rootcoord failed", zap.String("failed detail", resp.GetStatus().GetDetail())) - return 0, merr.WrapErrServiceInternal(resp.GetStatus().GetDetail()) + return 0, merr.Error(resp.GetStatus()) } m.IDStart, m.IDCount = resp.GetID(), int64(resp.GetCount()) m.IDIndex = 0 diff --git a/internal/proxy/meta_cache_testonly.go b/internal/proxy/meta_cache_testonly.go index 23af516dd4..4171c29e57 100644 --- a/internal/proxy/meta_cache_testonly.go +++ b/internal/proxy/meta_cache_testonly.go @@ -24,7 +24,6 @@ package proxy import ( "context" - "github.com/cockroachdb/errors" "github.com/stretchr/testify/mock" "github.com/milvus-io/milvus/internal/mocks" @@ -54,8 +53,8 @@ func InitEmptyGlobalCache() { var err error emptyMock := common.NewEmptyMockT() mixcoord := mocks.NewMockMixCoordClient(emptyMock) - mixcoord.EXPECT().DescribeCollection(mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("collection not found")) - mixcoord.EXPECT().DescribeAlias(mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("alias not found")) + mixcoord.EXPECT().DescribeCollection(mock.Anything, mock.Anything, mock.Anything).Return(nil, merr.WrapErrParameterInvalidMsg("collection not found")) + mixcoord.EXPECT().DescribeAlias(mock.Anything, mock.Anything, mock.Anything).Return(nil, merr.WrapErrParameterInvalidMsg("alias not found")) globalMetaCache, err = NewMetaCache(mixcoord) if err != nil { panic(err) diff --git a/internal/proxy/privilege/cache.go b/internal/proxy/privilege/cache.go index 3de88dd50d..45795f78e5 100644 --- a/internal/proxy/privilege/cache.go +++ b/internal/proxy/privilege/cache.go @@ -18,11 +18,9 @@ package privilege import ( "context" - "fmt" "sync" "sync/atomic" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" @@ -198,7 +196,7 @@ func (m *privilegeCache) RefreshPolicyInfo(op typeutil.CacheOp) (err error) { m.mu.Lock() defer m.mu.Unlock() if op.OpKey == "" { - return errors.New("empty op key") + return merr.WrapErrParameterInvalidMsg("empty op key") } } @@ -216,7 +214,7 @@ func (m *privilegeCache) RefreshPolicyInfo(op typeutil.CacheOp) (err error) { case typeutil.CacheAddUserToRole: user, role, err := funcutil.DecodeUserRoleCache(op.OpKey) if err != nil { - return fmt.Errorf("invalid opKey, fail to decode, op_type: %d, op_key: %s", int(op.OpType), op.OpKey) + return merr.WrapErrParameterInvalidMsg("invalid opKey, fail to decode, op_type: %d, op_key: %s", int(op.OpType), op.OpKey) } if m.userToRoles[user] == nil { m.userToRoles[user] = make(map[string]struct{}) @@ -225,7 +223,7 @@ func (m *privilegeCache) RefreshPolicyInfo(op typeutil.CacheOp) (err error) { case typeutil.CacheRemoveUserFromRole: user, role, err := funcutil.DecodeUserRoleCache(op.OpKey) if err != nil { - return fmt.Errorf("invalid opKey, fail to decode, op_type: %d, op_key: %s", int(op.OpType), op.OpKey) + return merr.WrapErrParameterInvalidMsg("invalid opKey, fail to decode, op_type: %d, op_key: %s", int(op.OpType), op.OpKey) } if m.userToRoles[user] != nil { delete(m.userToRoles[user], role) @@ -262,7 +260,7 @@ func (m *privilegeCache) RefreshPolicyInfo(op typeutil.CacheOp) (err error) { m.privilegeInfos = make(map[string]struct{}) m.unsafeInitPolicyInfo(resp.PolicyInfos, resp.UserRoles) default: - return fmt.Errorf("invalid opType, op_type: %d, op_key: %s", int(op.OpType), op.OpKey) + return merr.WrapErrParameterInvalidMsg("invalid opType, op_type: %d, op_key: %s", int(op.OpType), op.OpKey) } return nil } diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index 4a4ad6d37a..d204dd2116 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -22,7 +22,6 @@ import ( "sync" "time" - "github.com/cockroachdb/errors" "github.com/google/uuid" "github.com/hashicorp/golang-lru/v2/expirable" "go.uber.org/atomic" @@ -42,6 +41,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/internalpb" "github.com/milvus-io/milvus/pkg/v3/util/expr" "github.com/milvus-io/milvus/pkg/v3/util/logutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/metricsinfo" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/ratelimitutil" @@ -165,7 +165,7 @@ func (node *Proxy) Register() error { func (node *Proxy) initSession() error { node.session = sessionutil.NewSession(node.ctx) if node.session == nil { - return errors.New("new session failed, maybe etcd cannot be connected") + return merr.WrapErrServiceNotReadyMsg("new session failed, maybe etcd cannot be connected") } node.session.Init(typeutil.ProxyRole, node.address, false) sessionutil.SaveServerInfo(typeutil.ProxyRole, node.session.ServerID) @@ -378,7 +378,7 @@ func (node *Proxy) SetQueryNodeCreator(f func(ctx context.Context, addr string, // GetRateLimiter returns the rateLimiter in Proxy. func (node *Proxy) GetRateLimiter() (types.Limiter, error) { if node.simpleLimiter == nil { - return nil, errors.New("nil rate limiter in Proxy") + return nil, merr.WrapErrParameterInvalidMsg("nil rate limiter in Proxy") } return node.simpleLimiter, nil } diff --git a/internal/proxy/proxy_test.go b/internal/proxy/proxy_test.go index 60d84a1dce..71f6c1c052 100644 --- a/internal/proxy/proxy_test.go +++ b/internal/proxy/proxy_test.go @@ -3752,7 +3752,7 @@ func TestProxy(t *testing.T) { resp, err := proxy.Upsert(ctx, req) assert.NoError(t, err) - assert.Equal(t, commonpb.ErrorCode_UnexpectedError, resp.GetStatus().GetErrorCode()) + assert.Equal(t, commonpb.ErrorCode_IllegalArgument, resp.GetStatus().GetErrorCode()) assert.Equal(t, 0, len(resp.SuccIndex)) assert.Equal(t, rowNum, len(resp.ErrIndex)) assert.Equal(t, int64(0), resp.UpsertCnt) diff --git a/internal/proxy/query_pipeline.go b/internal/proxy/query_pipeline.go index 371e6c4c7e..058c2e5bda 100644 --- a/internal/proxy/query_pipeline.go +++ b/internal/proxy/query_pipeline.go @@ -18,7 +18,6 @@ package proxy import ( "context" - "fmt" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" @@ -282,7 +281,7 @@ func newReduceByGroupsOperator( for i := 0; i < fieldCount; i++ { indices := outputMap.IndexesAt(i) if len(indices) == 0 { - return nil, fmt.Errorf("no indices found for output field '%s'", outputMap.NameAt(i)) + return nil, merr.WrapErrParameterInvalidMsg("no indices found for output field '%s'", outputMap.NameAt(i)) } else if len(indices) == 1 { reOrganizedFieldDatas[i] = reducedFieldDatas[indices[0]] reOrganizedFieldDatas[i].FieldName = outputMap.NameAt(i) @@ -345,7 +344,7 @@ func newAggRemapOperator(outputMap *agg.AggregationFieldMap) queryutil.Operator for i := 0; i < fieldCount; i++ { indices := outputMap.IndexesAt(i) if len(indices) == 0 { - return nil, fmt.Errorf("no indices found for output field '%s'", outputMap.NameAt(i)) + return nil, merr.WrapErrParameterInvalidMsg("no indices found for output field '%s'", outputMap.NameAt(i)) } else if len(indices) == 1 { remapped[i] = rawFields[indices[0]] remapped[i].FieldName = outputMap.NameAt(i) diff --git a/internal/proxy/repack_func.go b/internal/proxy/repack_func.go index 7a5c181be1..9e4edcd4b0 100644 --- a/internal/proxy/repack_func.go +++ b/internal/proxy/repack_func.go @@ -17,9 +17,8 @@ package proxy import ( - "fmt" - "github.com/milvus-io/milvus/pkg/v3/mq/msgstream" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // insertRepackFunc deprecated, use defaultInsertRepackFunc instead. @@ -28,7 +27,7 @@ func insertRepackFunc( hashKeys [][]int32, ) (map[int32]*msgstream.MsgPack, error) { if len(hashKeys) < len(tsMsgs) { - return nil, fmt.Errorf( + return nil, merr.WrapErrParameterInvalidMsg( "the length of hash keys (%d) is less than the length of messages (%d)", len(hashKeys), len(tsMsgs), @@ -46,7 +45,7 @@ func insertRepackFunc( } result[key].Msgs = append(result[key].Msgs, request) } else { - return nil, fmt.Errorf("no hash key for %dth message", i) + return nil, merr.WrapErrParameterInvalidMsg("no hash key for %dth message", i) } } @@ -59,7 +58,7 @@ func defaultInsertRepackFunc( hashKeys [][]int32, ) (map[int32]*msgstream.MsgPack, error) { if len(hashKeys) < len(tsMsgs) { - return nil, fmt.Errorf( + return nil, merr.WrapErrParameterInvalidMsg( "the length of hash keys (%d) is less than the length of messages (%d)", len(hashKeys), len(tsMsgs), @@ -70,7 +69,7 @@ func defaultInsertRepackFunc( pack := make(map[int32]*msgstream.MsgPack) for idx, msg := range tsMsgs { if len(hashKeys[idx]) <= 0 { - return nil, fmt.Errorf("no hash key for %dth message", idx) + return nil, merr.WrapErrParameterInvalidMsg("no hash key for %dth message", idx) } key := hashKeys[idx][0] _, ok := pack[key] diff --git a/internal/proxy/search_agg/computer.go b/internal/proxy/search_agg/computer.go index f003b0c35e..48b7bcc1de 100644 --- a/internal/proxy/search_agg/computer.go +++ b/internal/proxy/search_agg/computer.go @@ -2,12 +2,12 @@ package search_agg import ( "context" - "fmt" "sort" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/agg" "github.com/milvus-io/milvus/internal/util/reduce" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -54,10 +54,10 @@ func NewSearchAggregationComputer( func (c *SearchAggregationComputer) Compute(ctx context.Context) ([][]*AggBucketResult, error) { if c.ctx == nil { - return nil, fmt.Errorf("search aggregation context is nil") + return nil, merr.WrapErrServiceInternalMsg("search aggregation context is nil") } if len(c.ctx.Levels) == 0 { - return nil, fmt.Errorf("search aggregation context has no levels") + return nil, merr.WrapErrServiceInternalMsg("search aggregation context has no levels") } output := make([][]*AggBucketResult, c.ctx.NQ) @@ -74,7 +74,7 @@ func (c *SearchAggregationComputer) Compute(ctx context.Context) ([][]*AggBucket func (c *SearchAggregationComputer) computeForQi(ctx context.Context, qi int64) ([]*AggBucketResult, error) { topks := c.data.GetTopks() if qi < 0 || qi >= int64(len(topks)) { - return nil, fmt.Errorf("invalid qi %d, topks length=%d", qi, len(topks)) + return nil, merr.WrapErrServiceInternalMsg("invalid qi %d, topks length=%d", qi, len(topks)) } var start int64 for i := int64(0); i < qi; i++ { @@ -90,7 +90,7 @@ func (c *SearchAggregationComputer) computeForQi(ctx context.Context, qi int64) func (c *SearchAggregationComputer) computeLevel(ctx context.Context, qi int64, levelIdx int, rows []reduce.RowRef) ([]*AggBucketResult, error) { if levelIdx < 0 || levelIdx >= len(c.ctx.Levels) { - return nil, fmt.Errorf("invalid level index %d", levelIdx) + return nil, merr.WrapErrServiceInternalMsg("invalid level index %d", levelIdx) } level := c.ctx.Levels[levelIdx] isLeaf := levelIdx == len(c.ctx.Levels)-1 @@ -340,7 +340,7 @@ func (c *SearchAggregationComputer) updateMetrics(bucket *bucketState, ref reduc for _, plan := range plans { targets := bucket.metricStates[plan.alias] if targets == nil { - return fmt.Errorf("metric %q: missing bucket state", plan.alias) + return merr.WrapErrServiceInternalMsg("metric %q: missing bucket state", plan.alias) } var raw any @@ -362,7 +362,7 @@ func (c *SearchAggregationComputer) updateMetrics(bucket *bucketState, ref reduc } if err := plan.aggregate.UpdateState(targets, agg.NewFieldValue(raw)); err != nil { - return fmt.Errorf("metric %q update failed: %w", plan.alias, err) + return merr.WrapErrServiceInternalMsg("metric %q update failed: %v", plan.alias, err) } } return nil @@ -370,13 +370,13 @@ func (c *SearchAggregationComputer) updateMetrics(bucket *bucketState, ref reduc func (c *SearchAggregationComputer) readValueByFieldID(ref reduce.RowRef, fieldID int64) (any, bool, error) { if c.data == nil { - return nil, true, fmt.Errorf("nil SearchResultData") + return nil, true, merr.WrapErrServiceInternalMsg("nil SearchResultData") } if fieldID == ScoreFieldID { scores := c.data.GetScores() if ref.RowIdx < 0 || ref.RowIdx >= int64(len(scores)) { - return nil, true, fmt.Errorf("score index %d out of range", ref.RowIdx) + return nil, true, merr.WrapErrServiceInternalMsg("score index %d out of range", ref.RowIdx) } return scores[ref.RowIdx], false, nil } @@ -384,9 +384,9 @@ func (c *SearchAggregationComputer) readValueByFieldID(ref reduce.RowRef, fieldI fd := c.fieldsByID[fieldID] if fd == nil { if c.ctx.IsGroupByField(fieldID) { - return nil, true, fmt.Errorf("group-by field %d missing from group_by_field_values", fieldID) + return nil, true, merr.WrapErrServiceInternalMsg("group-by field %d missing from group_by_field_values", fieldID) } - return nil, true, fmt.Errorf("field %d missing from fields_data", fieldID) + return nil, true, merr.WrapErrServiceInternalMsg("field %d missing from fields_data", fieldID) } iter := typeutil.GetDataIterator(fd) @@ -423,11 +423,11 @@ func finalizeMetrics(plans []metricPlan, states map[string][]*agg.FieldValue) (m for _, plan := range plans { slots := states[plan.alias] if plan.aggregate == nil { - return nil, fmt.Errorf("metric %q: semantic aggregate is nil", plan.alias) + return nil, merr.WrapErrServiceInternalMsg("metric %q: semantic aggregate is nil", plan.alias) } value, err := plan.aggregate.Terminate(slots) if err != nil { - return nil, fmt.Errorf("metric %q: %w", plan.alias, err) + return nil, merr.WrapErrServiceInternalMsg("metric %q: %v", plan.alias, err) } metrics[plan.alias] = value } @@ -470,90 +470,90 @@ func compareValues(a, b any) (int, error) { case int: bv, ok := b.(int) if !ok { - return 0, fmt.Errorf("type mismatch: %T vs %T", a, b) + return 0, merr.WrapErrServiceInternalMsg("type mismatch: %T vs %T", a, b) } return compareOrdered(av, bv), nil case int8: bv, ok := b.(int8) if !ok { - return 0, fmt.Errorf("type mismatch: %T vs %T", a, b) + return 0, merr.WrapErrServiceInternalMsg("type mismatch: %T vs %T", a, b) } return compareOrdered(av, bv), nil case int16: bv, ok := b.(int16) if !ok { - return 0, fmt.Errorf("type mismatch: %T vs %T", a, b) + return 0, merr.WrapErrServiceInternalMsg("type mismatch: %T vs %T", a, b) } return compareOrdered(av, bv), nil case int32: bv, ok := b.(int32) if !ok { - return 0, fmt.Errorf("type mismatch: %T vs %T", a, b) + return 0, merr.WrapErrServiceInternalMsg("type mismatch: %T vs %T", a, b) } return compareOrdered(av, bv), nil case int64: bv, ok := b.(int64) if !ok { - return 0, fmt.Errorf("type mismatch: %T vs %T", a, b) + return 0, merr.WrapErrServiceInternalMsg("type mismatch: %T vs %T", a, b) } return compareOrdered(av, bv), nil case uint: bv, ok := b.(uint) if !ok { - return 0, fmt.Errorf("type mismatch: %T vs %T", a, b) + return 0, merr.WrapErrServiceInternalMsg("type mismatch: %T vs %T", a, b) } return compareOrdered(av, bv), nil case uint8: bv, ok := b.(uint8) if !ok { - return 0, fmt.Errorf("type mismatch: %T vs %T", a, b) + return 0, merr.WrapErrServiceInternalMsg("type mismatch: %T vs %T", a, b) } return compareOrdered(av, bv), nil case uint16: bv, ok := b.(uint16) if !ok { - return 0, fmt.Errorf("type mismatch: %T vs %T", a, b) + return 0, merr.WrapErrServiceInternalMsg("type mismatch: %T vs %T", a, b) } return compareOrdered(av, bv), nil case uint32: bv, ok := b.(uint32) if !ok { - return 0, fmt.Errorf("type mismatch: %T vs %T", a, b) + return 0, merr.WrapErrServiceInternalMsg("type mismatch: %T vs %T", a, b) } return compareOrdered(av, bv), nil case uint64: bv, ok := b.(uint64) if !ok { - return 0, fmt.Errorf("type mismatch: %T vs %T", a, b) + return 0, merr.WrapErrServiceInternalMsg("type mismatch: %T vs %T", a, b) } return compareOrdered(av, bv), nil case float32: bv, ok := b.(float32) if !ok { - return 0, fmt.Errorf("type mismatch: %T vs %T", a, b) + return 0, merr.WrapErrServiceInternalMsg("type mismatch: %T vs %T", a, b) } return compareFloat64(float64(av), float64(bv)), nil case float64: bv, ok := b.(float64) if !ok { - return 0, fmt.Errorf("type mismatch: %T vs %T", a, b) + return 0, merr.WrapErrServiceInternalMsg("type mismatch: %T vs %T", a, b) } return compareFloat64(av, bv), nil case bool: bv, ok := b.(bool) if !ok { - return 0, fmt.Errorf("type mismatch: %T vs %T", a, b) + return 0, merr.WrapErrServiceInternalMsg("type mismatch: %T vs %T", a, b) } return compareBool(av, bv), nil case string: bv, ok := b.(string) if !ok { - return 0, fmt.Errorf("type mismatch: %T vs %T", a, b) + return 0, merr.WrapErrServiceInternalMsg("type mismatch: %T vs %T", a, b) } return compareOrdered(av, bv), nil } - return 0, fmt.Errorf("unsupported comparable types: %T and %T", a, b) + return 0, merr.WrapErrServiceInternalMsg("unsupported comparable types: %T and %T", a, b) } func compareOrdered[T ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~string](a, b T) int { diff --git a/internal/proxy/search_agg/context_builder.go b/internal/proxy/search_agg/context_builder.go index c30da20840..2ad868a902 100644 --- a/internal/proxy/search_agg/context_builder.go +++ b/internal/proxy/search_agg/context_builder.go @@ -1,7 +1,6 @@ package search_agg import ( - "fmt" "math" "sort" "strings" @@ -10,6 +9,7 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/agg" typeutil2 "github.com/milvus-io/milvus/internal/util/typeutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -87,7 +87,7 @@ func NewContext( } plans, err := compileMetricPlans(levels[i].Metrics) if err != nil { - return nil, fmt.Errorf("level %d metric compile failed: %w", i, err) + return nil, merr.Wrapf(err, "level %d metric compile failed", i) } levels[i].metricPlans = plans } @@ -132,7 +132,7 @@ func compileMetricPlans(metrics map[string]MetricSpec) ([]metricPlan, error) { for _, alias := range aliases { plan, err := buildMetricPlan(alias, metrics[alias]) if err != nil { - return nil, fmt.Errorf("metric %q: %w", alias, err) + return nil, merr.Wrapf(err, "metric %q", alias) } plans = append(plans, plan) } @@ -175,7 +175,7 @@ func deriveTopKAndGroupSizeChecked(levels []LevelContext) (topK, groupSize int64 var ok bool topK, ok = checkedMulInt64(topK, candidateSize(lvl)) if !ok { - return 0, 0, fmt.Errorf("search_aggregation derived topK overflows int64") + return 0, 0, merr.WrapErrParameterInvalidMsg("search_aggregation derived topK overflows int64") } } return topK, deriveGroupSize(levels), nil @@ -197,14 +197,14 @@ func validateSearchAggregationResultEntries(nq, topK, groupSize, maxEntries int6 } nqTopK, ok := checkedMulInt64(nq, topK) if !ok { - return fmt.Errorf("number of search_aggregation result entries is too large") + return merr.WrapErrParameterInvalidMsg("number of search_aggregation result entries is too large") } entries, ok := checkedMulInt64(nqTopK, groupSize) if !ok { - return fmt.Errorf("number of search_aggregation result entries is too large") + return merr.WrapErrParameterInvalidMsg("number of search_aggregation result entries is too large") } if entries > maxEntries { - return fmt.Errorf("number of search_aggregation result entries is too large") + return merr.WrapErrParameterInvalidMsg("number of search_aggregation result entries is too large") } return nil } @@ -239,17 +239,17 @@ const maxAggregationLevels = 4 func resolveAggregationSpec(groupBy *commonpb.SearchAggregationSpec, schema *schemapb.CollectionSchema) (*resolvedAggregationSpec, error) { if groupBy == nil { - return nil, fmt.Errorf("group_by spec is nil") + return nil, merr.WrapErrParameterInvalidMsg("group_by spec is nil") } if schema == nil { - return nil, fmt.Errorf("collection schema is nil") + return nil, merr.WrapErrParameterInvalidMsg("collection schema is nil") } depth := 0 for cur := groupBy; cur != nil; cur = cur.GetSubAggregation() { depth++ if depth > maxAggregationLevels { - return nil, fmt.Errorf("search_aggregation nesting exceeds max %d levels", maxAggregationLevels) + return nil, merr.WrapErrParameterInvalidMsg("search_aggregation nesting exceeds max %d levels", maxAggregationLevels) } } @@ -265,13 +265,13 @@ func resolveAggregationSpec(groupBy *commonpb.SearchAggregationSpec, schema *sch } if len(spec.GetFields()) == 0 { - return fmt.Errorf("group_by level has no fields") + return merr.WrapErrParameterInvalidMsg("group_by level has no fields") } if spec.GetSize() < 0 { - return fmt.Errorf("search_aggregation size must be non-negative") + return merr.WrapErrParameterInvalidMsg("search_aggregation size must be non-negative") } if spec.GetSearchSize() < 0 { - return fmt.Errorf("search_aggregation search_size must be non-negative") + return merr.WrapErrParameterInvalidMsg("search_aggregation search_size must be non-negative") } levelSize := normalizeAggregationSize(spec.GetSize()) @@ -280,7 +280,7 @@ func resolveAggregationSpec(groupBy *commonpb.SearchAggregationSpec, schema *sch searchSize = levelSize } if searchSize < levelSize { - return fmt.Errorf("search_aggregation search_size must be greater than or equal to size") + return merr.WrapErrParameterInvalidMsg("search_aggregation search_size must be greater than or equal to size") } level := LevelContext{ @@ -293,7 +293,7 @@ func resolveAggregationSpec(groupBy *commonpb.SearchAggregationSpec, schema *sch for _, fieldName := range spec.GetFields() { fieldID, err := resolveFieldID(fieldName, schema, dynamicField) if err != nil { - return fmt.Errorf("invalid group_by field %q: %w", fieldName, err) + return merr.Wrapf(err, "invalid group_by field %q", fieldName) } field, err := validateSearchAggregationFieldSupport(fieldName, fieldID, schema, "group_by field") if err != nil { @@ -301,14 +301,14 @@ func resolveAggregationSpec(groupBy *commonpb.SearchAggregationSpec, schema *sch } if field != nil { if field.GetDataType() == schemapb.DataType_Float || field.GetDataType() == schemapb.DataType_Double { - return fmt.Errorf("group_by field %q: FLOAT / DOUBLE fields are not supported with search_aggregation (BucketKeyEntry has no float variant; equality on floats is fragile)", fieldName) + return merr.WrapErrParameterInvalidMsg("group_by field %q: FLOAT / DOUBLE fields are not supported with search_aggregation (BucketKeyEntry has no float variant; equality on floats is fragile)", fieldName) } } if _, ok := levelFieldSeen[fieldID]; ok { - return fmt.Errorf("duplicated group_by field %q in one level", fieldName) + return merr.WrapErrParameterInvalidMsg("duplicated group_by field %q in one level", fieldName) } if _, ok := groupBySeen[fieldID]; ok { - return fmt.Errorf("duplicated group_by field %q across levels", fieldName) + return merr.WrapErrParameterInvalidMsg("duplicated group_by field %q across levels", fieldName) } levelFieldSeen[fieldID] = struct{}{} groupBySeen[fieldID] = struct{}{} @@ -329,15 +329,15 @@ func resolveAggregationSpec(groupBy *commonpb.SearchAggregationSpec, schema *sch for _, alias := range aliases { metric := spec.GetMetrics()[alias] if strings.TrimSpace(alias) == "" { - return fmt.Errorf("metric alias cannot be empty") + return merr.WrapErrParameterMissingMsg("metric alias cannot be empty") } metricSpec, metricSourceFieldID, err := buildMetricSpec(metric, schema, dynamicField) if err != nil { - return fmt.Errorf("invalid metric %q: %w", alias, err) + return merr.Wrapf(err, "invalid metric %q", alias) } plan, err := buildMetricPlan(alias, metricSpec) if err != nil { - return fmt.Errorf("invalid metric %q: %w", alias, err) + return merr.Wrapf(err, "invalid metric %q", alias) } level.Metrics[alias] = metricSpec level.metricPlans = append(level.metricPlans, plan) @@ -373,19 +373,19 @@ func resolveAggregationSpec(groupBy *commonpb.SearchAggregationSpec, schema *sch func buildMetricSpec(metric *commonpb.MetricAggSpec, schema *schemapb.CollectionSchema, dynamicField *schemapb.FieldSchema) (MetricSpec, int64, error) { if metric == nil { - return MetricSpec{}, 0, fmt.Errorf("metric spec is nil") + return MetricSpec{}, 0, merr.WrapErrParameterInvalidMsg("metric spec is nil") } op := strings.ToLower(strings.TrimSpace(metric.GetOp())) switch op { case "avg", "sum", "count", "min", "max": default: - return MetricSpec{}, 0, fmt.Errorf("unsupported metric op %q", metric.GetOp()) + return MetricSpec{}, 0, merr.WrapErrParameterInvalidMsg("unsupported metric op %q", metric.GetOp()) } fieldName := strings.TrimSpace(metric.GetFieldName()) if fieldName == "" { - return MetricSpec{}, 0, fmt.Errorf("metric field_name is empty") + return MetricSpec{}, 0, merr.WrapErrParameterInvalidMsg("metric field_name is empty") } switch fieldName { @@ -395,7 +395,7 @@ func buildMetricSpec(metric *commonpb.MetricAggSpec, schema *schemapb.Collection return MetricSpec{Op: op, FieldID: ScoreFieldID, FieldType: schemapb.DataType_Float}, 0, nil case "*": if op != "count" { - return MetricSpec{}, 0, fmt.Errorf("field_name '*' only supports count op") + return MetricSpec{}, 0, merr.WrapErrParameterInvalidMsg("field_name '*' only supports count op") } // count(*) has no source field; DataType_None is what internal/agg // expects for the synthetic always-1 input. @@ -422,7 +422,7 @@ func buildTopHitsConfig(topHits *commonpb.TopHitsSpec, schema *schemapb.Collecti return nil, nil, nil } if topHits.GetSize() < 0 { - return nil, nil, fmt.Errorf("top_hits size must be non-negative") + return nil, nil, merr.WrapErrParameterInvalidMsg("top_hits size must be non-negative") } cfg := &TopHitsConfig{ @@ -434,16 +434,16 @@ func buildTopHitsConfig(topHits *commonpb.TopHitsSpec, schema *schemapb.Collecti for _, sortSpec := range topHits.GetSort() { if sortSpec == nil { - return nil, nil, fmt.Errorf("top_hits.sort contains nil item") + return nil, nil, merr.WrapErrParameterInvalidMsg("top_hits.sort contains nil item") } fieldName := strings.TrimSpace(sortSpec.GetFieldName()) if fieldName == "" { - return nil, nil, fmt.Errorf("top_hits.sort field_name is empty") + return nil, nil, merr.WrapErrParameterInvalidMsg("top_hits.sort field_name is empty") } direction, err := normalizeDirection(sortSpec.GetDirection(), "desc") if err != nil { - return nil, nil, fmt.Errorf("invalid top_hits.sort direction for %q: %w", fieldName, err) + return nil, nil, merr.Wrapf(err, "invalid top_hits.sort direction for %q", fieldName) } if fieldName == "_score" { @@ -452,12 +452,12 @@ func buildTopHitsConfig(topHits *commonpb.TopHitsSpec, schema *schemapb.Collecti } if isJSONPathFieldExpr(fieldName) { - return nil, nil, fmt.Errorf("top_hits.sort JSON path is not yet supported: %q", fieldName) + return nil, nil, merr.WrapErrParameterInvalidMsg("top_hits.sort JSON path is not yet supported: %q", fieldName) } fieldID, err := resolveFieldID(fieldName, schema, dynamicField) if err != nil { - return nil, nil, fmt.Errorf("invalid top_hits.sort field %q: %w", fieldName, err) + return nil, nil, merr.Wrapf(err, "invalid top_hits.sort field %q", fieldName) } if _, err := validateSearchAggregationFieldSupport(fieldName, fieldID, schema, "top_hits.sort field"); err != nil { return nil, nil, err @@ -477,23 +477,23 @@ func buildOrderCriteria(orderSpecs []*commonpb.OrderSpec, metrics map[string]Met order := make([]OrderCriterion, 0, len(orderSpecs)) for _, orderSpec := range orderSpecs { if orderSpec == nil { - return nil, fmt.Errorf("order contains nil item") + return nil, merr.WrapErrParameterInvalidMsg("order contains nil item") } key := strings.TrimSpace(orderSpec.GetKey()) if key == "" { - return nil, fmt.Errorf("order key is empty") + return nil, merr.WrapErrParameterInvalidMsg("order key is empty") } if key != "_count" && key != "_key" { if _, ok := metrics[key]; !ok { - return nil, fmt.Errorf("order key %q is neither reserved key nor metric alias", key) + return nil, merr.WrapErrParameterInvalidMsg("order key %q is neither reserved key nor metric alias", key) } } direction, err := normalizeDirection(orderSpec.GetDirection(), "desc") if err != nil { - return nil, fmt.Errorf("invalid order direction for key %q: %w", key, err) + return nil, merr.Wrapf(err, "invalid order direction for key %q", key) } // ES bucket order has no null placement option; OrderSpec.NullFirst is ignored intentionally. order = append(order, OrderCriterion{Key: key, Dir: direction}) @@ -511,7 +511,7 @@ func normalizeDirection(direction string, defaultDir string) (string, error) { case "asc", "desc": return dir, nil default: - return "", fmt.Errorf("direction must be asc or desc") + return "", merr.WrapErrParameterInvalidMsg("direction must be asc or desc") } } @@ -521,7 +521,7 @@ func validateSearchAggregationFieldSupport(fieldName string, fieldID int64, sche return nil, nil } if field.GetDataType() == schemapb.DataType_JSON || field.GetIsDynamic() { - return nil, fmt.Errorf("%s %q: JSON / dynamic fields are not yet supported with search_aggregation", usage, fieldName) + return nil, merr.WrapErrParameterInvalidMsg("%s %q: JSON / dynamic fields are not yet supported with search_aggregation", usage, fieldName) } return field, nil } @@ -534,7 +534,7 @@ func isJSONPathFieldExpr(fieldExpr string) bool { func resolveFieldID(fieldExpr string, schema *schemapb.CollectionSchema, dynamicField *schemapb.FieldSchema) (int64, error) { fieldExpr = strings.TrimSpace(fieldExpr) if fieldExpr == "" { - return 0, fmt.Errorf("field is empty") + return 0, merr.WrapErrParameterInvalidMsg("field is empty") } if field := typeutil.GetFieldByName(schema, fieldExpr); field != nil { @@ -567,7 +567,7 @@ func resolveFieldID(fieldExpr string, schema *schemapb.CollectionSchema, dynamic } } - return 0, fmt.Errorf("field %q not found in schema", fieldExpr) + return 0, merr.WrapErrParameterInvalidMsg("field %q not found in schema", fieldExpr) } func findDynamicField(schema *schemapb.CollectionSchema) *schemapb.FieldSchema { diff --git a/internal/proxy/search_agg/context_builder_test.go b/internal/proxy/search_agg/context_builder_test.go index 6c5d65031d..4687dfd6cf 100644 --- a/internal/proxy/search_agg/context_builder_test.go +++ b/internal/proxy/search_agg/context_builder_test.go @@ -2,12 +2,14 @@ package search_agg import ( "math" + "strings" "testing" "github.com/stretchr/testify/require" "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" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -434,6 +436,57 @@ func TestBuildSearchAggregationContextRejectsJSONField(t *testing.T) { }) } +// TestBuildSearchAggregationContextRejectErrorsAreSingleSuffix guards against the +// doubled "invalid parameter" sentinel suffix. Once err-std made the leaf validators +// return typed merr errors, an outer context wrapper that re-stamped them via +// WrapErrParameterInvalidMsg("...: %v", err) appended a second suffix. Context wrappers +// must use merr.Wrapf so the sentinel suffix (and code) come exactly once, from the +// inner origin. +func TestBuildSearchAggregationContextRejectErrorsAreSingleSuffix(t *testing.T) { + schema := &schemapb.CollectionSchema{ + Name: "agg_test", + Fields: []*schemapb.FieldSchema{ + {FieldID: 100, Name: "id", DataType: schemapb.DataType_Int64, IsPrimaryKey: true}, + {FieldID: 108, Name: "meta", DataType: schemapb.DataType_JSON}, + }, + } + + cases := []struct { + name string + spec *commonpb.SearchAggregationSpec + want string + }{ + { + name: "group_by json field", // exercises the group_by context wrapper + spec: &commonpb.SearchAggregationSpec{Fields: []string{"meta"}, Size: 3}, + want: `group_by field "meta": JSON / dynamic fields are not yet supported`, + }, + { + name: "metric json field", // exercises the metric context wrapper (originally reported) + spec: &commonpb.SearchAggregationSpec{ + Fields: []string{"id"}, + Size: 3, + Metrics: map[string]*commonpb.MetricAggSpec{"avg_meta": {Op: "avg", FieldName: "meta"}}, + }, + want: `invalid metric "avg_meta": metric field "meta": JSON / dynamic fields are not yet supported`, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + _, err := BuildSearchAggregationContext(tc.spec, schema, 1) + require.Error(t, err) + require.Contains(t, err.Error(), tc.want) + // The merr sentinel suffix must appear exactly once — not doubled by an + // outer wrapper re-stamping the already-typed inner. + require.Equal(t, 1, strings.Count(err.Error(), "invalid parameter"), + "sentinel suffix doubled: %s", err.Error()) + // Wrapping must preserve the inner ParameterInvalid classification. + require.ErrorIs(t, err, merr.ErrParameterInvalid) + }) + } +} + func TestBuildSearchAggregationContextRejectsTopHitsSortJSONPath(t *testing.T) { schema := &schemapb.CollectionSchema{ Name: "agg_test", diff --git a/internal/proxy/search_agg/order.go b/internal/proxy/search_agg/order.go index e1c99c4dcb..4a4142e56b 100644 --- a/internal/proxy/search_agg/order.go +++ b/internal/proxy/search_agg/order.go @@ -1,8 +1,9 @@ package search_agg import ( - "fmt" "sort" + + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) func applyOrderAndSize(buckets []*AggBucketResult, level LevelContext) ([]*AggBucketResult, error) { @@ -72,7 +73,7 @@ func compareBucketKeys(a, b map[int64]any, ownFieldIDs []int64) (int, error) { for _, fieldID := range ownFieldIDs { cmp, err := compareValues(a[fieldID], b[fieldID]) if err != nil { - return 0, fmt.Errorf("compare _key field %d failed: %w", fieldID, err) + return 0, merr.WrapErrServiceInternalMsg("compare _key field %d failed: %v", fieldID, err) } if cmp != 0 { return cmp, nil diff --git a/internal/proxy/search_pipeline.go b/internal/proxy/search_pipeline.go index 5913efb670..eae7651cae 100644 --- a/internal/proxy/search_pipeline.go +++ b/internal/proxy/search_pipeline.go @@ -76,7 +76,7 @@ type Node struct { func (n *Node) unpackInputs(msg opMsg) ([]any, error) { for _, input := range n.inputs { if _, ok := msg[input]; !ok { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("Node [%s]'s input %s not found", n.name, input)) + return nil, merr.WrapErrServiceInternalMsg("Node [%s]'s input %s not found", n.name, input) } } inputs := make([]any, len(n.inputs)) @@ -89,7 +89,7 @@ func (n *Node) unpackInputs(msg opMsg) ([]any, error) { func (n *Node) packOutputs(outputs []any, srcMsg opMsg) (opMsg, error) { msg := srcMsg if len(outputs) != len(n.outputs) { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("Node [%s] output size not match operator output size", n.name)) + return nil, merr.WrapErrServiceInternalMsg("Node [%s] output size not match operator output size", n.name) } for i, output := range n.outputs { msg[output] = outputs[i] @@ -331,7 +331,7 @@ func (op *elementBestCollapseOperator) run(ctx context.Context, span trace.Span, return nil, merr.WrapErrParameterInvalidMsg("element best collapse: inputs[1] must be []string, got %T", inputs[1]) } if len(metrics) != len(results) { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("element best collapse: metrics length (%d) does not match results length (%d)", len(metrics), len(results))) + return nil, merr.WrapErrServiceInternalMsg("element best collapse: metrics length (%d) does not match results length (%d)", len(metrics), len(results)) } collapsed := make([]*milvuspb.SearchResults, len(results)) @@ -351,7 +351,7 @@ func (op *elementBestCollapseOperator) run(ctx context.Context, span trace.Span, totalRows += topk } if totalRows > 0 { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("element best collapse: missing metric type for element-level result[%d]", i)) + return nil, merr.WrapErrServiceInternalMsg("element best collapse: missing metric type for element-level result[%d]", i) } } var err error @@ -434,24 +434,24 @@ func collapseElementLevelResult(result *milvuspb.SearchResults, largerScoreIsBet } if typeutil.GetSizeOfIDs(data.GetIds()) < int(totalRows) { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("element best collapse: ids length (%d) is less than total rows (%d)", - typeutil.GetSizeOfIDs(data.GetIds()), totalRows)) + return nil, merr.WrapErrServiceInternalMsg("element best collapse: ids length (%d) is less than total rows (%d)", + typeutil.GetSizeOfIDs(data.GetIds()), totalRows) } if int64(len(data.GetScores())) < totalRows { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("element best collapse: scores length (%d) is less than total rows (%d)", - len(data.GetScores()), totalRows)) + return nil, merr.WrapErrServiceInternalMsg("element best collapse: scores length (%d) is less than total rows (%d)", + len(data.GetScores()), totalRows) } if int64(len(data.GetElementIndices().GetData())) < totalRows { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("element best collapse: element_indices length (%d) is less than total rows (%d)", - len(data.GetElementIndices().GetData()), totalRows)) + return nil, merr.WrapErrServiceInternalMsg("element best collapse: element_indices length (%d) is less than total rows (%d)", + len(data.GetElementIndices().GetData()), totalRows) } if len(data.GetDistances()) > 0 && int64(len(data.GetDistances())) < totalRows { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("element best collapse: distances length (%d) is less than total rows (%d)", - len(data.GetDistances()), totalRows)) + return nil, merr.WrapErrServiceInternalMsg("element best collapse: distances length (%d) is less than total rows (%d)", + len(data.GetDistances()), totalRows) } if len(data.GetRecalls()) > 0 && int64(len(data.GetRecalls())) < totalRows { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("element best collapse: recalls length (%d) is less than total rows (%d)", - len(data.GetRecalls()), totalRows)) + return nil, merr.WrapErrServiceInternalMsg("element best collapse: recalls length (%d) is less than total rows (%d)", + len(data.GetRecalls()), totalRows) } output := &schemapb.SearchResultData{ @@ -626,15 +626,15 @@ func prepareElementLevelHybridResult(result *milvuspb.SearchResults) (*milvuspb. return copySearchResultsWithData(result, output), nil } if typeutil.GetSizeOfIDs(data.GetIds()) < int(totalRows) { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("element-level hybrid: ids length (%d) is less than total rows (%d)", - typeutil.GetSizeOfIDs(data.GetIds()), totalRows)) + return nil, merr.WrapErrServiceInternalMsg("element-level hybrid: ids length (%d) is less than total rows (%d)", + typeutil.GetSizeOfIDs(data.GetIds()), totalRows) } if data.GetElementIndices() == nil { return nil, merr.WrapErrServiceInternal("element-level hybrid: missing element_indices") } if int64(len(data.GetElementIndices().GetData())) < totalRows { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("element-level hybrid: element_indices length (%d) is less than total rows (%d)", - len(data.GetElementIndices().GetData()), totalRows)) + return nil, merr.WrapErrServiceInternalMsg("element-level hybrid: element_indices length (%d) is less than total rows (%d)", + len(data.GetElementIndices().GetData()), totalRows) } keys := make([]string, 0, totalRows) @@ -719,11 +719,11 @@ func restoreElementLevelHybridRankResult(rankResult *milvuspb.SearchResults) (*m rawKey := typeutil.GetPK(data.GetIds(), int64(i)) key, ok := rawKey.(string) if !ok { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("element key restore: expected string element key, got %T", rawKey)) + return nil, merr.WrapErrServiceInternalMsg("element key restore: expected string element key, got %T", rawKey) } pk, elementIndex, ok := parseHybridElementKey(key) if !ok { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("element key restore: invalid element key %q", key)) + return nil, merr.WrapErrServiceInternalMsg("element key restore: invalid element key %q", key) } appendPK(outputIDs, pk) elementIndices = append(elementIndices, elementIndex) @@ -810,7 +810,7 @@ func (op *aggregateOperator) run(ctx context.Context, span trace.Span, inputs .. } reducedList, ok := inputs[0].([]*milvuspb.SearchResults) if !ok { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("aggregateOperator: expected []*milvuspb.SearchResults, got %T (pipeline wire)", inputs[0])) + return nil, merr.WrapErrServiceInternalMsg("aggregateOperator: expected []*milvuspb.SearchResults, got %T (pipeline wire)", inputs[0]) } // Upstream searchReduceOp has already done cross-shard composite-key reduce // and produced a single *milvuspb.SearchResults wrapping one SearchResultData. @@ -1168,7 +1168,7 @@ func buildChainFromMeta( case *legacyRerankMeta: return chain.BuildRerankChainWithLegacy(collSchema, m.legacyParams, metrics, searchParams, alloc) default: - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("rerank operator: unsupported rerankMeta type %T", meta)) + return nil, merr.WrapErrFunctionFailedMsg("rerank operator: unsupported rerankMeta type %T", meta) } } @@ -1255,7 +1255,7 @@ func (op *rerankOperator) run(ctx context.Context, span trace.Span, inputs ...an for _, df := range dataframes { df.Release() } - return nil, merr.WrapErrServiceInternal("rerank operator: rerankMeta is nil, cannot build rerank chain") + return nil, merr.WrapErrFunctionFailedMsg("rerank operator: rerankMeta is nil, cannot build rerank chain") } searchParams.ModelExtraInfo = &models.ModelExtraInfo{ ClusterID: paramtable.Get().CommonCfg.ClusterPrefix.GetValue(), @@ -1661,7 +1661,7 @@ func (op *hybridAssembleOperator) run(ctx context.Context, span trace.Span, inpu if op.elementLevelHybrid { elementIndices := rankResult.GetResults().GetElementIndices().GetData() if i >= len(elementIndices) { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("hybrid assemble: missing element index for reranked row %d, collection=%d", i, op.collectionID)) + return nil, merr.WrapErrServiceInternalMsg("hybrid assemble: missing element index for reranked row %d, collection=%d", i, op.collectionID) } candidateKey = makeHybridElementKey(candidateKey, elementIndices[i]) } @@ -1671,10 +1671,10 @@ func (op *hybridAssembleOperator) run(ctx context.Context, span trace.Span, inpu fmt.Sprintf("hybrid assemble: missing id %v, collection=%d", candidateKey, op.collectionID)) } if computers[loc.resultIdx] == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf( + return nil, merr.WrapErrServiceInternalMsg( "hybrid assemble: sub-result[%d] has empty FieldsData but contributed reranked id %v; "+ "all sub-results that contribute ids must share the same FieldsData layout, "+ - "collection=%d", loc.resultIdx, candidateKey, op.collectionID)) + "collection=%d", loc.resultIdx, candidateKey, op.collectionID) } locs[i] = loc itemsByResult[loc.resultIdx] = append(itemsByResult[loc.resultIdx], rowIdxComputeItem{ @@ -1815,7 +1815,7 @@ func (op *orderByOperator) run(ctx context.Context, span trace.Span, inputs ...a sumTopks += topk } if int(sumTopks) != numResults { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("order_by: Topks sum (%d) does not match numResults (%d)", sumTopks, numResults)) + return nil, merr.WrapErrServiceInternalMsg("order_by: Topks sum (%d) does not match numResults (%d)", sumTopks, numResults) } // Build indices array for sorting @@ -1881,7 +1881,7 @@ func (op *orderByOperator) validateOrderByFields(result *milvuspb.SearchResults) for _, orderBy := range op.orderByFields { if !fieldNames[orderBy.FieldName] { - return merr.WrapErrServiceInternal(fmt.Sprintf("order_by field '%s' not found in search results", orderBy.FieldName)) + return merr.WrapErrServiceInternalMsg("order_by field '%s' not found in search results", orderBy.FieldName) } } return nil @@ -2305,7 +2305,7 @@ func compareFieldDataAt(field *schemapb.FieldData, i, j int, nullsFirst bool) (i case schemapb.DataType_Int8, schemapb.DataType_Int16, schemapb.DataType_Int32: data := field.GetScalars().GetIntData().GetData() if i >= len(data) || j >= len(data) { - return 0, merr.WrapErrServiceInternal(fmt.Sprintf("compareFieldDataAt: index out of bounds for Int field %s (i=%d, j=%d, len=%d)", field.GetFieldName(), i, j, len(data))) + return 0, merr.WrapErrServiceInternalMsg("compareFieldDataAt: index out of bounds for Int field %s (i=%d, j=%d, len=%d)", field.GetFieldName(), i, j, len(data)) } if data[i] < data[j] { return -1, nil @@ -2316,7 +2316,7 @@ func compareFieldDataAt(field *schemapb.FieldData, i, j int, nullsFirst bool) (i case schemapb.DataType_Int64: data := field.GetScalars().GetLongData().GetData() if i >= len(data) || j >= len(data) { - return 0, merr.WrapErrServiceInternal(fmt.Sprintf("compareFieldDataAt: index out of bounds for Int64 field %s (i=%d, j=%d, len=%d)", field.GetFieldName(), i, j, len(data))) + return 0, merr.WrapErrServiceInternalMsg("compareFieldDataAt: index out of bounds for Int64 field %s (i=%d, j=%d, len=%d)", field.GetFieldName(), i, j, len(data)) } if data[i] < data[j] { return -1, nil @@ -2327,7 +2327,7 @@ func compareFieldDataAt(field *schemapb.FieldData, i, j int, nullsFirst bool) (i case schemapb.DataType_Float: data := field.GetScalars().GetFloatData().GetData() if i >= len(data) || j >= len(data) { - return 0, merr.WrapErrServiceInternal(fmt.Sprintf("compareFieldDataAt: index out of bounds for Float field %s (i=%d, j=%d, len=%d)", field.GetFieldName(), i, j, len(data))) + return 0, merr.WrapErrServiceInternalMsg("compareFieldDataAt: index out of bounds for Float field %s (i=%d, j=%d, len=%d)", field.GetFieldName(), i, j, len(data)) } if data[i] < data[j] { return -1, nil @@ -2338,7 +2338,7 @@ func compareFieldDataAt(field *schemapb.FieldData, i, j int, nullsFirst bool) (i case schemapb.DataType_Double: data := field.GetScalars().GetDoubleData().GetData() if i >= len(data) || j >= len(data) { - return 0, merr.WrapErrServiceInternal(fmt.Sprintf("compareFieldDataAt: index out of bounds for Double field %s (i=%d, j=%d, len=%d)", field.GetFieldName(), i, j, len(data))) + return 0, merr.WrapErrServiceInternalMsg("compareFieldDataAt: index out of bounds for Double field %s (i=%d, j=%d, len=%d)", field.GetFieldName(), i, j, len(data)) } if data[i] < data[j] { return -1, nil @@ -2349,7 +2349,7 @@ func compareFieldDataAt(field *schemapb.FieldData, i, j int, nullsFirst bool) (i case schemapb.DataType_VarChar, schemapb.DataType_String: data := field.GetScalars().GetStringData().GetData() if i >= len(data) || j >= len(data) { - return 0, merr.WrapErrServiceInternal(fmt.Sprintf("compareFieldDataAt: index out of bounds for String field %s (i=%d, j=%d, len=%d)", field.GetFieldName(), i, j, len(data))) + return 0, merr.WrapErrServiceInternalMsg("compareFieldDataAt: index out of bounds for String field %s (i=%d, j=%d, len=%d)", field.GetFieldName(), i, j, len(data)) } if data[i] < data[j] { return -1, nil @@ -2362,13 +2362,13 @@ func compareFieldDataAt(field *schemapb.FieldData, i, j int, nullsFirst bool) (i // not by semantic JSON value. For example, "2" > "10" because '2' > '1' in bytes. data := field.GetScalars().GetJsonData().GetData() if i >= len(data) || j >= len(data) { - return 0, merr.WrapErrServiceInternal(fmt.Sprintf("compareFieldDataAt: index out of bounds for JSON field %s (i=%d, j=%d, len=%d)", field.GetFieldName(), i, j, len(data))) + return 0, merr.WrapErrServiceInternalMsg("compareFieldDataAt: index out of bounds for JSON field %s (i=%d, j=%d, len=%d)", field.GetFieldName(), i, j, len(data)) } return bytes.Compare(data[i], data[j]), nil case schemapb.DataType_Bool: data := field.GetScalars().GetBoolData().GetData() if i >= len(data) || j >= len(data) { - return 0, merr.WrapErrServiceInternal(fmt.Sprintf("compareFieldDataAt: index out of bounds for Bool field %s (i=%d, j=%d, len=%d)", field.GetFieldName(), i, j, len(data))) + return 0, merr.WrapErrServiceInternalMsg("compareFieldDataAt: index out of bounds for Bool field %s (i=%d, j=%d, len=%d)", field.GetFieldName(), i, j, len(data)) } // false < true if !data[i] && data[j] { @@ -2378,7 +2378,7 @@ func compareFieldDataAt(field *schemapb.FieldData, i, j int, nullsFirst bool) (i } return 0, nil default: - return 0, merr.WrapErrServiceInternal(fmt.Sprintf("compareFieldDataAt: unsupported field type %s for field %s", field.GetType().String(), field.GetFieldName())) + return 0, merr.WrapErrServiceInternalMsg("compareFieldDataAt: unsupported field type %s for field %s", field.GetType().String(), field.GetFieldName()) } } @@ -2393,7 +2393,7 @@ func (op *orderByOperator) reorderResults(result *milvuspb.SearchResults, indice newData := make([]int64, n) for newIdx, oldIdx := range indices { if oldIdx < 0 || oldIdx >= len(intIds.Data) { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderResults: index %d out of bounds for int IDs (len=%d)", oldIdx, len(intIds.Data))) + return merr.WrapErrServiceInternalMsg("reorderResults: index %d out of bounds for int IDs (len=%d)", oldIdx, len(intIds.Data)) } newData[newIdx] = intIds.Data[oldIdx] } @@ -2402,7 +2402,7 @@ func (op *orderByOperator) reorderResults(result *milvuspb.SearchResults, indice newData := make([]string, n) for newIdx, oldIdx := range indices { if oldIdx < 0 || oldIdx >= len(strIds.Data) { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderResults: index %d out of bounds for string IDs (len=%d)", oldIdx, len(strIds.Data))) + return merr.WrapErrServiceInternalMsg("reorderResults: index %d out of bounds for string IDs (len=%d)", oldIdx, len(strIds.Data)) } newData[newIdx] = strIds.Data[oldIdx] } @@ -2415,7 +2415,7 @@ func (op *orderByOperator) reorderResults(result *milvuspb.SearchResults, indice newScores := make([]float32, n) for newIdx, oldIdx := range indices { if oldIdx < 0 || oldIdx >= len(results.Scores) { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderResults: index %d out of bounds for scores (len=%d)", oldIdx, len(results.Scores))) + return merr.WrapErrServiceInternalMsg("reorderResults: index %d out of bounds for scores (len=%d)", oldIdx, len(results.Scores)) } newScores[newIdx] = results.Scores[oldIdx] } @@ -2450,7 +2450,7 @@ func prepareNullableFieldDataReorder(field *schemapb.FieldData, indices []int) ( newValidData := make([]bool, len(indices)) for newIdx, oldIdx := range indices { if oldIdx < 0 || oldIdx >= len(validData) { - return nil, nil, 0, merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: index %d out of bounds for ValidData of field %s (len=%d)", oldIdx, field.GetFieldName(), len(validData))) + return nil, nil, 0, merr.WrapErrServiceInternalMsg("reorderFieldData: index %d out of bounds for ValidData of field %s (len=%d)", oldIdx, field.GetFieldName(), len(validData)) } newValidData[newIdx] = validData[oldIdx] } @@ -2470,7 +2470,7 @@ func countValidRows(validData []bool) int { func reorderNullableFloatVectorData(field *schemapb.FieldData, data []float32, width int, indices []int, newValidData []bool, logicalToPhysical []int, validCount int) ([]float32, error) { expected := validCount * width if len(data) != expected { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: nullable FloatVector field %s has %d elements, expected compact %d (valid=%d, dim=%d)", field.GetFieldName(), len(data), expected, validCount, width)) + return nil, merr.WrapErrServiceInternalMsg("reorderFieldData: nullable FloatVector field %s has %d elements, expected compact %d (valid=%d, dim=%d)", field.GetFieldName(), len(data), expected, validCount, width) } newData := make([]float32, 0, countValidRows(newValidData)*width) for _, oldIdx := range indices { @@ -2487,7 +2487,7 @@ func reorderNullableFloatVectorData(field *schemapb.FieldData, data []float32, w func reorderNullableByteVectorData(field *schemapb.FieldData, typeName string, data []byte, width int, indices []int, newValidData []bool, logicalToPhysical []int, validCount int) ([]byte, error) { expected := validCount * width if len(data) != expected { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: nullable %s field %s has %d bytes, expected compact %d (valid=%d, width=%d)", typeName, field.GetFieldName(), len(data), expected, validCount, width)) + return nil, merr.WrapErrServiceInternalMsg("reorderFieldData: nullable %s field %s has %d bytes, expected compact %d (valid=%d, width=%d)", typeName, field.GetFieldName(), len(data), expected, validCount, width) } newData := make([]byte, 0, countValidRows(newValidData)*width) for _, oldIdx := range indices { @@ -2503,7 +2503,7 @@ func reorderNullableByteVectorData(field *schemapb.FieldData, typeName string, d func reorderNullableSparseVectorData(field *schemapb.FieldData, contents [][]byte, indices []int, newValidData []bool, logicalToPhysical []int, validCount int) ([][]byte, error) { if len(contents) != validCount { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: nullable SparseFloatVector field %s has %d elements, expected compact %d", field.GetFieldName(), len(contents), validCount)) + return nil, merr.WrapErrServiceInternalMsg("reorderFieldData: nullable SparseFloatVector field %s has %d elements, expected compact %d", field.GetFieldName(), len(contents), validCount) } newContents := make([][]byte, 0, countValidRows(newValidData)) for _, oldIdx := range indices { @@ -2528,7 +2528,7 @@ func reorderFieldData(field *schemapb.FieldData, indices []int) error { newData := make([]int32, n) for newIdx, oldIdx := range indices { if oldIdx < 0 || oldIdx >= len(data.Data) { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: index %d out of bounds for %s field %s (len=%d)", oldIdx, field.GetType().String(), field.GetFieldName(), len(data.Data))) + return merr.WrapErrServiceInternalMsg("reorderFieldData: index %d out of bounds for %s field %s (len=%d)", oldIdx, field.GetType().String(), field.GetFieldName(), len(data.Data)) } newData[newIdx] = data.Data[oldIdx] } @@ -2539,7 +2539,7 @@ func reorderFieldData(field *schemapb.FieldData, indices []int) error { newData := make([]int64, n) for newIdx, oldIdx := range indices { if oldIdx < 0 || oldIdx >= len(data.Data) { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: index %d out of bounds for Int64 field %s (len=%d)", oldIdx, field.GetFieldName(), len(data.Data))) + return merr.WrapErrServiceInternalMsg("reorderFieldData: index %d out of bounds for Int64 field %s (len=%d)", oldIdx, field.GetFieldName(), len(data.Data)) } newData[newIdx] = data.Data[oldIdx] } @@ -2550,7 +2550,7 @@ func reorderFieldData(field *schemapb.FieldData, indices []int) error { newData := make([]float32, n) for newIdx, oldIdx := range indices { if oldIdx < 0 || oldIdx >= len(data.Data) { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: index %d out of bounds for Float field %s (len=%d)", oldIdx, field.GetFieldName(), len(data.Data))) + return merr.WrapErrServiceInternalMsg("reorderFieldData: index %d out of bounds for Float field %s (len=%d)", oldIdx, field.GetFieldName(), len(data.Data)) } newData[newIdx] = data.Data[oldIdx] } @@ -2561,7 +2561,7 @@ func reorderFieldData(field *schemapb.FieldData, indices []int) error { newData := make([]float64, n) for newIdx, oldIdx := range indices { if oldIdx < 0 || oldIdx >= len(data.Data) { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: index %d out of bounds for Double field %s (len=%d)", oldIdx, field.GetFieldName(), len(data.Data))) + return merr.WrapErrServiceInternalMsg("reorderFieldData: index %d out of bounds for Double field %s (len=%d)", oldIdx, field.GetFieldName(), len(data.Data)) } newData[newIdx] = data.Data[oldIdx] } @@ -2572,7 +2572,7 @@ func reorderFieldData(field *schemapb.FieldData, indices []int) error { newData := make([]string, n) for newIdx, oldIdx := range indices { if oldIdx < 0 || oldIdx >= len(data.Data) { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: index %d out of bounds for %s field %s (len=%d)", oldIdx, field.GetType().String(), field.GetFieldName(), len(data.Data))) + return merr.WrapErrServiceInternalMsg("reorderFieldData: index %d out of bounds for %s field %s (len=%d)", oldIdx, field.GetType().String(), field.GetFieldName(), len(data.Data)) } newData[newIdx] = data.Data[oldIdx] } @@ -2583,7 +2583,7 @@ func reorderFieldData(field *schemapb.FieldData, indices []int) error { newData := make([]bool, n) for newIdx, oldIdx := range indices { if oldIdx < 0 || oldIdx >= len(data.Data) { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: index %d out of bounds for Bool field %s (len=%d)", oldIdx, field.GetFieldName(), len(data.Data))) + return merr.WrapErrServiceInternalMsg("reorderFieldData: index %d out of bounds for Bool field %s (len=%d)", oldIdx, field.GetFieldName(), len(data.Data)) } newData[newIdx] = data.Data[oldIdx] } @@ -2594,7 +2594,7 @@ func reorderFieldData(field *schemapb.FieldData, indices []int) error { newData := make([][]byte, n) for newIdx, oldIdx := range indices { if oldIdx < 0 || oldIdx >= len(data.Data) { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: index %d out of bounds for JSON field %s (len=%d)", oldIdx, field.GetFieldName(), len(data.Data))) + return merr.WrapErrServiceInternalMsg("reorderFieldData: index %d out of bounds for JSON field %s (len=%d)", oldIdx, field.GetFieldName(), len(data.Data)) } newData[newIdx] = data.Data[oldIdx] } @@ -2605,7 +2605,7 @@ func reorderFieldData(field *schemapb.FieldData, indices []int) error { newData := make([]*schemapb.ScalarField, n) for newIdx, oldIdx := range indices { if oldIdx < 0 || oldIdx >= len(data.Data) { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: index %d out of bounds for Array field %s (len=%d)", oldIdx, field.GetFieldName(), len(data.Data))) + return merr.WrapErrServiceInternalMsg("reorderFieldData: index %d out of bounds for Array field %s (len=%d)", oldIdx, field.GetFieldName(), len(data.Data)) } newData[newIdx] = data.Data[oldIdx] } @@ -2616,7 +2616,7 @@ func reorderFieldData(field *schemapb.FieldData, indices []int) error { if vectors != nil && (newValidData != nil || vectors.GetFloatVector() != nil) { dim := int(vectors.GetDim()) if dim <= 0 { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: invalid dimension %d for FloatVector field %s", dim, field.GetFieldName())) + return merr.WrapErrServiceInternalMsg("reorderFieldData: invalid dimension %d for FloatVector field %s", dim, field.GetFieldName()) } var data []float32 if vectors.GetFloatVector() != nil { @@ -2631,12 +2631,12 @@ func reorderFieldData(field *schemapb.FieldData, indices []int) error { break } if len(data) != n*dim { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: FloatVector field %s has %d elements, expected %d (n=%d, dim=%d)", field.GetFieldName(), len(data), n*dim, n, dim)) + return merr.WrapErrServiceInternalMsg("reorderFieldData: FloatVector field %s has %d elements, expected %d (n=%d, dim=%d)", field.GetFieldName(), len(data), n*dim, n, dim) } newData := make([]float32, n*dim) for newIdx, oldIdx := range indices { if oldIdx < 0 || oldIdx >= n { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: index %d out of bounds for FloatVector field %s (n=%d)", oldIdx, field.GetFieldName(), n)) + return merr.WrapErrServiceInternalMsg("reorderFieldData: index %d out of bounds for FloatVector field %s (n=%d)", oldIdx, field.GetFieldName(), n) } srcStart := oldIdx * dim dstStart := newIdx * dim @@ -2650,7 +2650,7 @@ func reorderFieldData(field *schemapb.FieldData, indices []int) error { dim := int(vectors.GetDim()) bytesPerVector := dim / 8 if bytesPerVector <= 0 { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: invalid dimension %d for BinaryVector field %s", dim, field.GetFieldName())) + return merr.WrapErrServiceInternalMsg("reorderFieldData: invalid dimension %d for BinaryVector field %s", dim, field.GetFieldName()) } data := vectors.GetBinaryVector() if newValidData != nil { @@ -2662,12 +2662,12 @@ func reorderFieldData(field *schemapb.FieldData, indices []int) error { break } if len(data) != n*bytesPerVector { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: BinaryVector field %s has %d bytes, expected %d (n=%d, bytesPerVector=%d)", field.GetFieldName(), len(data), n*bytesPerVector, n, bytesPerVector)) + return merr.WrapErrServiceInternalMsg("reorderFieldData: BinaryVector field %s has %d bytes, expected %d (n=%d, bytesPerVector=%d)", field.GetFieldName(), len(data), n*bytesPerVector, n, bytesPerVector) } newData := make([]byte, n*bytesPerVector) for newIdx, oldIdx := range indices { if oldIdx < 0 || oldIdx >= n { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: index %d out of bounds for BinaryVector field %s (n=%d)", oldIdx, field.GetFieldName(), n)) + return merr.WrapErrServiceInternalMsg("reorderFieldData: index %d out of bounds for BinaryVector field %s (n=%d)", oldIdx, field.GetFieldName(), n) } srcStart := oldIdx * bytesPerVector dstStart := newIdx * bytesPerVector @@ -2680,7 +2680,7 @@ func reorderFieldData(field *schemapb.FieldData, indices []int) error { if vectors != nil && (newValidData != nil || len(vectors.GetFloat16Vector()) > 0) { dim := int(vectors.GetDim()) if dim <= 0 { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: invalid dimension %d for Float16Vector field %s", dim, field.GetFieldName())) + return merr.WrapErrServiceInternalMsg("reorderFieldData: invalid dimension %d for Float16Vector field %s", dim, field.GetFieldName()) } bytesPerVector := dim * 2 // 2 bytes per float16 data := vectors.GetFloat16Vector() @@ -2693,12 +2693,12 @@ func reorderFieldData(field *schemapb.FieldData, indices []int) error { break } if len(data) != n*bytesPerVector { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: Float16Vector field %s has %d bytes, expected %d (n=%d, bytesPerVector=%d)", field.GetFieldName(), len(data), n*bytesPerVector, n, bytesPerVector)) + return merr.WrapErrServiceInternalMsg("reorderFieldData: Float16Vector field %s has %d bytes, expected %d (n=%d, bytesPerVector=%d)", field.GetFieldName(), len(data), n*bytesPerVector, n, bytesPerVector) } newData := make([]byte, n*bytesPerVector) for newIdx, oldIdx := range indices { if oldIdx < 0 || oldIdx >= n { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: index %d out of bounds for Float16Vector field %s (n=%d)", oldIdx, field.GetFieldName(), n)) + return merr.WrapErrServiceInternalMsg("reorderFieldData: index %d out of bounds for Float16Vector field %s (n=%d)", oldIdx, field.GetFieldName(), n) } srcStart := oldIdx * bytesPerVector dstStart := newIdx * bytesPerVector @@ -2711,7 +2711,7 @@ func reorderFieldData(field *schemapb.FieldData, indices []int) error { if vectors != nil && (newValidData != nil || len(vectors.GetBfloat16Vector()) > 0) { dim := int(vectors.GetDim()) if dim <= 0 { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: invalid dimension %d for BFloat16Vector field %s", dim, field.GetFieldName())) + return merr.WrapErrServiceInternalMsg("reorderFieldData: invalid dimension %d for BFloat16Vector field %s", dim, field.GetFieldName()) } bytesPerVector := dim * 2 // 2 bytes per bfloat16 data := vectors.GetBfloat16Vector() @@ -2724,12 +2724,12 @@ func reorderFieldData(field *schemapb.FieldData, indices []int) error { break } if len(data) != n*bytesPerVector { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: BFloat16Vector field %s has %d bytes, expected %d (n=%d, bytesPerVector=%d)", field.GetFieldName(), len(data), n*bytesPerVector, n, bytesPerVector)) + return merr.WrapErrServiceInternalMsg("reorderFieldData: BFloat16Vector field %s has %d bytes, expected %d (n=%d, bytesPerVector=%d)", field.GetFieldName(), len(data), n*bytesPerVector, n, bytesPerVector) } newData := make([]byte, n*bytesPerVector) for newIdx, oldIdx := range indices { if oldIdx < 0 || oldIdx >= n { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: index %d out of bounds for BFloat16Vector field %s (n=%d)", oldIdx, field.GetFieldName(), n)) + return merr.WrapErrServiceInternalMsg("reorderFieldData: index %d out of bounds for BFloat16Vector field %s (n=%d)", oldIdx, field.GetFieldName(), n) } srcStart := oldIdx * bytesPerVector dstStart := newIdx * bytesPerVector @@ -2755,12 +2755,12 @@ func reorderFieldData(field *schemapb.FieldData, indices []int) error { break } if len(contents) != n { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: SparseFloatVector field %s has %d elements, expected %d", field.GetFieldName(), len(contents), n)) + return merr.WrapErrServiceInternalMsg("reorderFieldData: SparseFloatVector field %s has %d elements, expected %d", field.GetFieldName(), len(contents), n) } newContents := make([][]byte, n) for newIdx, oldIdx := range indices { if oldIdx < 0 || oldIdx >= n { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: index %d out of bounds for SparseFloatVector field %s (n=%d)", oldIdx, field.GetFieldName(), n)) + return merr.WrapErrServiceInternalMsg("reorderFieldData: index %d out of bounds for SparseFloatVector field %s (n=%d)", oldIdx, field.GetFieldName(), n) } newContents[newIdx] = contents[oldIdx] } @@ -2771,7 +2771,7 @@ func reorderFieldData(field *schemapb.FieldData, indices []int) error { if vectors != nil && (newValidData != nil || len(vectors.GetInt8Vector()) > 0) { dim := int(vectors.GetDim()) if dim <= 0 { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: invalid dimension %d for Int8Vector field %s", dim, field.GetFieldName())) + return merr.WrapErrServiceInternalMsg("reorderFieldData: invalid dimension %d for Int8Vector field %s", dim, field.GetFieldName()) } data := vectors.GetInt8Vector() if newValidData != nil { @@ -2783,12 +2783,12 @@ func reorderFieldData(field *schemapb.FieldData, indices []int) error { break } if len(data) != n*dim { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: Int8Vector field %s has %d bytes, expected %d (n=%d, dim=%d)", field.GetFieldName(), len(data), n*dim, n, dim)) + return merr.WrapErrServiceInternalMsg("reorderFieldData: Int8Vector field %s has %d bytes, expected %d (n=%d, dim=%d)", field.GetFieldName(), len(data), n*dim, n, dim) } newData := make([]byte, n*dim) for newIdx, oldIdx := range indices { if oldIdx < 0 || oldIdx >= n { - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: index %d out of bounds for Int8Vector field %s (n=%d)", oldIdx, field.GetFieldName(), n)) + return merr.WrapErrServiceInternalMsg("reorderFieldData: index %d out of bounds for Int8Vector field %s (n=%d)", oldIdx, field.GetFieldName(), n) } srcStart := oldIdx * dim dstStart := newIdx * dim @@ -2797,7 +2797,7 @@ func reorderFieldData(field *schemapb.FieldData, indices []int) error { vectors.Data = &schemapb.VectorField_Int8Vector{Int8Vector: newData} } default: - return merr.WrapErrServiceInternal(fmt.Sprintf("reorderFieldData: unhandled data type %s", field.GetType().String())) + return merr.WrapErrServiceInternalMsg("reorderFieldData: unhandled data type %s", field.GetType().String()) } // Reorder valid data if present diff --git a/internal/proxy/search_reduce_util.go b/internal/proxy/search_reduce_util.go index 8f8287e45b..2499a6c32b 100644 --- a/internal/proxy/search_reduce_util.go +++ b/internal/proxy/search_reduce_util.go @@ -2,9 +2,7 @@ package proxy import ( "context" - "fmt" - "github.com/cockroachdb/errors" "go.opentelemetry.io/otel" "go.uber.org/zap" "google.golang.org/protobuf/proto" @@ -142,7 +140,7 @@ func reduceSearchResultDataWithGroupBy( singleFieldGroupBy := len(groupByFieldIDs) == 1 if err := reduce.ValidateGroupByFieldsPresent(subSearchResultData, groupByFieldIDs, singleFieldGroupBy); err != nil { - return ret, merr.WrapErrServiceInternal("failed to construct group by field data builder", err.Error()) + return ret, merr.Wrap(err, "failed to construct group by field data builder") } if hitNum == 0 { ret.Results.Topks = make([]int64, nq) @@ -178,7 +176,7 @@ func reduceSearchResultDataWithGroupBy( } if err := reduce.WriteGroupByFieldValues(ret.Results, acceptedRows, subSearchResultData, groupByFieldIDs); err != nil { - return ret, merr.WrapErrServiceInternal("failed to construct group by field data builder", err.Error()) + return ret, merr.Wrap(err, "failed to construct group by field data builder") } if !metric.PositivelyRelated(metricType) { @@ -269,13 +267,13 @@ func runSingleFieldGroupByHotLoop( } if realTopK != -1 && realTopK != j { - log.Ctx(ctx).Warn("Proxy Reduce Search Result", zap.Error(errors.New("the length (topk) between all result of query is different"))) + log.Ctx(ctx).Warn("Proxy Reduce Search Result", zap.Error(merr.WrapErrServiceInternalMsg("the length (topk) between all result of query is different"))) } realTopK = j ret.Results.Topks = append(ret.Results.Topks, realTopK) if retSize > maxOutputSize { - return nil, fmt.Errorf("search results exceed the maxOutputSize Limit %d", maxOutputSize) + return nil, merr.WrapErrParameterInvalidMsg("search results exceed the maxOutputSize Limit %d", maxOutputSize) } } @@ -392,13 +390,13 @@ func runMultiFieldGroupByHotLoop( acceptedRows = append(acceptedRows, perNqAccepted...) if realTopK != -1 && realTopK != j { - log.Ctx(ctx).Warn("Proxy Reduce Search Result", zap.Error(errors.New("the length (topk) between all result of query is different"))) + log.Ctx(ctx).Warn("Proxy Reduce Search Result", zap.Error(merr.WrapErrServiceInternalMsg("the length (topk) between all result of query is different"))) } realTopK = j ret.Results.Topks = append(ret.Results.Topks, realTopK) if retSize > maxOutputSize { - return nil, fmt.Errorf("search results exceed the maxOutputSize Limit %d", maxOutputSize) + return nil, merr.WrapErrParameterInvalidMsg("search results exceed the maxOutputSize Limit %d", maxOutputSize) } } @@ -569,7 +567,7 @@ func reduceAdvanceGroupBy(ctx context.Context, subSearchResultData []*schemapb.S } if err := reduce.WriteGroupByFieldValues(ret.Results, acceptedRows, subSearchResultData, groupByFieldIDs); err != nil { - return ret, merr.WrapErrServiceInternal("failed to write group by field values", err.Error()) + return ret, merr.Wrap(err, "failed to write group by field values") } ret.Results.TopK = topK // realTopK is the topK of the nq-th query if !metric.PositivelyRelated(metricType) { @@ -736,15 +734,15 @@ func reduceSearchResultDataNoGroupBy(ctx context.Context, subSearchResultData [] cursors[subSearchIdx]++ } if realTopK != -1 && realTopK != j { - log.Ctx(ctx).Warn("Proxy Reduce Search Result", zap.Error(errors.New("the length (topk) between all result of query is different"))) - // return nil, errors.New("the length (topk) between all result of query is different") + log.Ctx(ctx).Warn("Proxy Reduce Search Result", zap.Error(merr.WrapErrParameterInvalidMsg("the length (topk) between all result of query is different"))) + // return nil, merr.WrapErrParameterInvalidMsg("the length (topk) between all result of query is different") } realTopK = j ret.Results.Topks = append(ret.Results.Topks, realTopK) // limit search result to avoid oom if retSize > maxOutputSize { - return nil, fmt.Errorf("search results exceed the maxOutputSize Limit %d", maxOutputSize) + return nil, merr.WrapErrParameterInvalidMsg("search results exceed the maxOutputSize Limit %d", maxOutputSize) } } ret.Results.TopK = realTopK // realTopK is the topK of the nq-th query @@ -783,7 +781,7 @@ func setupIdListForSearchResult(searchResult *milvuspb.SearchResults, pkType sch }, } default: - return errors.New("unsupported pk type") + return merr.WrapErrServiceInternalMsg("unsupported pk type") } return nil } @@ -856,14 +854,16 @@ func decodeSearchResults(ctx context.Context, searchResults []*internalpb.Search func checkSearchResultData(data *schemapb.SearchResultData, nq int64, topk int64, pkHitNum int) error { if data.NumQueries != nq { - return fmt.Errorf("search result's nq(%d) mis-match with %d", data.NumQueries, nq) + // The result shape comes from querynode/segcore, never from the request: + // a mismatch is an internal protocol violation, not user input. + return merr.WrapErrServiceInternalMsg("search result's nq(%d) mis-match with %d", data.NumQueries, nq) } if data.TopK != topk { - return fmt.Errorf("search result's topk(%d) mis-match with %d", data.TopK, topk) + return merr.WrapErrServiceInternalMsg("search result's topk(%d) mis-match with %d", data.TopK, topk) } if len(data.Scores) != pkHitNum { - return fmt.Errorf("search result's score length invalid, score length=%d, expectedLength=%d", + return merr.WrapErrServiceInternalMsg("search result's score length invalid, score length=%d, expectedLength=%d", len(data.Scores), pkHitNum) } return nil diff --git a/internal/proxy/search_util.go b/internal/proxy/search_util.go index 0c66be934d..105aeee375 100644 --- a/internal/proxy/search_util.go +++ b/internal/proxy/search_util.go @@ -7,7 +7,6 @@ import ( "strconv" "strings" - "github.com/cockroachdb/errors" "github.com/google/uuid" "github.com/tidwall/gjson" "google.golang.org/protobuf/proto" @@ -190,7 +189,7 @@ func parseOrderByFields(searchParamsPair []*commonpb.KeyValuePair, schema *schem return nil, err } if fieldSpec == "" { - return nil, fmt.Errorf("empty field name in order_by_fields") + return nil, merr.WrapErrParameterInvalidMsg("empty field name in order_by_fields") } ascending := true @@ -201,7 +200,7 @@ func parseOrderByFields(searchParamsPair []*commonpb.KeyValuePair, schema *schem case "desc", "descending": ascending = false default: - return nil, fmt.Errorf("invalid order direction '%s' for field '%s', expected 'asc' or 'desc'", direction, fieldSpec) + return nil, merr.WrapErrParameterInvalidMsg("invalid order direction '%s' for field '%s', expected 'asc' or 'desc'", direction, fieldSpec) } } @@ -213,7 +212,7 @@ func parseOrderByFields(searchParamsPair []*commonpb.KeyValuePair, schema *schem case orderByNullsLast: nullsFirst = false default: - return nil, fmt.Errorf("invalid null ordering '%s', expected '%s' or '%s'", nullOrdering, orderByNullsFirst, orderByNullsLast) + return nil, merr.WrapErrParameterInvalidMsg("invalid null ordering '%s', expected '%s' or '%s'", nullOrdering, orderByNullsFirst, orderByNullsLast) } } @@ -251,7 +250,7 @@ func splitOrderByFieldOptions(pair string) (fieldSpec, direction, nullOrdering s if bracketDepth == 0 { colonIdxs = append(colonIdxs, i) if len(colonIdxs) > 2 { - return "", "", "", fmt.Errorf("too many order_by field options in '%s'", pair) + return "", "", "", merr.WrapErrParameterInvalidMsg("too many order_by field options in '%s'", pair) } } } @@ -293,7 +292,7 @@ func parseOrderByFieldSpec(fieldSpec string, fieldSchemaMap map[string]*schemapb fieldID = field.GetFieldID() jsonPath, err = typeutil2.ParseAndVerifyNestedPath(fieldSpec, schema, fieldID) if err != nil { - return "", 0, "", "", false, fmt.Errorf("invalid JSON path in order_by field '%s': %w", fieldSpec, err) + return "", 0, "", "", false, merr.WrapErrParameterInvalidMsg("invalid JSON path in order_by field '%s': %v", fieldSpec, err) } outputFieldName = fieldSpec // Explicit $meta["key"] path; single-level, parser accepts it isDynamicField = true @@ -304,13 +303,13 @@ func parseOrderByFieldSpec(fieldSpec string, fieldSchemaMap map[string]*schemapb fieldID = field.GetFieldID() jsonPath, err = typeutil2.ParseAndVerifyNestedPath(fieldSpec, schema, fieldID) if err != nil { - return "", 0, "", "", false, fmt.Errorf("invalid JSON path in order_by field '%s': %w", fieldSpec, err) + return "", 0, "", "", false, merr.WrapErrParameterInvalidMsg("invalid JSON path in order_by field '%s': %v", fieldSpec, err) } outputFieldName = baseName // Request the whole JSON field isDynamicField = false } else { // Non-JSON field with brackets - not supported - return "", 0, "", "", false, fmt.Errorf("order_by field '%s' has brackets but is not a JSON type", fieldSpec) + return "", 0, "", "", false, merr.WrapErrParameterInvalidMsg("order_by field '%s' has brackets but is not a JSON type", fieldSpec) } } else if dynamicField != nil { // Unknown field name with brackets, treat as dynamic field path @@ -319,13 +318,13 @@ func parseOrderByFieldSpec(fieldSpec string, fieldSchemaMap map[string]*schemapb fieldID = dynamicField.GetFieldID() jsonPath, err = typeutil2.ParseAndVerifyNestedPath(fieldSpec, schema, fieldID) if err != nil { - return "", 0, "", "", false, fmt.Errorf("invalid JSON path in order_by field '%s': %w", fieldSpec, err) + return "", 0, "", "", false, merr.WrapErrParameterInvalidMsg("invalid JSON path in order_by field '%s': %v", fieldSpec, err) } // Request the base dynamic field; full path is in jsonPath outputFieldName = baseName isDynamicField = true } else { - return "", 0, "", "", false, fmt.Errorf("order_by field '%s' not found in schema and no dynamic field available", baseName) + return "", 0, "", "", false, merr.WrapErrParameterInvalidMsg("order_by field '%s' not found in schema and no dynamic field available", baseName) } } else { // No brackets - regular field name or dynamic field key @@ -338,7 +337,7 @@ func parseOrderByFieldSpec(fieldSpec string, fieldSchemaMap map[string]*schemapb isDynamicField = false // Validate sortable type if !isSortableFieldType(field.GetDataType()) { - return "", 0, "", "", false, fmt.Errorf("order_by field '%s' has unsortable type %s; supported types: bool, int8/16/32/64, float, double, string, varchar; for JSON fields use path syntax like field[\"key\"]", + return "", 0, "", "", false, merr.WrapErrParameterInvalidMsg("order_by field '%s' has unsortable type %s; supported types: bool, int8/16/32/64, float, double, string, varchar; for JSON fields use path syntax like field[\"key\"]", fieldSpec, field.GetDataType().String()) } } else if dynamicField != nil { @@ -347,13 +346,13 @@ func parseOrderByFieldSpec(fieldSpec string, fieldSchemaMap map[string]*schemapb fieldID = dynamicField.GetFieldID() jsonPath, err = typeutil2.ParseAndVerifyNestedPath(fieldSpec, schema, fieldID) if err != nil { - return "", 0, "", "", false, fmt.Errorf("invalid dynamic field key '%s': %w", fieldSpec, err) + return "", 0, "", "", false, merr.WrapErrParameterInvalidMsg("invalid dynamic field key '%s': %v", fieldSpec, err) } // For dynamic fields, pass the original key so translateOutputFields can extract it outputFieldName = fieldSpec isDynamicField = true } else { - return "", 0, "", "", false, fmt.Errorf("order_by field '%s' does not exist in collection schema", fieldSpec) + return "", 0, "", "", false, merr.WrapErrParameterInvalidMsg("order_by field '%s' does not exist in collection schema", fieldSpec) } } @@ -390,7 +389,7 @@ func parseSearchIteratorV2Info(searchParamsPair []*commonpb.KeyValuePair, groupB // iteratorV1 and iteratorV2 should be set together for compatibility if !isIterator { - return nil, fmt.Errorf("both %s and %s must be set in the SDK", IteratorField, SearchIterV2Key) + return nil, merr.WrapErrParameterMissingMsg("both %s and %s must be set in the SDK", IteratorField, SearchIterV2Key) } // disable groupBy when doing iteratorV2 @@ -417,22 +416,22 @@ func parseSearchIteratorV2Info(searchParamsPair []*commonpb.KeyValuePair, groupB } else { // Validate existing token is a valid UUID if _, err := uuid.Parse(token); err != nil { - return nil, errors.New("invalid token format") + return nil, merr.WrapErrParameterInvalidMsg("invalid token format") } } // parse batch size, required non-zero value batchSizeStr, _ := funcutil.GetAttrByKeyFromRepeatedKV(SearchIterBatchSizeKey, searchParamsPair) if batchSizeStr == "" { - return nil, errors.New("batch size is required") + return nil, merr.WrapErrParameterMissingMsg("batch size is required") } batchSize, err := strconv.ParseInt(batchSizeStr, 0, 64) if err != nil { - return nil, fmt.Errorf("batch size is invalid, %w", err) + return nil, merr.WrapErrParameterInvalidMsg("batch size is invalid, %v", err) } // use the same validation logic as topk if err := validateLimit(batchSize, largeTopKEnabled); err != nil { - return nil, fmt.Errorf("batch size is invalid, %w", err) + return nil, merr.WrapErrParameterInvalidMsg("batch size is invalid, %v", err) } *queryTopK = batchSize // for compatibility @@ -447,7 +446,7 @@ func parseSearchIteratorV2Info(searchParamsPair []*commonpb.KeyValuePair, groupB if lastBoundStr != "" { lastBound, err := strconv.ParseFloat(lastBoundStr, 32) if err != nil { - return nil, fmt.Errorf("failed to parse input last bound, %w", err) + return nil, merr.WrapErrParameterInvalidMsg("failed to parse input last bound, %v", err) } lastBoundFloat32 := float32(lastBound) planIteratorV2Info.LastBound = &lastBoundFloat32 // escape pointer @@ -464,14 +463,14 @@ func parseSearchInfo(searchParamsPair []*commonpb.KeyValuePair, schema *schemapb topKStr, err := funcutil.GetAttrByKeyFromRepeatedKV(TopKKey, searchParamsPair) if err != nil { if externalLimit <= 0 { - return nil, fmt.Errorf("%s is required", TopKKey) + return nil, merr.WrapErrParameterMissingMsg("%s is required", TopKKey) } topK = externalLimit } else { topKInParam, err := strconv.ParseInt(topKStr, 0, 64) if err != nil { if externalLimit <= 0 { - return nil, fmt.Errorf("%s [%s] is invalid", TopKKey, topKStr) + return nil, merr.WrapErrParameterInvalidMsg("%s [%s] is invalid", TopKKey, topKStr) } topK = externalLimit } else { @@ -495,7 +494,7 @@ func parseSearchInfo(searchParamsPair []*commonpb.KeyValuePair, schema *schemapb topK = Params.QuotaConfig.TopKLimit.GetAsInt64() } } else { - return nil, fmt.Errorf("%s [%d] is invalid, %w", TopKKey, topK, err) + return nil, merr.WrapErrParameterInvalidMsg("%s [%d] is invalid, %v", TopKKey, topK, err) } } @@ -506,12 +505,12 @@ func parseSearchInfo(searchParamsPair []*commonpb.KeyValuePair, schema *schemapb if err == nil { offset, err = strconv.ParseInt(offsetStr, 0, 64) if err != nil { - return nil, fmt.Errorf("%s [%s] is invalid", OffsetKey, offsetStr) + return nil, merr.WrapErrParameterInvalidMsg("%s [%s] is invalid", OffsetKey, offsetStr) } if offset != 0 { if err := validateLimit(offset, largeTopKEnabled); err != nil { - return nil, fmt.Errorf("%s [%d] is invalid, %w", OffsetKey, offset, err) + return nil, merr.WrapErrParameterInvalidMsg("%s [%d] is invalid, %v", OffsetKey, offset, err) } } } @@ -519,7 +518,7 @@ func parseSearchInfo(searchParamsPair []*commonpb.KeyValuePair, schema *schemapb queryTopK := topK + offset if err := validateLimit(queryTopK, largeTopKEnabled); err != nil { - return nil, fmt.Errorf("%s+%s [%d] is invalid, %w", OffsetKey, TopKKey, queryTopK, err) + return nil, merr.WrapErrParameterInvalidMsg("%s+%s [%d] is invalid, %v", OffsetKey, TopKKey, queryTopK, err) } // 2. parse metrics type @@ -541,11 +540,11 @@ func parseSearchInfo(searchParamsPair []*commonpb.KeyValuePair, schema *schemapb roundDecimal, err := strconv.ParseInt(roundDecimalStr, 0, 64) if err != nil { - return nil, fmt.Errorf("%s [%s] is invalid, should be -1 or an integer in range [0, 6]", RoundDecimalKey, roundDecimalStr) + return nil, merr.WrapErrParameterInvalidMsg("%s [%s] is invalid, should be -1 or an integer in range [0, 6]", RoundDecimalKey, roundDecimalStr) } if roundDecimal != -1 && (roundDecimal > 6 || roundDecimal < 0) { - return nil, fmt.Errorf("%s [%s] is invalid, should be -1 or an integer in range [0, 6]", RoundDecimalKey, roundDecimalStr) + return nil, merr.WrapErrParameterInvalidMsg("%s [%s] is invalid, should be -1 or an integer in range [0, 6]", RoundDecimalKey, roundDecimalStr) } // 4. parse search param str @@ -599,7 +598,7 @@ func parseSearchInfo(searchParamsPair []*commonpb.KeyValuePair, schema *schemapb planSearchIteratorV2Info, err := parseSearchIteratorV2Info(searchParamsPair, groupByFieldId, isIterator, offset, &queryTopK, largeTopKEnabled) if err != nil { - return nil, fmt.Errorf("parse iterator v2 info failed: %w", err) + return nil, merr.WrapErrParameterInvalidMsg("parse iterator v2 info failed: %v", err) } // 7. parse order_by_fields @@ -643,7 +642,7 @@ func getOutputFieldIDs(schema *schemaInfo, outputFields []string) (outputFieldID for _, name := range outputFields { id, ok := schema.MapFieldID(name) if !ok { - return nil, fmt.Errorf("field %s not exist", name) + return nil, merr.WrapErrParameterInvalidMsg("Field %s not exist", name) } outputFieldIDs = append(outputFieldIDs, id) } @@ -705,7 +704,7 @@ func getPartitionIDs(ctx context.Context, dbName string, collectionName string, pattern := fmt.Sprintf("^%s$", partitionName) re, err := regexp.Compile(pattern) if err != nil { - return nil, fmt.Errorf("invalid partition: %s", partitionName) + return nil, merr.WrapErrParameterInvalidMsg("invalid partition: %s", partitionName) } var found bool for name, pID := range partitionsMap { @@ -715,13 +714,13 @@ func getPartitionIDs(ctx context.Context, dbName string, collectionName string, } } if !found { - return nil, fmt.Errorf("partition name %s not found", partitionName) + return nil, merr.WrapErrParameterInvalidMsg("partition name %s not found", partitionName) } } else { partitionID, found := partitionsMap[partitionName] if !found { // TODO change after testcase updated: return nil, merr.WrapErrPartitionNotFound(partitionName) - return nil, fmt.Errorf("partition name %s not found", partitionName) + return nil, merr.WrapErrParameterInvalidMsg("partition name %s not found", partitionName) } partitionsSet.Insert(partitionID) } @@ -852,7 +851,7 @@ func parseGroupByField(groupByFieldName string, schema *schemapb.CollectionSchem jsonPath = groupByFieldName } else { // Case 2.3: Field not found and no dynamic field - return -1, "", merr.WrapErrFieldNotFound(groupByFieldName, "groupBy field not found in schema") + return -1, "", merr.WrapErrAsInputError(merr.WrapErrFieldNotFound(groupByFieldName, "groupBy field not found in schema")) } } } else { @@ -867,7 +866,7 @@ func parseGroupByField(groupByFieldName string, schema *schemapb.CollectionSchem jsonPath = groupByFieldName } else { // Case 2.3: Field not found and no dynamic field - return -1, "", merr.WrapErrFieldNotFound(groupByFieldName, "groupBy field not found in schema") + return -1, "", merr.WrapErrAsInputError(merr.WrapErrFieldNotFound(groupByFieldName, "groupBy field not found in schema")) } } } @@ -990,24 +989,24 @@ func parseRankParams(rankParamsPair []*commonpb.KeyValuePair, schema *schemapb.C limitStr, err := funcutil.GetAttrByKeyFromRepeatedKV(LimitKey, rankParamsPair) if err != nil { - return nil, errors.New(LimitKey + " not found in rank_params") + return nil, merr.WrapErrParameterInvalidMsg(LimitKey + " not found in rank_params") } limit, err = strconv.ParseInt(limitStr, 0, 64) if err != nil { - return nil, fmt.Errorf("%s [%s] is invalid", LimitKey, limitStr) + return nil, merr.WrapErrParameterInvalidMsg("%s [%s] is invalid", LimitKey, limitStr) } offsetStr, err := funcutil.GetAttrByKeyFromRepeatedKV(OffsetKey, rankParamsPair) if err == nil { offset, err = strconv.ParseInt(offsetStr, 0, 64) if err != nil { - return nil, fmt.Errorf("%s [%s] is invalid", OffsetKey, offsetStr) + return nil, merr.WrapErrParameterInvalidMsg("%s [%s] is invalid", OffsetKey, offsetStr) } } // validate max result window. if err = validateMaxQueryResultWindow(offset, limit, largeTopKEnabled); err != nil { - return nil, fmt.Errorf("invalid max query result window, %w", err) + return nil, merr.WrapErrParameterInvalidMsg("invalid max query result window, %v", err) } roundDecimalStr, err := funcutil.GetAttrByKeyFromRepeatedKV(RoundDecimalKey, rankParamsPair) @@ -1017,11 +1016,11 @@ func parseRankParams(rankParamsPair []*commonpb.KeyValuePair, schema *schemapb.C roundDecimal, err = strconv.ParseInt(roundDecimalStr, 0, 64) if err != nil { - return nil, fmt.Errorf("%s [%s] is invalid, should be -1 or an integer in range [0, 6]", RoundDecimalKey, roundDecimalStr) + return nil, merr.WrapErrParameterInvalidMsg("%s [%s] is invalid, should be -1 or an integer in range [0, 6]", RoundDecimalKey, roundDecimalStr) } if roundDecimal != -1 && (roundDecimal > 6 || roundDecimal < 0) { - return nil, fmt.Errorf("%s [%s] is invalid, should be -1 or an integer in range [0, 6]", RoundDecimalKey, roundDecimalStr) + return nil, merr.WrapErrParameterInvalidMsg("%s [%s] is invalid, should be -1 or an integer in range [0, 6]", RoundDecimalKey, roundDecimalStr) } // parse group_by parameters from main request body for hybrid search diff --git a/internal/proxy/service_provider.go b/internal/proxy/service_provider.go index 36c8bb9611..bcf992eca7 100644 --- a/internal/proxy/service_provider.go +++ b/internal/proxy/service_provider.go @@ -58,7 +58,7 @@ func NewInterceptor[Req Request, Resp Response](proxy *Proxy, method string) (*I } return interface{}(interceptor).(*InterceptorImpl[Req, Resp]), nil default: - return nil, fmt.Errorf("method %s not supported", method) + return nil, merr.WrapErrParameterInvalidMsg("method %s not supported", method) } } @@ -290,7 +290,7 @@ func (node *RemoteProxyServiceProvider) DescribeCollection(ctx context.Context, zap.Uint64("EndTS", dct.EndTs())) metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, - metrics.FailLabel, request.GetDbName(), request.GetCollectionName()).Inc() + failMetricLabel(err), request.GetDbName(), request.GetCollectionName()).Inc() return nil, err } diff --git a/internal/proxy/shardclient/lb_policy.go b/internal/proxy/shardclient/lb_policy.go index 20be8ec2e1..66cc8a7dbd 100644 --- a/internal/proxy/shardclient/lb_policy.go +++ b/internal/proxy/shardclient/lb_policy.go @@ -329,6 +329,13 @@ func (lb *LBPolicyImpl) ExecuteWithRetry(ctx context.Context, workload ChannelWo log.Warn("search/query channel failed", zap.Int64("nodeID", targetNode.NodeID), zap.Error(err)) + // An input error is the request's own fault: re-dispatching it to + // other replicas cannot make it succeed, and blacklisting the + // (healthy) serving node would penalize it for a bad request. Abort + // immediately without retrying or touching the blacklist. + if merr.GetErrorType(err) == merr.InputError { + return false, err + } if merr.IsRetryableErr(err) { requestExcludedNodes.Insert(targetNode.NodeID) } else { @@ -424,7 +431,10 @@ func (lb *LBPolicyImpl) ExecuteOneChannel(ctx context.Context, workload Collecti PreferredNodeID: preferredNodeID(workload, channel), }) } - return fmt.Errorf("no acitvate sheard leader exist for collection: %s", workload.CollectionName) + // An empty leader list here is a transient routing-cache state (leaders are + // re-discovered on retry); reporting "collection not loaded" would tell the + // user to re-load a collection that is loaded. + return merr.WrapErrServiceUnavailable(fmt.Sprintf("no available shard leader for collection %d", workload.CollectionID)) } func (lb *LBPolicyImpl) UpdateCostMetrics(node int64, cost *internalpb.CostAggregation) { diff --git a/internal/proxy/shardclient/lb_policy_test.go b/internal/proxy/shardclient/lb_policy_test.go index b56b5fc411..d6f391fb37 100644 --- a/internal/proxy/shardclient/lb_policy_test.go +++ b/internal/proxy/shardclient/lb_policy_test.go @@ -653,7 +653,7 @@ func (s *LBPolicySuite) TestExecuteWithRetryNonRetriableErrorUsesBlacklist() { Channel: channel, Nq: 1, Exec: func(ctx context.Context, nodeID UniqueID, qn types.QueryNodeClient, channel string) error { - return errors.Wrapf(merr.ErrParameterInvalid, "fail on QueryNode %d", nodeID) + return errors.Wrapf(merr.ErrServiceInternal, "fail on QueryNode %d", nodeID) }, }) @@ -661,6 +661,45 @@ func (s *LBPolicySuite) TestExecuteWithRetryNonRetriableErrorUsesBlacklist() { s.Contains(s.lbPolicy.blacklist.GetBlacklistedNodes(channel), int64(1)) } +// TestExecuteWithRetryInputErrorSkipsBlacklist verifies that an input error +// (the request's own fault) does not blacklist the serving node nor get retried +// across replicas. +func (s *LBPolicySuite) TestExecuteWithRetryInputErrorSkipsBlacklist() { + ctx := context.Background() + channel := s.channels[0] + nodes := []NodeInfo{{NodeID: 1, Address: "localhost:9000", Serviceable: true}} + s.lbPolicy.retryOnReplica = 3 + + s.mgr.ExpectedCalls = nil + s.lbBalancer.ExpectedCalls = nil + s.mgr.EXPECT().GetShard(mock.Anything, true, s.dbName, s.collectionName, s.collectionID, channel).Return(nodes, nil) + s.mgr.EXPECT().GetShard(mock.Anything, false, s.dbName, s.collectionName, s.collectionID, channel).Return(nodes, nil).Maybe() + s.mgr.EXPECT().GetClient(mock.Anything, mock.Anything).Return(s.qn, nil) + s.lbBalancer.EXPECT().RegisterNodeInfo(mock.Anything) + s.lbBalancer.EXPECT().SelectNode(mock.Anything, mock.Anything, mock.Anything).Return(int64(1), nil) + s.lbBalancer.EXPECT().CancelWorkload(mock.Anything, mock.Anything) + + execCount := 0 + err := s.lbPolicy.ExecuteWithRetry(ctx, ChannelWorkload{ + Db: s.dbName, + CollectionName: s.collectionName, + CollectionID: s.collectionID, + Channel: channel, + Nq: 1, + Exec: func(ctx context.Context, nodeID UniqueID, qn types.QueryNodeClient, channel string) error { + execCount++ + return errors.Wrapf(merr.ErrParameterInvalid, "bad request on QueryNode %d", nodeID) + }, + }) + + s.Error(err) + s.ErrorIs(err, merr.ErrParameterInvalid) + // not retried across replicas despite retryOnReplica=3 + s.Equal(1, execCount) + // serving node not blacklisted for the request's own fault + s.NotContains(s.lbPolicy.blacklist.GetBlacklistedNodes(channel), int64(1)) +} + func (s *LBPolicySuite) TestExecuteOneChannel() { ctx := context.Background() mockErr := errors.New("mock error") diff --git a/internal/proxy/shardclient/shard_client.go b/internal/proxy/shardclient/shard_client.go index 9eca6ca8cb..1ade0621cf 100644 --- a/internal/proxy/shardclient/shard_client.go +++ b/internal/proxy/shardclient/shard_client.go @@ -28,6 +28,7 @@ import ( "github.com/milvus-io/milvus/internal/types" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -46,8 +47,6 @@ func (n NodeInfo) String() string { return fmt.Sprintf("", n.NodeID, n.Serviceable, n.Address) } -var errClosed = errors.New("client is closed") - type shardClient struct { sync.RWMutex info NodeInfo @@ -128,11 +127,11 @@ func (n *shardClient) roundRobinSelectClient() (types.QueryNodeClient, error) { n.RLock() defer n.RUnlock() if n.isClosed { - return nil, errClosed + return nil, merr.WrapErrServiceUnavailable("client is closed") } if len(n.clients) == 0 { - return nil, errors.New("no available clients") + return nil, merr.WrapErrServiceUnavailable("no available clients") } nextClientIndex := n.idx.Inc() % int64(len(n.clients)) diff --git a/internal/proxy/simple_rate_limiter.go b/internal/proxy/simple_rate_limiter.go index 70ceb1fbe2..3ee39479e4 100644 --- a/internal/proxy/simple_rate_limiter.go +++ b/internal/proxy/simple_rate_limiter.go @@ -34,6 +34,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/internalpb" "github.com/milvus-io/milvus/pkg/v3/proto/proxypb" "github.com/milvus-io/milvus/pkg/v3/util" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/ratelimitutil" "github.com/milvus-io/milvus/pkg/v3/util/retry" @@ -284,7 +285,7 @@ func (m *SimpleLimiter) updateLimiterNode(req *proxypb.Limiter, node *rlinternal for _, rate := range req.GetRates() { limit, ok := curLimiters.Get(rate.GetRt()) if !ok { - return fmt.Errorf("unregister rateLimiter for rateType %s", rate.GetRt().String()) + return merr.WrapErrParameterInvalidMsg("unregister rateLimiter for rateType %s", rate.GetRt().String()) } limit.SetLimit(ratelimitutil.Limit(rate.GetR())) setRateGaugeByRateType(rate.GetRt(), paramtable.GetNodeID(), sourceID, rate.GetR()) diff --git a/internal/proxy/snapshot_impl.go b/internal/proxy/snapshot_impl.go index f476e37fb9..05cbe7fe1f 100644 --- a/internal/proxy/snapshot_impl.go +++ b/internal/proxy/snapshot_impl.go @@ -63,7 +63,7 @@ func (node *Proxy) CreateSnapshot(ctx context.Context, req *milvuspb.CreateSnaps } if err := t.WaitToFinish(); err != nil { - metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.FailLabel, req.GetDbName(), req.GetCollectionName()).Inc() + metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failMetricLabel(err), req.GetDbName(), req.GetCollectionName()).Inc() log.Warn("CreateSnapshot failed to WaitToFinish", zap.Error(err)) return merr.Status(err), nil @@ -103,7 +103,7 @@ func (node *Proxy) DropSnapshot(ctx context.Context, req *milvuspb.DropSnapshotR } if err := t.WaitToFinish(); err != nil { - metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.FailLabel, req.GetDbName(), req.GetCollectionName()).Inc() + metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failMetricLabel(err), req.GetDbName(), req.GetCollectionName()).Inc() log.Warn("DropSnapshot failed to WaitToFinish", zap.Error(err)) return merr.Status(err), nil @@ -145,7 +145,7 @@ func (node *Proxy) DescribeSnapshot(ctx context.Context, req *milvuspb.DescribeS } if err := t.WaitToFinish(); err != nil { - metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.FailLabel, req.GetDbName(), req.GetCollectionName()).Inc() + metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failMetricLabel(err), req.GetDbName(), req.GetCollectionName()).Inc() log.Warn("DescribeSnapshot failed to WaitToFinish", zap.Error(err)) return &milvuspb.DescribeSnapshotResponse{ @@ -188,7 +188,7 @@ func (node *Proxy) ListSnapshots(ctx context.Context, req *milvuspb.ListSnapshot } if err := t.WaitToFinish(); err != nil { - metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.FailLabel, req.GetDbName(), req.GetCollectionName()).Inc() + metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failMetricLabel(err), req.GetDbName(), req.GetCollectionName()).Inc() log.Warn("ListSnapshots failed to WaitToFinish", zap.Error(err)) return &milvuspb.ListSnapshotsResponse{ @@ -230,7 +230,7 @@ func (node *Proxy) RestoreSnapshot(ctx context.Context, req *milvuspb.RestoreSna } if err := t.WaitToFinish(); err != nil { - metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.FailLabel, req.GetDbName(), req.GetCollectionName()).Inc() + metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failMetricLabel(err), req.GetDbName(), req.GetCollectionName()).Inc() log.Warn("RestoreSnapshot failed to WaitToFinish", zap.Error(err)) return &milvuspb.RestoreSnapshotResponse{Status: merr.Status(err)}, nil @@ -272,7 +272,7 @@ func (node *Proxy) GetRestoreSnapshotState(ctx context.Context, req *milvuspb.Ge } if err := t.WaitToFinish(); err != nil { - metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.FailLabel, "", "").Inc() + metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failMetricLabel(err), "", "").Inc() log.Warn("GetRestoreSnapshotState failed to WaitToFinish", zap.Error(err)) return &milvuspb.GetRestoreSnapshotStateResponse{ @@ -316,7 +316,7 @@ func (node *Proxy) ListRestoreSnapshotJobs(ctx context.Context, req *milvuspb.Li } if err := t.WaitToFinish(); err != nil { - metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.FailLabel, req.GetDbName(), req.GetCollectionName()).Inc() + metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failMetricLabel(err), req.GetDbName(), req.GetCollectionName()).Inc() log.Warn("ListRestoreSnapshotJobs failed to WaitToFinish", zap.Error(err)) return &milvuspb.ListRestoreSnapshotJobsResponse{ @@ -361,7 +361,7 @@ func (node *Proxy) PinSnapshotData(ctx context.Context, req *milvuspb.PinSnapsho } if err := t.WaitToFinish(); err != nil { - metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.FailLabel, req.GetDbName(), req.GetCollectionName()).Inc() + metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failMetricLabel(err), req.GetDbName(), req.GetCollectionName()).Inc() log.Warn("PinSnapshotData failed to WaitToFinish", zap.Error(err)) return &milvuspb.PinSnapshotDataResponse{ @@ -402,7 +402,7 @@ func (node *Proxy) UnpinSnapshotData(ctx context.Context, req *milvuspb.UnpinSna } if err := t.WaitToFinish(); err != nil { - metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, metrics.FailLabel, "", "").Inc() + metrics.ProxyFunctionCall.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), method, failMetricLabel(err), "", "").Inc() log.Warn("UnpinSnapshotData failed to WaitToFinish", zap.Error(err)) return merr.Status(err), nil diff --git a/internal/proxy/struct_hybrid_search.go b/internal/proxy/struct_hybrid_search.go index 3eeeae982f..a67ff57cac 100644 --- a/internal/proxy/struct_hybrid_search.go +++ b/internal/proxy/struct_hybrid_search.go @@ -77,7 +77,7 @@ func parseAndRemoveElementScope(searchParamStr string) (elementCollapseConfig, b delete(root, elementScopeKey) sanitized, err := json.Marshal(root) if err != nil { - return elementCollapseConfig{}, false, "", merr.WrapErrServiceInternal(fmt.Sprintf("failed to rewrite search params without %s: %v", elementScopeKey, err)) + return elementCollapseConfig{}, false, "", merr.WrapErrServiceInternalMsg("failed to rewrite search params without %s: %v", elementScopeKey, err) } return cfg, true, string(sanitized), nil } @@ -94,7 +94,7 @@ func parseElementScope(scopeRaw json.RawMessage) (elementCollapseConfig, error) } collapseRaw, ok := scope["collapse"] if !ok { - return elementCollapseConfig{}, merr.WrapErrParameterInvalidMsg("%s.collapse is required", elementScopeKey) + return elementCollapseConfig{}, merr.WrapErrParameterMissingMsg("%s.collapse is required", elementScopeKey) } var collapse map[string]json.RawMessage @@ -115,7 +115,7 @@ func parseElementScope(scopeRaw json.RawMessage) (elementCollapseConfig, error) } strategy = strings.TrimSpace(strategy) if strategy == "" { - return elementCollapseConfig{}, merr.WrapErrParameterInvalidMsg("%s.collapse.strategy is required", elementScopeKey) + return elementCollapseConfig{}, merr.WrapErrParameterMissingMsg("%s.collapse.strategy is required", elementScopeKey) } if !isSupportedElementCollapseStrategy(strategy) { return elementCollapseConfig{}, merr.WrapErrParameterInvalidMsg("unsupported %s.collapse.strategy: %s", elementScopeKey, strategy) @@ -134,7 +134,7 @@ func parseElementScope(scopeRaw json.RawMessage) (elementCollapseConfig, error) switch strategy { case elementCollapseTopKSum, elementCollapseTopKAvg: if cfg.TopK <= 0 { - return elementCollapseConfig{}, merr.WrapErrParameterInvalidMsg("%s.collapse.topk is required for strategy %s", elementScopeKey, strategy) + return elementCollapseConfig{}, merr.WrapErrParameterMissingMsg("%s.collapse.topk is required for strategy %s", elementScopeKey, strategy) } default: if cfg.TopK != 0 { diff --git a/internal/proxy/task.go b/internal/proxy/task.go index 3704ef770a..c931b126f0 100644 --- a/internal/proxy/task.go +++ b/internal/proxy/task.go @@ -268,11 +268,11 @@ func (t *createCollectionTask) validatePartitionKey(ctx context.Context) error { for i, field := range t.schema.Fields { if field.GetIsPartitionKey() { if idx != -1 { - return fmt.Errorf("there are more than one partition key, field name = %s, %s", t.schema.Fields[idx].Name, field.Name) + return merr.WrapErrParameterInvalidMsg("there are more than one partition key, field name = %s, %s", t.schema.Fields[idx].Name, field.Name) } if field.GetIsPrimaryKey() { - return errors.New("the partition key field must not be primary field") + return merr.WrapErrParameterInvalidMsg("the partition key field must not be primary field") } if field.GetNullable() { @@ -281,11 +281,11 @@ func (t *createCollectionTask) validatePartitionKey(ctx context.Context) error { // The type of the partition key field can only be int64 and varchar if field.DataType != schemapb.DataType_Int64 && field.DataType != schemapb.DataType_VarChar { - return errors.New("the data type of partition key should be Int64 or VarChar") + return merr.WrapErrParameterInvalidMsg("the data type of partition key should be Int64 or VarChar") } if t.GetNumPartitions() < 0 { - return errors.New("the specified partitions should be greater than 0 if partition key is used") + return merr.WrapErrParameterInvalidMsg("the specified partitions should be greater than 0 if partition key is used") } maxPartitionNum := Params.RootCoordCfg.MaxPartitionNum.GetAsInt64() @@ -319,13 +319,13 @@ func (t *createCollectionTask) validatePartitionKey(ctx context.Context) error { mustPartitionKey := Params.ProxyCfg.MustUsePartitionKey.GetAsBool() if mustPartitionKey && idx == -1 { - return merr.WrapErrParameterInvalidMsg("partition key must be set when creating the collection" + + return merr.WrapErrParameterMissingMsg("partition key must be set when creating the collection" + " because the mustUsePartitionKey config is true") } if idx == -1 { if t.GetNumPartitions() != 0 { - return errors.New("num_partitions should only be specified with partition key field enabled") + return merr.WrapErrParameterInvalidMsg("num_partitions should only be specified with partition key field enabled") } } else { log.Ctx(ctx).Info("create collection with partition key mode", @@ -464,21 +464,21 @@ func (t *createCollectionTask) PreExecute(ctx context.Context) error { // External collections must be single-shard: the refresh mechanism assigns all // segments to VChannelNames[0], so multiple shards would leave segments orphaned. if isExternalCollection && t.ShardsNum > 1 { - return fmt.Errorf("external collection does not support multiple shards, got ShardsNum=%d", t.ShardsNum) + return merr.WrapErrParameterInvalidMsg("external collection does not support multiple shards, got ShardsNum=%d", t.ShardsNum) } if t.ShardsNum > Params.ProxyCfg.MaxShardNum.GetAsInt32() { - return fmt.Errorf("maximum shards's number should be limited to %d", Params.ProxyCfg.MaxShardNum.GetAsInt()) + return merr.WrapErrParameterInvalidMsg("maximum shards's number should be limited to %d", Params.ProxyCfg.MaxShardNum.GetAsInt()) } totalFieldsNum := typeutil.GetTotalFieldsNum(t.schema) if totalFieldsNum > Params.ProxyCfg.MaxFieldNum.GetAsInt() { - return fmt.Errorf("maximum field's number should be limited to %d", Params.ProxyCfg.MaxFieldNum.GetAsInt()) + return merr.WrapErrParameterInvalidMsg("maximum field's number should be limited to %d", Params.ProxyCfg.MaxFieldNum.GetAsInt()) } vectorFields := len(typeutil.GetVectorFieldSchemas(t.schema)) if vectorFields > Params.ProxyCfg.MaxVectorFieldNum.GetAsInt() { - return fmt.Errorf("maximum vector field's number should be limited to %d", Params.ProxyCfg.MaxVectorFieldNum.GetAsInt()) + return merr.WrapErrParameterInvalidMsg("maximum vector field's number should be limited to %d", Params.ProxyCfg.MaxVectorFieldNum.GetAsInt()) } if vectorFields == 0 { @@ -594,7 +594,7 @@ func (t *createCollectionTask) PreExecute(ctx context.Context) error { // This allows different structs to have fields with the same name err = transformStructFieldNames(t.schema) if err != nil { - return fmt.Errorf("failed to transform struct field names: %v", err) + return merr.WrapErrParameterInvalidMsg("failed to transform struct field names: %v", err) } // validate whether field names duplicates (after transformation) @@ -698,7 +698,7 @@ func (t *addCollectionFieldTask) PreExecute(ctx context.Context) error { // User-added fields must be nullable so that old segments without this field can return // NULL rather than causing a schema inconsistency at query time. if !t.fieldSchema.GetNullable() { - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("added field must be nullable, please check it, field name = %s", t.fieldSchema.GetName())) + return merr.WrapErrParameterInvalidMsg("added field must be nullable, please check it, field name = %s", t.fieldSchema.GetName()) } if err := ValidateField(t.fieldSchema, t.oldSchema); err != nil { return err @@ -826,7 +826,7 @@ func validateAddStructFieldRequest(schema *schemapb.CollectionSchema, structFiel totalFieldsNum := typeutil.GetTotalFieldsNum(schema) + len(structFieldSchema.GetFields()) + 1 if totalFieldsNum > Params.ProxyCfg.MaxFieldNum.GetAsInt() { - return fmt.Errorf("maximum field's number should be limited to %d", Params.ProxyCfg.MaxFieldNum.GetAsInt()) + return merr.WrapErrParameterInvalidMsg("maximum field's number should be limited to %d", Params.ProxyCfg.MaxFieldNum.GetAsInt()) } vectorFields := len(typeutil.GetVectorFieldSchemas(schema)) @@ -836,7 +836,7 @@ func validateAddStructFieldRequest(schema *schemapb.CollectionSchema, structFiel } } if vectorFields > Params.ProxyCfg.MaxVectorFieldNum.GetAsInt() { - return fmt.Errorf("maximum vector field's number should be limited to %d", Params.ProxyCfg.MaxVectorFieldNum.GetAsInt()) + return merr.WrapErrParameterInvalidMsg("maximum vector field's number should be limited to %d", Params.ProxyCfg.MaxVectorFieldNum.GetAsInt()) } return nil @@ -932,11 +932,10 @@ func validateAddFieldRequest(schema *schemapb.CollectionSchema, newFieldSchema * fieldList.Insert(structArrayField.GetName()) } if typeutil.GetTotalFieldsNum(schema) >= Params.ProxyCfg.MaxFieldNum.GetAsInt() { - msg := fmt.Sprintf("The number of fields has reached the maximum value %d", Params.ProxyCfg.MaxFieldNum.GetAsInt()) - return merr.WrapErrParameterInvalidMsg(msg) + return merr.WrapErrParameterInvalidMsg("The number of fields has reached the maximum value %d", Params.ProxyCfg.MaxFieldNum.GetAsInt()) } if fieldList.Contain(newFieldSchema.GetName()) { - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("duplicated field name %s", newFieldSchema.GetName())) + return merr.WrapErrParameterInvalidMsg("duplicated field name %s", newFieldSchema.GetName()) } // --- new field property constraints --- @@ -963,13 +962,13 @@ func validateAddFieldRequest(schema *schemapb.CollectionSchema, newFieldSchema * return err } if funcutil.SliceContain([]string{common.RowIDFieldName, common.TimeStampFieldName, common.MetaFieldName, common.NamespaceFieldName, common.VirtualPKFieldName}, newFieldSchema.GetName()) { - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("not support to add system field, field name = %s", newFieldSchema.GetName())) + return merr.WrapErrParameterInvalidMsg("not support to add system field, field name = %s", newFieldSchema.GetName()) } if newFieldSchema.GetIsPrimaryKey() { - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("not support to add pk field, field name = %s", newFieldSchema.GetName())) + return merr.WrapErrParameterInvalidMsg("not support to add pk field, field name = %s", newFieldSchema.GetName()) } if newFieldSchema.GetAutoID() { - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("only primary field can speficy AutoID with true, field name = %s", newFieldSchema.GetName())) + return merr.WrapErrParameterInvalidMsg("only primary field can speficy AutoID with true, field name = %s", newFieldSchema.GetName()) } if newFieldSchema.GetIsPartitionKey() { return merr.WrapErrParameterInvalidMsg("not support to add partition key field, field name = %s", newFieldSchema.GetName()) @@ -982,14 +981,14 @@ func validateAddFieldRequest(schema *schemapb.CollectionSchema, newFieldSchema * } for _, f := range schema.GetFields() { if f.GetIsClusteringKey() { - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("already has another clustering key field, field name: %s", newFieldSchema.GetName())) + return merr.WrapErrParameterInvalidMsg("already has another clustering key field, field name: %s", newFieldSchema.GetName()) } } } if typeutil.IsVectorType(newFieldSchema.DataType) { vectorFields := len(typeutil.GetVectorFieldSchemas(schema)) if vectorFields >= Params.ProxyCfg.MaxVectorFieldNum.GetAsInt() { - return fmt.Errorf("maximum vector field's number should be limited to %d", Params.ProxyCfg.MaxVectorFieldNum.GetAsInt()) + return merr.WrapErrParameterInvalidMsg("maximum vector field's number should be limited to %d", Params.ProxyCfg.MaxVectorFieldNum.GetAsInt()) } } @@ -1008,7 +1007,7 @@ func validateAddFieldRequest(schema *schemapb.CollectionSchema, newFieldSchema * newFieldSchema.DataType == schemapb.DataType_BinaryVector || newFieldSchema.DataType == schemapb.DataType_Int8Vector { if len(newFieldSchema.TypeParams) == 0 { - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("vector field must have dimension specified, field name = %s", newFieldSchema.GetName())) + return merr.WrapErrParameterInvalidMsg("vector field must have dimension specified, field name = %s", newFieldSchema.GetName()) } } } @@ -1096,6 +1095,8 @@ func (t *alterCollectionSchemaTask) preExecuteAdd(ctx context.Context) error { // RootCoord enforces the same constraint (ddl_callbacks_alter_collection_schema.go); // validate early at Proxy to give clearer error messages. if len(funcSchemas) != 1 || funcSchemas[0] == nil { + // Count constraint (covers both zero and >1), not a pure missing param, + // so keep this as an invalid-parameter error. return merr.WrapErrParameterInvalidMsg("For now, exactly one function schema is required in alter schema task") } functionType := funcSchemas[0].GetType() @@ -1103,7 +1104,7 @@ func (t *alterCollectionSchemaTask) preExecuteAdd(ctx context.Context) error { return merr.WrapErrParameterInvalidMsg("For now, only BM25 and MinHash functions are supported in alter schema task") } if len(fieldInfos) == 0 { - return merr.WrapErrParameterInvalidMsg("fieldInfos is empty, function output fields are required") + return merr.WrapErrParameterMissingMsg("fieldInfos is empty, function output fields are required") } if len(fieldInfos) != 1 { @@ -1188,7 +1189,7 @@ func (t *alterCollectionSchemaTask) preExecuteDrop(ctx context.Context) error { return validateDropField(t.oldSchema, id.FieldName) default: - return merr.WrapErrParameterInvalidMsg("drop request must specify field_name, field_id, or function_name") + return merr.WrapErrParameterMissingMsg("drop request must specify field_name, field_id, or function_name") } } @@ -1779,7 +1780,7 @@ func (t *describeCollectionTask) Execute(ctx context.Context) error { } if err := restoreStructFieldNames(t.result.Schema); err != nil { - return fmt.Errorf("failed to restore struct field names: %v", err) + return merr.WrapErrParameterInvalidMsg("failed to restore struct field names: %v", err) } return nil @@ -1887,16 +1888,19 @@ func (t *showCollectionsTask) Execute(ctx context.Context) error { } if resp == nil { - return errors.New("failed to show collections") + return merr.WrapErrServiceInternalMsg("failed to show collections") } if resp.GetStatus().GetErrorCode() != commonpb.ErrorCode_Success { // update collectionID to collection name, and return new error info to sdk - newErrorReason := resp.GetStatus().GetReason() + // while preserving the downstream typed code (rebuild via merr.Error). + status := resp.GetStatus() + newErrorReason := status.GetReason() for _, collectionID := range collectionIDs { newErrorReason = ReplaceID2Name(newErrorReason, collectionID, IDs2Names[collectionID]) } - return errors.New(newErrorReason) + status.Reason = newErrorReason + return merr.Error(status) } t.result = &milvuspb.ShowCollectionsResponse{ @@ -2051,7 +2055,7 @@ func checkVectorIndexExist(ctx context.Context, dbName, collectionName string, c IndexName: "", }) if err = merr.CheckRPCCall(indexResponse, err); err != nil && !errors.Is(err, merr.ErrIndexNotFound) { - return "", merr.WrapErrServiceInternal("describe index failed", err.Error()) + return "", merr.Wrap(err, "describe index failed") } for _, index := range indexResponse.IndexInfos { for _, field := range collSchema.Fields { @@ -2657,7 +2661,7 @@ func (t *createPartitionTask) PreExecute(ctx context.Context) error { return err } if typeutil.HasPartitionKey(collSchema.CollectionSchema) { - return errors.New("disable create partition if partition key mode is used") + return merr.WrapErrParameterInvalidMsg("disable create partition if partition key mode is used") } if err := validatePartitionTag(partitionTag, true); err != nil { @@ -2756,7 +2760,7 @@ func (t *dropPartitionTask) PreExecute(ctx context.Context) error { return err } if typeutil.HasPartitionKey(collSchema.CollectionSchema) { - return errors.New("disable drop partition if partition key mode is used") + return merr.WrapErrParameterInvalidMsg("disable drop partition if partition key mode is used") } if err := validatePartitionTag(partitionTag, true); err != nil { @@ -2785,7 +2789,7 @@ func (t *dropPartitionTask) PreExecute(ctx context.Context) error { return err } if loaded { - return errors.New("partition cannot be dropped, partition is loaded, please release it first") + return merr.WrapErrParameterInvalidMsg("partition cannot be dropped, partition is loaded, please release it first") } } @@ -2995,7 +2999,7 @@ func (t *showPartitionsTask) Execute(ctx context.Context) error { if !ok { log.Ctx(ctx).Debug("Failed to get partition id.", zap.String("partitionName", partitionName), zap.Int64("requestID", t.Base.MsgID), zap.String("requestType", "showPartitions")) - return errors.New("failed to show partitions") + return merr.WrapErrParameterInvalidMsg("failed to show partitions") } partitionInfo, err := globalMetaCache.GetPartitionInfo(ctx, t.GetDbName(), collectionName, partitionName) if err != nil { @@ -3157,7 +3161,7 @@ func (t *loadCollectionTask) Execute(ctx context.Context) (err error) { if len(unindexedVecFields) != 0 { errMsg := fmt.Sprintf("there is no vector index on field: %v, please create index firstly", unindexedVecFields) log.Debug(errMsg) - return errors.New(errMsg) + return merr.WrapErrParameterInvalidMsg("%s", errMsg) } request := &querypb.LoadCollectionRequest{ Base: commonpbutil.UpdateMsgBase( @@ -3179,7 +3183,7 @@ func (t *loadCollectionTask) Execute(ctx context.Context) (err error) { zap.Int32("priority", int32(request.GetPriority()))) t.result, err = t.mixCoord.LoadCollection(ctx, request) if err = merr.CheckRPCCall(t.result, err); err != nil { - return fmt.Errorf("call query coordinator LoadCollection: %s", err) + return merr.Wrap(err, "call query coordinator LoadCollection") } return nil } @@ -3348,7 +3352,7 @@ func (t *loadPartitionsTask) PreExecute(ctx context.Context) error { return err } if partitionKeyMode { - return errors.New("disable load partitions if partition key mode is used") + return merr.WrapErrParameterInvalidMsg("disable load partitions if partition key mode is used") } return nil @@ -3417,7 +3421,7 @@ func (t *loadPartitionsTask) Execute(ctx context.Context) error { if len(unindexedVecFields) != 0 { errMsg := fmt.Sprintf("there is no vector index on field: %v, please create index firstly", unindexedVecFields) log.Ctx(ctx).Debug(errMsg) - return errors.New(errMsg) + return merr.WrapErrParameterInvalidMsg("%s", errMsg) } for _, partitionName := range t.PartitionNames { @@ -3428,7 +3432,7 @@ func (t *loadPartitionsTask) Execute(ctx context.Context) error { partitionIDs = append(partitionIDs, partitionID) } if len(partitionIDs) == 0 { - return errors.New("failed to load partition, due to no partition specified") + return merr.WrapErrParameterInvalidMsg("failed to load partition, due to no partition specified") } request := &querypb.LoadPartitionsRequest{ Base: commonpbutil.UpdateMsgBase( @@ -3525,7 +3529,7 @@ func (t *releasePartitionsTask) PreExecute(ctx context.Context) error { return err } if partitionKeyMode { - return errors.New("disable release partitions if partition key mode is used") + return merr.WrapErrParameterInvalidMsg("disable release partitions if partition key mode is used") } return nil @@ -4145,14 +4149,14 @@ func (t *RunAnalyzerTask) PreExecute(ctx context.Context) error { collID, err := globalMetaCache.GetCollectionID(ctx, t.dbName, t.GetCollectionName()) if err != nil { // err is not nil if collection not exists - return merr.WrapErrAsInputErrorWhen(err, merr.ErrCollectionNotFound, merr.ErrDatabaseNotFound) + return err } t.collectionID = collID schema, err := globalMetaCache.GetCollectionSchema(ctx, t.dbName, t.GetCollectionName()) if err != nil { // err is not nil if collection not exists - return merr.WrapErrAsInputErrorWhen(err, merr.ErrCollectionNotFound, merr.ErrDatabaseNotFound) + return err } fieldId, ok := schema.MapFieldID(t.GetFieldName()) @@ -4297,7 +4301,7 @@ func isIgnoreGrowing(params []*commonpb.KeyValuePair) (bool, error) { if kv.GetKey() == IgnoreGrowingKey { ignoreGrowing, err := strconv.ParseBool(kv.GetValue()) if err != nil { - return false, errors.New("parse ignore growing field failed") + return false, merr.WrapErrParameterInvalidMsg("parse ignore growing field failed") } return ignoreGrowing, nil } diff --git a/internal/proxy/task_alias.go b/internal/proxy/task_alias.go index 8751cdeec3..abd9903ee7 100644 --- a/internal/proxy/task_alias.go +++ b/internal/proxy/task_alias.go @@ -191,7 +191,9 @@ func (t *DropAliasTask) Execute(ctx context.Context) error { var err error t.result, err = t.mixCoord.DropAlias(ctx, t.DropAliasRequest) if err = merr.CheckRPCCall(t.result, err); err != nil { - return err + // alias/collection/db names are caller-supplied; a not-found from rootcoord + // is the user's input error, not a system fault. + return merr.WrapErrAsInputErrorWhen(err, merr.ErrAliasNotFound, merr.ErrCollectionNotFound, merr.ErrDatabaseNotFound) } return nil } @@ -277,7 +279,9 @@ func (t *AlterAliasTask) Execute(ctx context.Context) error { var err error t.result, err = t.mixCoord.AlterAlias(ctx, t.AlterAliasRequest) if err = merr.CheckRPCCall(t.result, err); err != nil { - return err + // alias/collection/db names are caller-supplied; a not-found from rootcoord + // is the user's input error, not a system fault. + return merr.WrapErrAsInputErrorWhen(err, merr.ErrAliasNotFound, merr.ErrCollectionNotFound, merr.ErrDatabaseNotFound) } return nil } @@ -347,7 +351,10 @@ func (a *DescribeAliasTask) PreExecute(ctx context.Context) error { func (a *DescribeAliasTask) Execute(ctx context.Context) error { var err error a.result, err = a.mixCoord.DescribeAlias(ctx, a.DescribeAliasRequest) - return merr.CheckRPCCall(a.result, err) + // alias/collection/db names are caller-supplied; a not-found from rootcoord is + // the user's input error, not a system fault. + return merr.WrapErrAsInputErrorWhen(merr.CheckRPCCall(a.result, err), + merr.ErrAliasNotFound, merr.ErrCollectionNotFound, merr.ErrDatabaseNotFound) } func (a *DescribeAliasTask) PostExecute(ctx context.Context) error { diff --git a/internal/proxy/task_delete.go b/internal/proxy/task_delete.go index e915448e28..0d54bc9261 100644 --- a/internal/proxy/task_delete.go +++ b/internal/proxy/task_delete.go @@ -6,7 +6,6 @@ import ( "strconv" "time" - "github.com/cockroachdb/errors" "go.uber.org/atomic" "go.uber.org/zap" "google.golang.org/protobuf/proto" @@ -274,13 +273,13 @@ func (dr *deleteRunner) Init(ctx context.Context) error { db, err := globalMetaCache.GetDatabaseInfo(ctx, dr.req.GetDbName()) if err != nil { - return merr.WrapErrAsInputErrorWhen(err, merr.ErrDatabaseNotFound) + return err } dr.dbID = db.dbID dr.collectionID, err = globalMetaCache.GetCollectionID(ctx, dr.req.GetDbName(), collName) if err != nil { - return ErrWithLog(log, "Failed to get collection id", merr.WrapErrAsInputErrorWhen(err, merr.ErrCollectionNotFound)) + return ErrWithLog(log, "Failed to get collection id", err) } dr.schema, err = globalMetaCache.GetCollectionSchema(ctx, dr.req.GetDbName(), collName) @@ -314,7 +313,7 @@ func (dr *deleteRunner) Init(ctx context.Context) error { partName := dr.req.GetPartitionName() if dr.schema.IsPartitionKeyCollection() { if len(partName) > 0 { - return errors.New("not support manually specifying the partition names if partition key mode is used") + return merr.WrapErrParameterInvalidMsg("not support manually specifying the partition names if partition key mode is used") } expr, err := exprutil.ParseExprFromPlan(dr.plan) if err != nil { @@ -644,7 +643,7 @@ func getPrimaryKeysFromUnaryRangeExpr(schema *schemapb.CollectionSchema, unaryRa }, } default: - return pks, errors.New("invalid field data type specifyed in simple delete expr") + return pks, merr.WrapErrParameterInvalidMsg("invalid field data type specifyed in simple delete expr") } return pks, nil @@ -675,7 +674,7 @@ func getPrimaryKeysFromTermExpr(schema *schemapb.CollectionSchema, termExpr *pla }, } default: - return pks, 0, errors.New("invalid field data type specifyed in simple delete expr") + return pks, 0, merr.WrapErrParameterInvalidMsg("invalid field data type specifyed in simple delete expr") } return pks, pkCount, nil diff --git a/internal/proxy/task_delete_streaming.go b/internal/proxy/task_delete_streaming.go index ea541fc789..7ae699480c 100644 --- a/internal/proxy/task_delete_streaming.go +++ b/internal/proxy/task_delete_streaming.go @@ -44,7 +44,7 @@ func (dt *deleteTask) Execute(ctx context.Context) (err error) { schema, err := globalMetaCache.GetCollectionSchema(ctx, dt.req.GetDbName(), dt.req.GetCollectionName()) if err != nil { log.Ctx(ctx).Warn("get collection schema from global meta cache failed", zap.String("collectionName", dt.req.GetCollectionName()), zap.Error(err)) - return merr.WrapErrAsInputErrorWhen(err, merr.ErrCollectionNotFound, merr.ErrDatabaseNotFound) + return err } ez = hookutil.GetEzByCollProperties(schema.GetProperties(), dt.collectionID).AsMessageConfig() diff --git a/internal/proxy/task_flush_all_streaming.go b/internal/proxy/task_flush_all_streaming.go index 80e299e06a..60f54b9224 100644 --- a/internal/proxy/task_flush_all_streaming.go +++ b/internal/proxy/task_flush_all_streaming.go @@ -18,7 +18,6 @@ package proxy import ( "context" - "fmt" "github.com/samber/lo" @@ -35,7 +34,7 @@ func (t *flushAllTask) Execute(ctx context.Context) error { Base: commonpbutil.NewMsgBase(commonpbutil.WithMsgType(commonpb.MsgType_Flush)), }) if err = merr.CheckRPCCall(resp, err); err != nil { - return fmt.Errorf("failed to call flush all to data coordinator: %s", err.Error()) + return merr.Wrap(err, "failed to call flush all to data coordinator") } t.result = &milvuspb.FlushAllResponse{ Status: merr.Success(), diff --git a/internal/proxy/task_flush_streaming.go b/internal/proxy/task_flush_streaming.go index 47c44351aa..447d3f5fd0 100644 --- a/internal/proxy/task_flush_streaming.go +++ b/internal/proxy/task_flush_streaming.go @@ -18,7 +18,6 @@ package proxy import ( "context" - "fmt" "go.uber.org/zap" @@ -49,7 +48,7 @@ func (t *flushTask) Execute(ctx context.Context) error { for _, collName := range t.CollectionNames { collID, err := globalMetaCache.GetCollectionID(t.ctx, t.DbName, collName) if err != nil { - return merr.WrapErrAsInputErrorWhen(err, merr.ErrCollectionNotFound, merr.ErrDatabaseNotFound) + return err } vchannels, err := t.chMgr.getVChannels(collID) @@ -77,7 +76,7 @@ func (t *flushTask) Execute(ctx context.Context) error { } resp, err := t.mixCoord.Flush(ctx, flushReq) if err = merr.CheckRPCCall(resp, err); err != nil { - return fmt.Errorf("failed to call flush to data coordinator: %s", err.Error()) + return merr.Wrap(err, "failed to call flush to data coordinator") } // Remove the flushed segments from onFlushSegmentIDs diff --git a/internal/proxy/task_import.go b/internal/proxy/task_import.go index 2db567a6b0..1e5f2ab096 100644 --- a/internal/proxy/task_import.go +++ b/internal/proxy/task_import.go @@ -20,7 +20,6 @@ package proxy import ( "context" - "fmt" "github.com/samber/lo" "go.uber.org/zap" @@ -103,7 +102,7 @@ func (it *importTask) PreExecute(ctx context.Context) error { return err } if schema.CollectionSchema == nil || len(schema.GetFields()) == 0 { - return merr.WrapErrImportFailed("collection schema has no fields") + return merr.WrapErrImportSysFailed("collection schema has no fields") } if err := validateTextStorageV3Enabled(schema.CollectionSchema); err != nil { return err @@ -123,7 +122,7 @@ func (it *importTask) PreExecute(ctx context.Context) error { var partitionIDs []int64 if isBackup { if req.GetPartitionName() == "" { - return merr.WrapErrParameterInvalidMsg("partition not specified") + return merr.WrapErrParameterMissingMsg("partition not specified") } // Currently, Backup tool call import must with a partition name, each time restore a partition partitionID, err := globalMetaCache.GetPartitionID(ctx, req.GetDbName(), req.GetCollectionName(), req.GetPartitionName()) @@ -185,8 +184,8 @@ func (it *importTask) PreExecute(ctx context.Context) error { return merr.WrapErrParameterInvalidMsg("import request is empty") } if len(req.Files) > Params.DataCoordCfg.MaxFilesPerImportReq.GetAsInt() { - return merr.WrapErrImportFailed(fmt.Sprintf("The max number of import files should not exceed %d, but got %d", - Params.DataCoordCfg.MaxFilesPerImportReq.GetAsInt(), len(req.Files))) + return merr.WrapErrImportFailedMsg("The max number of import files should not exceed %d, but got %d", + Params.DataCoordCfg.MaxFilesPerImportReq.GetAsInt(), len(req.Files)) } if !isBackup && !isL0Import { // check file type diff --git a/internal/proxy/task_index.go b/internal/proxy/task_index.go index 3cd4f6e9d1..6c479d50e7 100644 --- a/internal/proxy/task_index.go +++ b/internal/proxy/task_index.go @@ -235,7 +235,7 @@ func (cit *createIndexTask) parseFunctionParamsToIndex(indexParamsMap map[string switch cit.functionSchema.GetType() { case schemapb.FunctionType_Unknown: - return errors.New("unknown function type encountered") + return merr.WrapErrParameterInvalidMsg("unknown function type encountered") case schemapb.FunctionType_BM25: // set default BM25 params if not provided in index params @@ -254,7 +254,7 @@ func (cit *createIndexTask) parseFunctionParamsToIndex(indexParamsMap map[string if metricType, ok := indexParamsMap["metric_type"]; !ok { indexParamsMap["metric_type"] = metric.BM25 } else if metricType != metric.BM25 { - return fmt.Errorf("index metric type of BM25 function output field must be BM25, got %s", metricType) + return merr.WrapErrParameterInvalidMsg("index metric type of BM25 function output field must be BM25, got %s", metricType) } default: @@ -358,7 +358,7 @@ func (cit *createIndexTask) parseIndexParams(ctx context.Context) error { } else if typeutil.IsGeometryType(dataType) { return Params.AutoIndexConfig.ScalarGeometryIndexType.GetValue(), nil } - return "", fmt.Errorf("create auto index on type:%s is not supported", dataType.String()) + return "", merr.WrapErrParameterInvalidMsg("create auto index on type:%s is not supported", dataType.String()) }() if err != nil { return merr.WrapErrParameterInvalid("supported field", err.Error()) @@ -460,12 +460,12 @@ func (cit *createIndexTask) parseIndexParams(ctx context.Context) error { } if len(indexParamsMap) > numberParams+1 { - return errors.New("only metric type can be passed when use AutoIndex") + return merr.WrapErrParameterInvalidMsg("only metric type can be passed when use AutoIndex") } if len(indexParamsMap) == numberParams+1 { if !metricTypeExist { - return errors.New("only metric type can be passed when use AutoIndex") + return merr.WrapErrParameterInvalidMsg("only metric type can be passed when use AutoIndex") } // only metric type is passed. @@ -530,7 +530,7 @@ func (cit *createIndexTask) parseIndexParams(ctx context.Context) error { indexType, exist := indexParamsMap[common.IndexTypeKey] if !exist { - return errors.New("IndexType not specified") + return merr.WrapErrParameterMissingMsg("IndexType not specified") } // index parameters defined in the YAML file are merged with the user-provided parameters during create stage if Params.KnowhereConfig.Enable.GetAsBool() { @@ -589,6 +589,13 @@ func (cit *createIndexTask) parseIndexParams(ctx context.Context) error { err := checkTrain(ctx, cit.fieldSchema, indexParamsMap) if err != nil { + // checkTrain may propagate errors from indexparamcheck (not yet + // merr-standardized). Already-merr errors (leaves / fillDimension / + // the merr-returning checkers) pass through with their real code; + // only legacy plain errors get wrapped once into ParameterInvalid. + if merr.IsMilvusError(err) { + return err + } return merr.WrapErrParameterInvalid("valid index params", "invalid index params", err.Error()) } @@ -621,20 +628,20 @@ func (cit *createIndexTask) getIndexedFieldAndFunction(ctx context.Context) erro schema, err := globalMetaCache.GetCollectionSchema(ctx, cit.req.GetDbName(), cit.req.GetCollectionName()) if err != nil { log.Ctx(ctx).Error("failed to get collection schema", zap.Error(err)) - return fmt.Errorf("failed to get collection schema: %s", err) + return merr.Wrap(err, "failed to get collection schema") } field, err := schema.schemaHelper.GetFieldFromNameDefaultJSON(cit.req.GetFieldName()) if err != nil { log.Ctx(ctx).Error("create index on non-exist field", zap.Error(err)) - return fmt.Errorf("cannot create index on non-exist field: %s", cit.req.GetFieldName()) + return merr.WrapErrParameterInvalidMsg("cannot create index on non-exist field: %s", cit.req.GetFieldName()) } if field.IsFunctionOutput { function, err := schema.schemaHelper.GetFunctionByOutputField(field) if err != nil { log.Ctx(ctx).Error("create index failed, cannot find function of function output field", zap.Error(err)) - return fmt.Errorf("create index failed, cannot find function of function output field: %s", cit.req.GetFieldName()) + return merr.WrapErrParameterInvalidMsg("create index failed, cannot find function of function output field: %s", cit.req.GetFieldName()) } cit.functionSchema = function } @@ -651,12 +658,12 @@ func fillDimension(field *schemapb.FieldSchema, indexParams map[string]string) e params = append(params, field.GetIndexParams()...) dimensionInSchema, err := funcutil.GetAttrByKeyFromRepeatedKV(DimKey, params) if err != nil { - return errors.New("dimension not found in schema") + return merr.WrapErrParameterInvalidMsg("dimension not found in schema") } dimension, exist := indexParams[DimKey] if exist { if dimensionInSchema != dimension { - return fmt.Errorf("dimension mismatch, dimension in schema: %s, dimension: %s", dimensionInSchema, dimension) + return merr.WrapErrParameterInvalidMsg("dimension mismatch, dimension in schema: %s, dimension: %s", dimensionInSchema, dimension) } } else { indexParams[DimKey] = dimensionInSchema @@ -680,7 +687,7 @@ func checkTrain(ctx context.Context, field *schemapb.FieldSchema, indexParams ma checker, err := indexparamcheck.GetIndexCheckerMgrInstance().GetChecker(indexType) if err != nil { log.Ctx(ctx).Warn("Failed to get index checker", zap.String(common.IndexTypeKey, indexType)) - return fmt.Errorf("invalid index type: %s", indexType) + return merr.WrapErrParameterInvalidMsg("invalid index type: %s", indexType) } // For ArrayOfVector with non-EmbList metrics (e.g., COSINE, L2, IP), each embedding @@ -698,7 +705,7 @@ func checkTrain(ctx context.Context, field *schemapb.FieldSchema, indexParams ma if typeutil.IsVectorType(field.DataType) && indexType != indexparamcheck.AutoIndex { exist := CheckVecIndexWithDataTypeExist(indexType, effectiveDataType, effectiveElementType) if !exist { - return fmt.Errorf("data type %s can't build with this index %s", schemapb.DataType_name[int32(field.GetDataType())], indexType) + return merr.WrapErrParameterInvalidMsg("data type %s can't build with this index %s", schemapb.DataType_name[int32(field.GetDataType())], indexType) } } @@ -993,7 +1000,7 @@ func (dit *describeIndexTask) Execute(ctx context.Context) error { schema, err := globalMetaCache.GetCollectionSchema(ctx, dit.GetDbName(), dit.GetCollectionName()) if err != nil { log.Ctx(ctx).Error("failed to get collection schema", zap.Error(err)) - return fmt.Errorf("failed to get collection schema: %s", err) + return merr.Wrap(err, "failed to get collection schema") } resp, err := dit.mixCoord.DescribeIndex(ctx, &indexpb.DescribeIndexRequest{CollectionID: dit.collectionID, IndexName: dit.IndexName, Timestamp: dit.Timestamp}) @@ -1015,7 +1022,7 @@ func (dit *describeIndexTask) Execute(ctx context.Context) error { field, err := schema.schemaHelper.GetFieldFromID(indexInfo.FieldID) if err != nil { log.Ctx(ctx).Error("failed to get collection field", zap.Error(err)) - return fmt.Errorf("failed to get collection field: %d", indexInfo.FieldID) + return merr.WrapErrParameterInvalidMsg("failed to get collection field: %d", indexInfo.FieldID) } params := indexInfo.GetUserIndexParams() if params == nil { @@ -1133,7 +1140,7 @@ func (dit *getIndexStatisticsTask) Execute(ctx context.Context) error { schema, err := globalMetaCache.GetCollectionSchema(ctx, dit.GetDbName(), dit.GetCollectionName()) if err != nil { log.Ctx(ctx).Error("failed to get collection schema", zap.String("collection_name", dit.GetCollectionName()), zap.Error(err)) - return fmt.Errorf("failed to get collection schema: %s", dit.GetCollectionName()) + return merr.Wrap(err, "failed to get collection schema") } schemaHelper := schema.schemaHelper @@ -1149,7 +1156,7 @@ func (dit *getIndexStatisticsTask) Execute(ctx context.Context) error { field, err := schemaHelper.GetFieldFromID(indexInfo.FieldID) if err != nil { log.Ctx(ctx).Error("failed to get collection field", zap.Int64("field_id", indexInfo.FieldID), zap.Error(err)) - return fmt.Errorf("failed to get collection field: %d", indexInfo.FieldID) + return merr.WrapErrParameterInvalidMsg("failed to get collection field: %d", indexInfo.FieldID) } params := indexInfo.GetUserIndexParams() if params == nil { diff --git a/internal/proxy/task_insert.go b/internal/proxy/task_insert.go index 1d6af21c4e..f3b7978088 100644 --- a/internal/proxy/task_insert.go +++ b/internal/proxy/task_insert.go @@ -153,7 +153,7 @@ func (it *insertTask) PreExecute(ctx context.Context) error { schema, err := globalMetaCache.GetCollectionSchema(ctx, it.insertMsg.GetDbName(), collectionName) if err != nil { log.Ctx(ctx).Warn("get collection schema from global meta cache failed", zap.String("collectionName", collectionName), zap.Error(err)) - return merr.WrapErrAsInputErrorWhen(err, merr.ErrCollectionNotFound, merr.ErrDatabaseNotFound) + return err } it.schema = schema.CollectionSchema it.schemaVersion = schema.Version diff --git a/internal/proxy/task_query.go b/internal/proxy/task_query.go index a2e4869e93..22f8202953 100644 --- a/internal/proxy/task_query.go +++ b/internal/proxy/task_query.go @@ -126,12 +126,12 @@ func isSupportedGroupByFieldType(dt schemapb.DataType) bool { func validateGroupByFieldSchema(field *schemapb.FieldSchema) error { if field == nil { - return merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg("group by field schema is nil")) + return merr.WrapErrParameterInvalidMsg("group by field schema is nil") } if !isSupportedGroupByFieldType(field.GetDataType()) { - return merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg( + return merr.WrapErrParameterInvalidMsg( "group by field %s has unsupported data type %s", field.GetName(), field.GetDataType().String(), - )) + ) } return nil } @@ -151,7 +151,7 @@ func translateGroupByFieldIds(groupByFieldNames []string, schema *schemapb.Colle groupByField = strings.TrimSpace(groupByField) fieldSchema, found := fieldNameToSchema[groupByField] if !found { - return nil, fmt.Errorf("field %s not exist", groupByField) + return nil, merr.WrapErrParameterInvalidMsg("field %s not exist", groupByField) } if err := validateGroupByFieldSchema(fieldSchema); err != nil { return nil, err @@ -203,19 +203,19 @@ func validateOrderByFieldsWithGroupBy( // Reject aggregate expressions — not yet supported if isAgg, _, _ := agg.MatchAggregationExpression(fieldName); isAgg { - return merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg( + return merr.WrapErrParameterInvalidMsg( "ORDER BY on aggregate expression '%s' is not yet supported", fieldName, - )) + ) } if !validTargets[fieldName] { - return merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg( + return merr.WrapErrParameterInvalidMsg( "ORDER BY field '%s' is not valid: when using GROUP BY or aggregates, "+ "ORDER BY can only reference GROUP BY columns. "+ "Valid targets are: %v", fieldName, getValidTargetList(groupByFields, aggregates), - )) + ) } } @@ -238,7 +238,7 @@ func getValidTargetList(groupByFields []string, _ []agg.AggregateBase) []string func translateOrderByFields(orderByFieldSpecs []string, schema *schemapb.CollectionSchema) ([]*planpb.OrderByField, error) { parsed, err := orderby.ParseOrderByFields(orderByFieldSpecs, schema) if err != nil { - return nil, merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg(err.Error())) + return nil, merr.WrapErrParameterInvalidMsg(err.Error()) } if len(parsed) == 0 { return nil, nil @@ -296,7 +296,7 @@ func translateToOutputFieldIDs(outputFields []string, schema *schemapb.Collectio } if !fieldFound { - return nil, fmt.Errorf("field %s not exist", reqField) + return nil, merr.WrapErrParameterInvalidMsg("field %s not exist", reqField) } } @@ -384,7 +384,7 @@ func parseQueryParams(queryParamsPair []*commonpb.KeyValuePair, largeTopKEnabled isLimitProvided = true limit, err = strconv.ParseInt(limitStr, 0, 64) if err != nil { - return nil, fmt.Errorf("%s [%s] is invalid", LimitKey, limitStr) + return nil, merr.WrapErrParameterInvalidMsg("%s [%s] is invalid", LimitKey, limitStr) } } if isLimitProvided { @@ -393,12 +393,12 @@ func parseQueryParams(queryParamsPair []*commonpb.KeyValuePair, largeTopKEnabled if err == nil { offset, err = strconv.ParseInt(offsetStr, 0, 64) if err != nil { - return nil, fmt.Errorf("%s [%s] is invalid", OffsetKey, offsetStr) + return nil, merr.WrapErrParameterInvalidMsg("%s [%s] is invalid", OffsetKey, offsetStr) } } // validate max result window. if err = validateMaxQueryResultWindow(offset, limit, largeTopKEnabled); err != nil { - return nil, fmt.Errorf("invalid max query result window, %w", err) + return nil, merr.WrapErrParameterInvalidMsg("invalid max query result window, %v", err) } } @@ -466,14 +466,14 @@ func parseQueryIteratorCursor(queryParamsPair []*commonpb.KeyValuePair, isIterat return nil, nil } if !isIterator { - return nil, merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg( + return nil, merr.WrapErrParameterInvalidMsg( "invalid query iterator cursor params: %s and %s can only be used when iterator=true", - QueryIterLastPKKey, QueryIterLastOffsetKey)) + QueryIterLastPKKey, QueryIterLastOffsetKey) } if !hasLastPK || !hasLastOffset { - return nil, merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg( + return nil, merr.WrapErrParameterInvalidMsg( "incomplete query iterator cursor params: %s and %s must be provided together, has_last_pk=%t, has_last_element_offset=%t", - QueryIterLastPKKey, QueryIterLastOffsetKey, hasLastPK, hasLastOffset)) + QueryIterLastPKKey, QueryIterLastOffsetKey, hasLastPK, hasLastOffset) } lastOffset, err := strconv.ParseInt(lastOffsetStr, 0, 64) @@ -496,7 +496,7 @@ func parseQueryIteratorCursor(queryParamsPair []*commonpb.KeyValuePair, isIterat case schemapb.DataType_VarChar: cursor.LastStrPk = &lastPK default: - return nil, merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg("unsupported primary key type %s for query iterator cursor", pkDataType.String())) + return nil, merr.WrapErrParameterInvalidMsg("unsupported primary key type %s for query iterator cursor", pkDataType.String()) } return cursor, nil } @@ -529,7 +529,7 @@ func createCntPlan(expr string, schemaHelper *typeutil.SchemaHelper, exprTemplat plan, err := planparserv2.CreateRetrievePlan(schemaHelper, expr, exprTemplateValues) if err != nil { metrics.ProxyParseExpressionLatency.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), metrics.QueryLabel, metrics.FailLabel).Observe(float64(time.Since(start).Microseconds()) / 1000.0) - return nil, merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg("failed to create query plan: %v", err)) + return nil, merr.WrapErrParameterInvalidMsg("failed to create query plan: %v", err) } metrics.ProxyParseExpressionLatency.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), metrics.QueryLabel, metrics.SuccessLabel).Observe(float64(time.Since(start).Microseconds()) / 1000.0) plan.Node.(*planpb.PlanNode_Query).Query.IsCount = true @@ -549,7 +549,7 @@ func (t *queryTask) createPlanArgs(ctx context.Context, visitorArgs *planparserv t.plan, err = planparserv2.CreateRetrievePlanArgs(schema.schemaHelper, t.request.Expr, t.request.GetExprTemplateValues(), visitorArgs) if err != nil { metrics.ProxyParseExpressionLatency.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), metrics.QueryLabel, metrics.FailLabel).Observe(float64(time.Since(start).Microseconds()) / 1000.0) - return merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg("failed to create query plan: %v", err)) + return merr.WrapErrParameterInvalidMsg("failed to create query plan: %v", err) } metrics.ProxyParseExpressionLatency.WithLabelValues(strconv.FormatInt(paramtable.GetNodeID(), 10), metrics.QueryLabel, metrics.SuccessLabel).Observe(float64(time.Since(start).Microseconds()) / 1000.0) } @@ -599,7 +599,7 @@ func (t *queryTask) createPlanArgs(ctx context.Context, visitorArgs *planparserv t.plan.OutputFieldIds = emptyOutputFields aggFieldMap, err := agg.NewAggregationFieldMap(originalOuputFields, t.queryParams.groupByFields, t.userAggregates) if err != nil { - return merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg(err.Error())) + return merr.WrapErrParameterInvalidMsg(err.Error()) } t.aggregationFieldMap = aggFieldMap } else { @@ -676,7 +676,7 @@ func (t *queryTask) PreExecute(ctx context.Context) error { collID, err := globalMetaCache.GetCollectionID(ctx, t.request.GetDbName(), collectionName) if err != nil { log.Warn("Failed to get collection id.", zap.String("collectionName", collectionName), zap.Error(err)) - return merr.WrapErrAsInputErrorWhen(err, merr.ErrCollectionNotFound, merr.ErrDatabaseNotFound) + return err } t.CollectionID = collID @@ -684,7 +684,11 @@ func (t *queryTask) PreExecute(ctx context.Context) error { if err != nil { log.Warn("Failed to get collection info.", zap.String("collectionName", collectionName), zap.Int64("collectionID", t.CollectionID), zap.Error(err)) - return merr.WrapErrAsInputErrorWhen(err, merr.ErrCollectionNotFound, merr.ErrDatabaseNotFound) + // The name was already resolved above (GetCollectionID succeeded), so a + // not-found here means the collection was concurrently dropped between the + // two lookups — a TOCTOU race, not the caller's input error. Leave it as the + // default SystemError; do not stamp InputError. + return err } log.Debug("Get collection ID by name", zap.Int64("collectionID", t.CollectionID)) @@ -708,11 +712,11 @@ func (t *queryTask) PreExecute(ctx context.Context) error { return err } if t.partitionKeyMode && len(t.request.GetPartitionNames()) != 0 { - return merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg("not support manually specifying the partition names if partition key mode is used")) + return merr.WrapErrParameterInvalidMsg("not support manually specifying the partition names if partition key mode is used") } if t.mustUsePartitionKey && !t.partitionKeyMode { - return merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg("must use partition key in the query request " + - "because the mustUsePartitionKey config is true")) + return merr.WrapErrParameterInvalidMsg("must use partition key in the query request " + + "because the mustUsePartitionKey config is true") } for _, tag := range t.request.PartitionNames { @@ -732,8 +736,8 @@ func (t *queryTask) PreExecute(ctx context.Context) error { return err } if queryParams.collectionID > 0 && queryParams.collectionID != t.GetCollectionID() { - return merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg("Input collection id is not consistent to collectionID in the context," + - "alias or database may have changed")) + return merr.WrapErrParameterInvalidMsg("Input collection id is not consistent to collectionID in the context," + + "alias or database may have changed") } if queryParams.reduceType == reduce.IReduceInOrderForBest { t.ReduceStopForBest = true @@ -746,7 +750,7 @@ func (t *queryTask) PreExecute(ctx context.Context) error { // SortBuffer loads all matching rows into memory for sorting; // MaxOutputSize only guards proxy reduce, not segment sorting. if len(queryParams.orderByFields) > 0 && queryParams.limit == typeutil.Unlimited { - return merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg("ORDER BY requires explicit limit")) + return merr.WrapErrParameterInvalidMsg("ORDER BY requires explicit limit") } // ORDER BY with iterator is not yet supported. Current iterator relies on @@ -754,7 +758,7 @@ func (t *queryTask) PreExecute(ctx context.Context) error { // different pipeline that sorts by arbitrary fields. Future work will // enable iterator with user-specified ORDER BY fields. if len(queryParams.orderByFields) > 0 && queryParams.isIterator { - return merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg("ORDER BY with iterator is not supported")) + return merr.WrapErrParameterInvalidMsg("ORDER BY with iterator is not supported") } t.Limit = queryParams.limit + queryParams.offset @@ -793,7 +797,7 @@ func (t *queryTask) PreExecute(ctx context.Context) error { hasAgg := len(t.userAggregates) > 0 if planparserv2.IsAlwaysTruePlan(t.plan) && t.Limit == typeutil.Unlimited && !hasAgg { - return merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg("empty expression should be used with limit")) + return merr.WrapErrParameterInvalidMsg("empty expression should be used with limit") } // convert partition names only when requery is false @@ -821,7 +825,7 @@ func (t *queryTask) PreExecute(ctx context.Context) error { // count(*) without GROUP BY is a single-value result, pagination is meaningless. // But count(*) with GROUP BY + limit is valid (limits the number of groups returned). if t.hasCountStar() && t.queryParams.limit != typeutil.Unlimited && len(t.GetGroupByFieldIds()) == 0 { - return merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg("count entities with pagination is not allowed")) + return merr.WrapErrParameterInvalidMsg("count entities with pagination is not allowed") } t.plan.Namespace = t.request.Namespace @@ -888,7 +892,7 @@ func (t *queryTask) PreExecute(ctx context.Context) error { t.CollectionTtlTimestamps = tsoutil.ComposeTSByTime(expireTime, 0) // preventing overflow, abort if t.CollectionTtlTimestamps > t.GetBase().GetTimestamp() { - return merr.WrapErrServiceInternal(fmt.Sprintf("ttl timestamp overflow, base timestamp: %d, ttl duration %v", t.GetBase().GetTimestamp(), collectionInfo.collectionTTL)) + return merr.WrapErrServiceInternalMsg("ttl timestamp overflow, base timestamp: %d, ttl duration %v", t.GetBase().GetTimestamp(), collectionInfo.collectionTTL) } } deadline, ok := t.TraceCtx().Deadline() @@ -949,7 +953,7 @@ func (t *queryTask) PostExecute(ctx context.Context) error { select { case <-t.TraceCtx().Done(): log.Warn("proxy", zap.Int64("Query: wait to finish failed, timeout!, msgID:", t.ID())) - return nil + return merr.Wrapf(t.TraceCtx().Err(), "Query wait to finish timeout, msgID=%d", t.ID()) default: log.Debug("all queries are finished or canceled") t.resultBuf.Range(func(res *internalpb.RetrieveResults) bool { @@ -1135,13 +1139,15 @@ func reduceRetrieveResults(ctx context.Context, retrieveResults []*internalpb.Re if r == nil || len(r.GetFieldsData()) == 0 || size == 0 { continue } - // Validate element-level consistency: if any result is element-level, all must be + // Validate element-level consistency: if any result is element-level, all + // must be. The flag and element_indices come from querynode results, never + // from the request, so a mismatch is an internal contract violation. if isElementLevel && !r.GetElementLevel() { - return nil, fmt.Errorf("inconsistent element-level flag: expected all results to be element-level") + return nil, merr.WrapErrServiceInternalMsg("inconsistent element-level flag: expected all results to be element-level") } // Validate element_indices length matches ids length for element-level if isElementLevel && len(r.GetElementIndices()) != size { - return nil, fmt.Errorf("element_indices length (%d) does not match ids length (%d)", len(r.GetElementIndices()), size) + return nil, merr.WrapErrServiceInternalMsg("element_indices length (%d) does not match ids length (%d)", len(r.GetElementIndices()), size) } validRetrieveResults = append(validRetrieveResults, r) loopEnd += size @@ -1219,7 +1225,7 @@ func reduceRetrieveResults(ctx context.Context, retrieveResults []*internalpb.Re // limit retrieve result to avoid oom if retSize > maxOutputSize { - return nil, fmt.Errorf("query results exceed the maxOutputSize Limit %d", maxOutputSize) + return nil, merr.WrapErrParameterInvalidMsg("query results exceed the maxOutputSize Limit %d", maxOutputSize) } cursors[sel]++ diff --git a/internal/proxy/task_search.go b/internal/proxy/task_search.go index 50ff9671bf..6b0866e5a4 100644 --- a/internal/proxy/task_search.go +++ b/internal/proxy/task_search.go @@ -167,7 +167,7 @@ func (t *searchTask) PreExecute(ctx context.Context) error { t.collectionName = collectionName collID, err := globalMetaCache.GetCollectionID(ctx, t.request.GetDbName(), collectionName) if err != nil { // err is not nil if collection not exists - return merr.WrapErrAsInputErrorWhen(err, merr.ErrCollectionNotFound, merr.ErrDatabaseNotFound) + return err } t.DbID = 0 // todo @@ -196,7 +196,7 @@ func (t *searchTask) PreExecute(ctx context.Context) error { return err } if t.partitionKeyMode && len(t.request.GetPartitionNames()) != 0 { - return errors.New("not support manually specifying the partition names if partition key mode is used") + return merr.WrapErrParameterInvalidMsg("not support manually specifying the partition names if partition key mode is used") } if t.mustUsePartitionKey && !t.partitionKeyMode { return merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg("must use partition key in the search request " + @@ -224,14 +224,14 @@ func (t *searchTask) PreExecute(ctx context.Context) error { } if len(aggs) > 0 { log.Warn("aggregates are not supported in search request", zap.Strings("aggregates", lo.Map(aggs, func(agg agg.AggregateBase, _ int) string { return agg.OriginalName() }))) - return errors.New("aggregates are not supported in search request") + return merr.WrapErrParameterInvalidMsg("aggregates are not supported in search request") } log.Debug("translate output fields", zap.Strings("output fields", t.translatedOutputFields)) if t.GetIsAdvanced() { if len(t.request.GetSubReqs()) > defaultMaxSearchRequest { - return errors.New(fmt.Sprintf("maximum of ann search requests is %d", defaultMaxSearchRequest)) + return merr.WrapErrParameterInvalidMsg("maximum of ann search requests is %d", defaultMaxSearchRequest) } } @@ -330,7 +330,7 @@ func (t *searchTask) PreExecute(ctx context.Context) error { t.CollectionTtlTimestamps = tsoutil.ComposeTSByTime(expireTime, 0) // preventing overflow, abort if t.CollectionTtlTimestamps > t.GetBase().GetTimestamp() { - return merr.WrapErrServiceInternal(fmt.Sprintf("ttl timestamp overflow, base timestamp: %d, ttl duration %v", t.GetBase().GetTimestamp(), collectionInfo.collectionTTL)) + return merr.WrapErrServiceInternalMsg("ttl timestamp overflow, base timestamp: %d, ttl duration %v", t.GetBase().GetTimestamp(), collectionInfo.collectionTTL) } } @@ -396,7 +396,7 @@ func (t *searchTask) checkNq(ctx context.Context) (int64, error) { // Check if nq is valid: // https://milvus.io/docs/limitations.md if err := validateNQLimit(nq); err != nil { - return 0, fmt.Errorf("%s [%d] is invalid, %w", NQKey, nq, err) + return 0, merr.WrapErrParameterInvalidMsg("%s [%d] is invalid, %v", NQKey, nq, err) } return nq, nil } @@ -478,7 +478,7 @@ func setQueryInfoIfMvEnable(queryInfo *planpb.QueryInfo, t *searchTask, plan *pl } queryInfo.MaterializedViewInvolved = true } else { - return errors.New("partition key field data type is not supported in materialized view") + return merr.WrapErrParameterInvalidMsg("partition key field data type is not supported in materialized view") } } return nil @@ -744,7 +744,7 @@ func (t *searchTask) fillResult() { func (t *searchTask) getBM25SearchTexts(placeholder []byte) ([]string, error) { pb := &commonpb.PlaceholderGroup{} if err := proto.Unmarshal(placeholder, pb); err != nil { - return nil, merr.WrapErrServiceInternal("failed to unmarshal BM25 search placeholder group", err.Error()) + return nil, merr.WrapErrParameterInvalidMsg("failed to unmarshal BM25 search placeholder group: %v", err) } if len(pb.Placeholders) != 1 || len(pb.Placeholders[0].Values) == 0 { @@ -753,7 +753,7 @@ func (t *searchTask) getBM25SearchTexts(placeholder []byte) ([]string, error) { holder := pb.Placeholders[0] if holder.Type != commonpb.PlaceholderType_VarChar { - return nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("please provide varchar/text for BM25 Function based search, got %s", holder.Type.String())) + return nil, merr.WrapErrParameterInvalidMsg("please provide varchar/text for BM25 Function based search, got %s", holder.Type.String()) } texts := funcutil.GetVarCharFromPlaceholder(holder) @@ -1050,11 +1050,11 @@ func (t *searchTask) tryGeneratePlan(params []*commonpb.KeyValuePair, dsl string if err != nil || len(annsFieldName) == 0 { vecFields := typeutil.GetVectorFieldSchemas(t.schema.CollectionSchema) if len(vecFields) == 0 { - return nil, nil, 0, false, nil, internalpb.SearchType_DEFAULT, errors.New(AnnsFieldKey + " not found in schema") + return nil, nil, 0, false, nil, internalpb.SearchType_DEFAULT, merr.WrapErrParameterInvalidMsg(AnnsFieldKey + " not found in schema") } if enableMultipleVectorFields && len(vecFields) > 1 { - return nil, nil, 0, false, nil, internalpb.SearchType_DEFAULT, errors.New("multiple anns_fields exist, please specify a anns_field in search_params") + return nil, nil, 0, false, nil, internalpb.SearchType_DEFAULT, merr.WrapErrParameterInvalidMsg("multiple anns_fields exist, please specify a anns_field in search_params") } annsFieldName = vecFields[0].Name } @@ -1069,7 +1069,7 @@ func (t *searchTask) tryGeneratePlan(params []*commonpb.KeyValuePair, dsl string annField := typeutil.GetFieldByName(t.schema.CollectionSchema, annsFieldName) if searchInfo.planInfo.GetGroupByFieldId() != -1 && annField.GetDataType() == schemapb.DataType_BinaryVector { - return nil, nil, 0, false, nil, internalpb.SearchType_DEFAULT, errors.New("not support search_group_by operation based on binary vector column") + return nil, nil, 0, false, nil, internalpb.SearchType_DEFAULT, merr.WrapErrParameterInvalidMsg("not support search_group_by operation based on binary vector column") } searchInfo.planInfo.QueryFieldId = annField.GetFieldID() @@ -1440,7 +1440,7 @@ func (t *searchTask) collectSearchResults(ctx context.Context) ([]*internalpb.Se select { case <-t.TraceCtx().Done(): log.Ctx(ctx).Warn("search task wait to finish timeout!") - return nil, fmt.Errorf("search task wait to finish timeout, msgID=%d", t.ID()) + return nil, merr.Wrapf(t.TraceCtx().Err(), "search task wait to finish timeout, msgID=%d", t.ID()) default: toReduceResults := make([]*internalpb.SearchResults, 0) log.Ctx(ctx).Debug("all searches are finished or canceled") diff --git a/internal/proxy/task_search_test.go b/internal/proxy/task_search_test.go index 50543b802a..12f47e08c8 100644 --- a/internal/proxy/task_search_test.go +++ b/internal/proxy/task_search_test.go @@ -3292,7 +3292,7 @@ func TestSearchTask_ErrExecute(t *testing.T) { if enableMultipleVectorFields { err = task.PreExecute(ctx) assert.Error(t, err) - assert.Equal(t, err.Error(), "multiple anns_fields exist, please specify a anns_field in search_params") + assert.Contains(t, err.Error(), "multiple anns_fields exist, please specify a anns_field in search_params") } else { assert.NoError(t, task.PreExecute(ctx)) } diff --git a/internal/proxy/task_snapshot.go b/internal/proxy/task_snapshot.go index 3328374ef1..0f88ff66ee 100644 --- a/internal/proxy/task_snapshot.go +++ b/internal/proxy/task_snapshot.go @@ -220,7 +220,7 @@ func (dst *dropSnapshotTask) PreExecute(ctx context.Context) error { } if dst.req.GetCollectionName() == "" { - return merr.WrapErrParameterInvalidMsg("collection_name is required for drop snapshot") + return merr.WrapErrParameterMissingMsg("collection_name is required for drop snapshot") } collectionID, err := globalMetaCache.GetCollectionID(ctx, dst.req.GetDbName(), dst.req.GetCollectionName()) if err != nil { @@ -315,7 +315,7 @@ func (dst *describeSnapshotTask) PreExecute(ctx context.Context) error { } if dst.req.GetCollectionName() == "" { - return merr.WrapErrParameterInvalidMsg("collection_name is required for describe snapshot") + return merr.WrapErrParameterMissingMsg("collection_name is required for describe snapshot") } collectionID, err := globalMetaCache.GetCollectionID(ctx, dst.req.GetDbName(), dst.req.GetCollectionName()) if err != nil { @@ -446,7 +446,7 @@ func (lst *listSnapshotsTask) PreExecute(ctx context.Context) error { } if lst.req.GetCollectionName() == "" { - return merr.WrapErrParameterInvalidMsg("collection_name is required for ListSnapshots") + return merr.WrapErrParameterMissingMsg("collection_name is required for ListSnapshots") } collectionID, err := globalMetaCache.GetCollectionID(ctx, lst.req.GetDbName(), lst.req.GetCollectionName()) @@ -550,12 +550,12 @@ func (rst *restoreSnapshotTask) PreExecute(ctx context.Context) error { // Validate source collection name if rst.req.GetCollectionName() == "" { - return merr.WrapErrParameterInvalidMsg("collection_name is required for restore snapshot") + return merr.WrapErrParameterMissingMsg("collection_name is required for restore snapshot") } // Validate target collection name (required, cheap checks before RPC) if rst.req.GetTargetCollectionName() == "" { - return merr.WrapErrParameterInvalidMsg("target_collection_name is required for restore snapshot") + return merr.WrapErrParameterMissingMsg("target_collection_name is required for restore snapshot") } if err := ValidateCollectionName(rst.req.GetTargetCollectionName()); err != nil { return err @@ -896,7 +896,7 @@ func (pst *pinSnapshotDataTask) PreExecute(ctx context.Context) error { } if pst.req.GetCollectionName() == "" { - return merr.WrapErrParameterInvalidMsg("collection_name is required for pin snapshot data") + return merr.WrapErrParameterMissingMsg("collection_name is required for pin snapshot data") } if pst.req.GetTtlSeconds() < 0 { @@ -1001,7 +1001,7 @@ func (ust *unpinSnapshotDataTask) OnEnqueue() error { func (ust *unpinSnapshotDataTask) PreExecute(ctx context.Context) error { if ust.req.GetPinId() == 0 { - return merr.WrapErrParameterInvalidMsg("pin_id is required for unpin snapshot data") + return merr.WrapErrParameterMissingMsg("pin_id is required for unpin snapshot data") } return nil } diff --git a/internal/proxy/task_statistic.go b/internal/proxy/task_statistic.go index fc0e226fa5..0310a0d5f8 100644 --- a/internal/proxy/task_statistic.go +++ b/internal/proxy/task_statistic.go @@ -196,7 +196,7 @@ func (g *getStatisticsTask) PostExecute(ctx context.Context) error { select { case <-g.TraceCtx().Done(): log.Ctx(ctx).Debug("wait to finish timeout!") - return nil + return merr.Wrapf(g.TraceCtx().Err(), "GetStatistics wait to finish timeout, msgID=%d", g.ID()) default: log.Ctx(ctx).Debug("all get statistics are finished or canceled") g.resultBuf.Range(func(res *internalpb.GetStatisticsResponse) bool { @@ -318,11 +318,11 @@ func checkFullLoaded(ctx context.Context, qc types.QueryCoordClient, dbName stri // TODO: Consider to check if partition loaded from cache to save rpc. info, err := globalMetaCache.GetCollectionInfo(ctx, dbName, collectionName, collectionID) if err != nil { - return nil, nil, fmt.Errorf("GetCollectionInfo failed, dbName = %s, collectionName = %s,collectionID = %d, err = %s", dbName, collectionName, collectionID, err) + return nil, nil, merr.Wrapf(err, "GetCollectionInfo failed, dbName = %s, collectionName = %s, collectionID = %d", dbName, collectionName, collectionID) } partitionInfos, err := globalMetaCache.GetPartitions(ctx, dbName, collectionName) if err != nil { - return nil, nil, fmt.Errorf("GetPartitions failed, dbName = %s, collectionName = %s,collectionID = %d, err = %s", dbName, collectionName, collectionID, err) + return nil, nil, merr.Wrapf(err, "GetPartitions failed, dbName = %s, collectionName = %s, collectionID = %d", dbName, collectionName, collectionID) } // If request to search partitions @@ -336,10 +336,10 @@ func checkFullLoaded(ctx context.Context, qc types.QueryCoordClient, dbName stri PartitionIDs: searchPartitionIDs, }) if err != nil { - return nil, nil, fmt.Errorf("showPartitions failed, collection = %d, partitionIDs = %v, err = %s", collectionID, searchPartitionIDs, err) + return nil, nil, merr.Wrapf(err, "showPartitions failed, collection = %d, partitionIDs = %v", collectionID, searchPartitionIDs) } if resp.GetStatus().GetErrorCode() != commonpb.ErrorCode_Success { - return nil, nil, fmt.Errorf("showPartitions failed, collection = %d, partitionIDs = %v, reason = %s", collectionID, searchPartitionIDs, resp.GetStatus().GetReason()) + return nil, nil, merr.Wrapf(merr.Error(resp.GetStatus()), "showPartitions failed, collection = %d, partitionIDs = %v", collectionID, searchPartitionIDs) } for i, percentage := range resp.GetInMemoryPercentages() { @@ -361,10 +361,10 @@ func checkFullLoaded(ctx context.Context, qc types.QueryCoordClient, dbName stri CollectionID: info.collID, }) if err != nil { - return nil, nil, fmt.Errorf("showPartitions failed, collection = %d, partitionIDs = %v, err = %s", collectionID, searchPartitionIDs, err) + return nil, nil, merr.Wrapf(err, "showPartitions failed, collection = %d, partitionIDs = %v", collectionID, searchPartitionIDs) } if resp.GetStatus().GetErrorCode() != commonpb.ErrorCode_Success { - return nil, nil, fmt.Errorf("showPartitions failed, collection = %d, partitionIDs = %v, reason = %s", collectionID, searchPartitionIDs, resp.GetStatus().GetReason()) + return nil, nil, merr.Wrapf(merr.Error(resp.GetStatus()), "showPartitions failed, collection = %d, partitionIDs = %v", collectionID, searchPartitionIDs) } loadedMap := make(map[UniqueID]bool) @@ -389,7 +389,7 @@ func decodeGetStatisticsResults(results []*internalpb.GetStatisticsResponse) ([] ret := make([]map[string]string, len(results)) for i, result := range results { if result.GetStatus().GetErrorCode() != commonpb.ErrorCode_Success { - return nil, fmt.Errorf("fail to decode result, reason=%s", result.GetStatus().GetReason()) + return nil, merr.Wrap(merr.Error(result.GetStatus()), "fail to decode statistics result") } ret[i] = funcutil.KeyValuePair2Map(result.GetStats()) } @@ -747,7 +747,7 @@ func (g *getPartitionStatisticsTask) Execute(ctx context.Context) error { result, _ := g.mixCoord.GetPartitionStatistics(ctx, req) if result == nil { - return errors.New("get partition statistics resp is nil") + return merr.WrapErrServiceInternalMsg("get partition statistics resp is nil") } if result.GetStatus().GetErrorCode() != commonpb.ErrorCode_Success { return merr.Error(result.GetStatus()) diff --git a/internal/proxy/task_test.go b/internal/proxy/task_test.go index b75b41830a..d17fcc9a2e 100644 --- a/internal/proxy/task_test.go +++ b/internal/proxy/task_test.go @@ -1519,7 +1519,7 @@ func TestCreateCollectionTask(t *testing.T) { Params.Save(Params.ProxyCfg.MustUsePartitionKey.Key, "true") err = task.PreExecute(ctx) assert.Error(t, err) - assert.ErrorIs(t, err, merr.ErrParameterInvalid) + assert.ErrorIs(t, err, merr.ErrParameterMissing) Params.Reset(Params.ProxyCfg.MustUsePartitionKey.Key) task.Schema = []byte{0x1, 0x2, 0x3, 0x4} @@ -7585,7 +7585,7 @@ func TestAlterCollectionSchemaTask(t *testing.T) { task := buildTask(req, oldSchema) err := task.PreExecute(ctx) assert.Error(t, err) - assert.ErrorIs(t, err, merr.ErrParameterInvalid) + assert.ErrorIs(t, err, merr.ErrParameterMissing) }) t.Run("PreExecute multiple fieldInfos", func(t *testing.T) { diff --git a/internal/proxy/task_upsert.go b/internal/proxy/task_upsert.go index c2a3f099df..b6ff4c9aaf 100644 --- a/internal/proxy/task_upsert.go +++ b/internal/proxy/task_upsert.go @@ -20,7 +20,6 @@ import ( "fmt" "strconv" - "github.com/cockroachdb/errors" "github.com/samber/lo" "go.opentelemetry.io/otel" "go.uber.org/zap" @@ -249,7 +248,7 @@ func (it *upsertTask) queryPreExecute(ctx context.Context) error { primaryFieldData, err := typeutil.GetPrimaryFieldData(it.req.GetFieldsData(), primaryFieldSchema) if err != nil { log.Error("get primary field data failed", zap.Error(err)) - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("must assign pk when upsert, primary field: %v", primaryFieldSchema.Name)) + return merr.WrapErrParameterInvalidMsg("must assign pk when upsert, primary field: %v", primaryFieldSchema.Name) } upsertIDs, err := parsePrimaryFieldData2IDs(primaryFieldData) @@ -398,7 +397,7 @@ func (it *upsertTask) queryPreExecute(ctx context.Context) error { oldPK := typeutil.GetPK(upsertIDs, int64(upsertIdx)) idx, ok := existPKToIndex[oldPK] if !ok { - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("upsert pk %v not found in query result", oldPK)) + return merr.WrapErrParameterInvalidMsg("upsert pk %v not found in query result", oldPK) } existIndices[i] = int64(idx) } @@ -736,7 +735,7 @@ func ToCompressedFormatNullable(field *schemapb.FieldData) error { } default: - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("undefined data type:%s", field.Type.String())) + return merr.WrapErrParameterInvalidMsg("undefined data type:%s", field.Type.String()) } case *schemapb.FieldData_Vectors: @@ -744,7 +743,7 @@ func ToCompressedFormatNullable(field *schemapb.FieldData) error { return nil default: - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("undefined data type:%s", field.Type.String())) + return merr.WrapErrParameterInvalidMsg("undefined data type:%s", field.Type.String()) } return nil @@ -1042,7 +1041,7 @@ func GenNullableFieldData(field *schemapb.FieldSchema, upsertIDSize int) (*schem }, nil default: - return nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("undefined data type:%s", field.DataType.String())) + return nil, merr.WrapErrParameterInvalidMsg("undefined data type:%s", field.DataType.String()) } } @@ -1324,7 +1323,7 @@ func (it *upsertTask) PreExecute(ctx context.Context) error { } if it.partitionKeyMode { if len(it.req.GetPartitionName()) > 0 { - return errors.New("not support manually specifying the partition names if partition key mode is used") + return merr.WrapErrParameterInvalidMsg("not support manually specifying the partition names if partition key mode is used") } } else { // set default partition name if not use partition key diff --git a/internal/proxy/task_upsert_partial_op.go b/internal/proxy/task_upsert_partial_op.go index 7dc44263a0..c727e267fa 100644 --- a/internal/proxy/task_upsert_partial_op.go +++ b/internal/proxy/task_upsert_partial_op.go @@ -78,7 +78,7 @@ func validateFieldPartialUpdateOps(req *milvuspb.UpsertRequest, schema *schemapb for _, opMsg := range fieldOps { name := opMsg.GetFieldName() if name == "" { - return false, merr.WrapErrParameterInvalidMsg("FieldPartialUpdateOp.field_name is required") + return false, merr.WrapErrParameterMissingMsg("FieldPartialUpdateOp.field_name is required") } if _, dup := seenOpFields[name]; dup { return false, merr.WrapErrParameterInvalidMsg( @@ -166,7 +166,7 @@ func findFieldSchemaByName(schema *schemapb.CollectionSchema, name string) (*sch return f, nil } } - return nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("field %q not found in collection schema", name)) + return nil, merr.WrapErrParameterInvalidMsg("field %q not found in collection schema", name) } // checkArrayAppendPayloadWithinCapacity checks that the per-row payload diff --git a/internal/proxy/task_validator.go b/internal/proxy/task_validator.go index e037a20e51..f7bc533bfb 100644 --- a/internal/proxy/task_validator.go +++ b/internal/proxy/task_validator.go @@ -1,9 +1,8 @@ package proxy import ( - "fmt" - "github.com/milvus-io/milvus/pkg/v3/proto/internalpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -29,7 +28,7 @@ func (v *searchTaskValidator) validateSubSearch(subReq *internalpb.SubSearchRequ nEntries *= subReq.GroupSize } if nEntries > maxResultEntries { - return fmt.Errorf("number of result entries is too large") + return merr.WrapErrParameterInvalidMsg("number of result entries is too large") } return nil } @@ -46,7 +45,7 @@ func (v *searchTaskValidator) validateSearch(search *searchTask) error { nEntries *= search.GroupSize } if nEntries > maxResultEntries { - return fmt.Errorf("number of result entries is too large") + return merr.WrapErrParameterInvalidMsg("number of result entries is too large") } return nil } diff --git a/internal/proxy/timestamp.go b/internal/proxy/timestamp.go index 57e4cf863d..6d204c7bae 100644 --- a/internal/proxy/timestamp.go +++ b/internal/proxy/timestamp.go @@ -18,16 +18,14 @@ package proxy import ( "context" - "fmt" "strconv" "time" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus/pkg/v3/metrics" "github.com/milvus-io/milvus/pkg/v3/proto/rootcoordpb" "github.com/milvus-io/milvus/pkg/v3/util/commonpbutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/timerecord" ) @@ -65,13 +63,13 @@ func (ta *timestampAllocator) alloc(ctx context.Context, count uint32) ([]Timest }() if err != nil { - return nil, fmt.Errorf("syncTimestamp Failed:%w", err) + return nil, merr.Wrap(err, "syncTimestamp Failed") } if resp.GetStatus().GetErrorCode() != commonpb.ErrorCode_Success { - return nil, fmt.Errorf("syncTimeStamp Failed:%s", resp.GetStatus().GetReason()) + return nil, merr.Error(resp.GetStatus()) } if resp == nil { - return nil, errors.New("empty AllocTimestampResponse") + return nil, merr.WrapErrServiceInternalMsg("empty AllocTimestampResponse") } start, cnt := resp.GetTimestamp(), resp.GetCount() ret := make([]Timestamp, cnt) diff --git a/internal/proxy/util.go b/internal/proxy/util.go index e26c4f5ec5..ab77394ab6 100644 --- a/internal/proxy/util.go +++ b/internal/proxy/util.go @@ -49,6 +49,7 @@ import ( typeutil2 "github.com/milvus-io/milvus/internal/util/typeutil" "github.com/milvus-io/milvus/pkg/v3/common" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/metrics" "github.com/milvus-io/milvus/pkg/v3/mq/msgstream" "github.com/milvus-io/milvus/pkg/v3/proto/internalpb" "github.com/milvus-io/milvus/pkg/v3/proto/planpb" @@ -123,20 +124,20 @@ func restoreStructFieldNames(schema *schemapb.CollectionSchema) error { func extractOriginalFieldName(transformedName string) (string, error) { idx := strings.Index(transformedName, "[") if idx == -1 { - return "", fmt.Errorf("not a transformed struct field name: %s", transformedName) + return "", merr.WrapErrParameterInvalidMsg("not a transformed struct field name: %s", transformedName) } if !strings.HasSuffix(transformedName, "]") { - return "", fmt.Errorf("invalid struct field format: %s, missing closing bracket", transformedName) + return "", merr.WrapErrParameterInvalidMsg("invalid struct field format: %s, missing closing bracket", transformedName) } if idx == 0 { - return "", fmt.Errorf("invalid struct field format: %s, missing struct name", transformedName) + return "", merr.WrapErrParameterInvalidMsg("invalid struct field format: %s, missing struct name", transformedName) } fieldName := transformedName[idx+1 : len(transformedName)-1] if fieldName == "" { - return "", fmt.Errorf("invalid struct field format: %s, empty field name", transformedName) + return "", merr.WrapErrParameterInvalidMsg("invalid struct field format: %s, empty field name", transformedName) } return fieldName, nil @@ -161,16 +162,16 @@ func isNumber(c uint8) bool { // check run analyzer params when collection name was set func validateRunAnalyzer(req *milvuspb.RunAnalyzerRequest) error { if req.GetAnalyzerParams() != "" { - return fmt.Errorf("run analyzer can't use analyzer params and (collection,field) in same time") + return merr.WrapErrParameterInvalidMsg("run analyzer can't use analyzer params and (collection,field) in same time") } if req.GetFieldName() == "" { - return fmt.Errorf("must set field name when collection name was set") + return merr.WrapErrParameterInvalidMsg("must set field name when collection name was set") } if req.GetAnalyzerNames() != nil { if len(req.GetAnalyzerNames()) != 1 && len(req.GetAnalyzerNames()) != len(req.GetPlaceholder()) { - return fmt.Errorf("only support set one analyzer name for all text or set analyzer name for each text, but now analzer name num: %d, text num: %d", + return merr.WrapErrParameterInvalidMsg("only support set one analyzer name for all text or set analyzer name for each text, but now analzer name num: %d, text num: %d", len(req.GetAnalyzerNames()), len(req.GetPlaceholder())) } } @@ -180,10 +181,10 @@ func validateRunAnalyzer(req *milvuspb.RunAnalyzerRequest) error { func validateMaxQueryResultWindow(offset int64, limit int64, largeTopKEnabled bool) error { if offset < 0 { - return fmt.Errorf("%s [%d] is invalid, should be gte than 0", OffsetKey, offset) + return merr.WrapErrParameterInvalidMsg("%s [%d] is invalid, should be gte than 0", OffsetKey, offset) } if limit <= 0 { - return fmt.Errorf("%s [%d] is invalid, should be greater than 0", LimitKey, limit) + return merr.WrapErrParameterInvalidMsg("%s [%d] is invalid, should be greater than 0", LimitKey, limit) } depth := offset + limit @@ -192,7 +193,7 @@ func validateMaxQueryResultWindow(offset int64, limit int64, largeTopKEnabled bo maxQueryResultWindow = Params.QuotaConfig.LargeMaxQueryResultWindow.GetAsInt64() } if depth <= 0 || depth > maxQueryResultWindow { - return fmt.Errorf("(offset+limit) should be in range [1, %d], but got %d", maxQueryResultWindow, depth) + return merr.WrapErrParameterInvalidMsg("(offset+limit) should be in range [1, %d], but got %d", maxQueryResultWindow, depth) } return nil } @@ -203,7 +204,7 @@ func validateLimit(limit int64, largeTopKEnabled bool) error { topKLimit = Params.QuotaConfig.LargeTopKLimit.GetAsInt64() } if limit <= 0 || limit > topKLimit { - return fmt.Errorf("it should be in range [1, %d], but got %d", topKLimit, limit) + return merr.WrapErrParameterInvalidMsg("it should be in range [1, %d], but got %d", topKLimit, limit) } return nil } @@ -211,7 +212,7 @@ func validateLimit(limit int64, largeTopKEnabled bool) error { func validateNQLimit(limit int64) error { nqLimit := Params.QuotaConfig.NQLimit.GetAsInt64() if limit <= 0 || limit > nqLimit { - return fmt.Errorf("nq (number of search vector per search request) should be in range [1, %d], but got %d", nqLimit, limit) + return merr.WrapErrParameterInvalidMsg("nq (number of search vector per search request) should be in range [1, %d], but got %d", nqLimit, limit) } return nil } @@ -269,7 +270,7 @@ func ValidatePrivilegeGroupName(groupName string) error { func ValidateResourceGroupName(entity string) error { if entity == "" { - return errors.New("resource group name couldn't be empty") + return merr.WrapErrParameterMissingMsg("resource group name couldn't be empty") } invalidMsg := fmt.Sprintf("Invalid resource group name %s.", entity) @@ -342,18 +343,18 @@ func validatePartitionTag(partitionTag string, strictCheck bool) error { invalidMsg := "Invalid partition name: " + partitionTag + ". " if partitionTag == "" { msg := invalidMsg + "Partition name should not be empty." - return errors.New(msg) + return merr.WrapErrParameterInvalidMsg("%s", msg) } if len(partitionTag) > Params.ProxyCfg.MaxNameLength.GetAsInt() { msg := invalidMsg + "The length of a partition name must be less than " + Params.ProxyCfg.MaxNameLength.GetValue() + " characters." - return errors.New(msg) + return merr.WrapErrParameterInvalidMsg("%s", msg) } if strictCheck { firstChar := partitionTag[0] if firstChar != '_' && !isAlpha(firstChar) && !isNumber(firstChar) { msg := invalidMsg + "The first character of a partition name must be an underscore or letter." - return errors.New(msg) + return merr.WrapErrParameterInvalidMsg("%s", msg) } tagSize := len(partitionTag) @@ -361,7 +362,7 @@ func validatePartitionTag(partitionTag string, strictCheck bool) error { c := partitionTag[i] if c != '_' && !isAlpha(c) && !isNumber(c) && c != '-' { msg := invalidMsg + "Partition name can only contain numbers, letters and underscores." - return errors.New(msg) + return merr.WrapErrParameterInvalidMsg("%s", msg) } } } @@ -420,16 +421,16 @@ func validateDimension(field *schemapb.FieldSchema) error { // for sparse vector field, dim should not be specified if typeutil.IsSparseFloatVectorType(field.DataType) { if exist { - return fmt.Errorf("dim should not be specified for sparse vector field %s(%d)", field.GetName(), field.FieldID) + return merr.WrapErrParameterInvalidMsg("dim should not be specified for sparse vector field %s(%d)", field.GetName(), field.FieldID) } return nil } if !exist { - return errors.Newf("dimension is not defined in field type params of field %s, check type param `dim` for vector field", field.GetName()) + return merr.WrapErrParameterInvalidMsg("dimension is not defined in field type params of field %s, check type param `dim` for vector field", field.GetName()) } if dim <= 1 { - return fmt.Errorf("invalid dimension: %d. should be in range 2 ~ %d", dim, Params.ProxyCfg.MaxDimension.GetAsInt()) + return merr.WrapErrParameterInvalidMsg("invalid dimension: %d. should be in range 2 ~ %d", dim, Params.ProxyCfg.MaxDimension.GetAsInt()) } // for dense vector field, dim will be limited by max_dimension @@ -437,14 +438,14 @@ func validateDimension(field *schemapb.FieldSchema) error { (field.GetDataType() == schemapb.DataType_ArrayOfVector && typeutil.IsBinaryVectorType(field.GetElementType())) if isBinaryDimension { if dim%8 != 0 { - return fmt.Errorf("invalid dimension: %d of field %s. binary vector dimension should be multiple of 8. ", dim, field.GetName()) + return merr.WrapErrParameterInvalidMsg("invalid dimension: %d of field %s. binary vector dimension should be multiple of 8. ", dim, field.GetName()) } if dim > Params.ProxyCfg.MaxDimension.GetAsInt64()*8 { - return fmt.Errorf("invalid dimension: %d of field %s. binary vector dimension should be in range 2 ~ %d", dim, field.GetName(), Params.ProxyCfg.MaxDimension.GetAsInt()*8) + return merr.WrapErrParameterInvalidMsg("invalid dimension: %d of field %s. binary vector dimension should be in range 2 ~ %d", dim, field.GetName(), Params.ProxyCfg.MaxDimension.GetAsInt()*8) } } else { if dim > Params.ProxyCfg.MaxDimension.GetAsInt64() { - return fmt.Errorf("invalid dimension: %d of field %s. float vector dimension should be in range 2 ~ %d", dim, field.GetName(), Params.ProxyCfg.MaxDimension.GetAsInt()) + return merr.WrapErrParameterInvalidMsg("invalid dimension: %d of field %s. float vector dimension should be in range 2 ~ %d", dim, field.GetName(), Params.ProxyCfg.MaxDimension.GetAsInt()) } } return nil @@ -476,7 +477,7 @@ func validateMaxLengthPerRow(collectionName string, field *schemapb.FieldSchema) } // if not exist type params max_length, return error if !exist { - return fmt.Errorf("type param(max_length) should be specified for the field(%s) of collection %s", field.GetName(), collectionName) + return merr.WrapErrParameterMissingMsg("type param(max_length) should be specified for the field(%s) of collection %s", field.GetName(), collectionName) } return nil @@ -493,16 +494,16 @@ func getMaxCapacityPerRow(collectionName string, field *schemapb.FieldSchema) (i var err error maxCapacityPerRow, err = strconv.ParseInt(param.Value, 10, 64) if err != nil { - return 0, fmt.Errorf("the value for %s of field %s must be an integer", common.MaxCapacityKey, field.GetName()) + return 0, merr.WrapErrParameterInvalidMsg("the value for %s of field %s must be an integer", common.MaxCapacityKey, field.GetName()) } if maxCapacityPerRow > defaultMaxArrayCapacity || maxCapacityPerRow <= 0 { - return 0, errors.New("the maximum capacity specified for a Array should be in (0, 4096]") + return 0, merr.WrapErrParameterInvalidMsg("the maximum capacity specified for a Array should be in (0, 4096]") } exist = true } // if not exist type params max_capacity, return error if !exist { - return 0, fmt.Errorf("type param(max_capacity) should be specified for array field %s of collection %s", field.GetName(), collectionName) + return 0, merr.WrapErrParameterMissingMsg("type param(max_capacity) should be specified for array field %s of collection %s", field.GetName(), collectionName) } return maxCapacityPerRow, nil } @@ -524,7 +525,7 @@ func validateVectorFieldMetricType(field *schemapb.FieldSchema) error { return nil } } - return fmt.Errorf(`index param "metric_type" is not specified for index float vector %s`, field.GetName()) + return merr.WrapErrParameterMissingMsg(`index param "metric_type" is not specified for index float vector %s`, field.GetName()) } func validateDuplicatedFieldName(schema *schemapb.CollectionSchema) error { @@ -532,7 +533,7 @@ func validateDuplicatedFieldName(schema *schemapb.CollectionSchema) error { validateFieldNames := func(name string) error { _, ok := names[name] if ok { - return errors.Newf("duplicated field name %s found", name) + return merr.WrapErrParameterInvalidMsg("duplicated field name %s found", name) } names[name] = true return nil @@ -562,20 +563,20 @@ func validateElementType(dataType schemapb.DataType) error { schemapb.DataType_Int64, schemapb.DataType_Float, schemapb.DataType_Double, schemapb.DataType_VarChar: return nil case schemapb.DataType_String: - return errors.New("string data type not supported yet, please use VarChar type instead") + return merr.WrapErrParameterInvalidMsg("string data type not supported yet, please use VarChar type instead") case schemapb.DataType_None: - return errors.New("element data type None is not valid") + return merr.WrapErrParameterInvalidMsg("element data type None is not valid") } - return fmt.Errorf("element type %s is not supported", dataType.String()) + return merr.WrapErrParameterInvalidMsg("element type %s is not supported", dataType.String()) } func validateFieldType(schema *schemapb.CollectionSchema) error { for _, field := range schema.GetFields() { switch field.GetDataType() { case schemapb.DataType_String: - return errors.New("string data type not supported yet, please use VarChar type instead") + return merr.WrapErrParameterInvalidMsg("string data type not supported yet, please use VarChar type instead") case schemapb.DataType_None: - return errors.New("data type None is not valid") + return merr.WrapErrParameterInvalidMsg("data type None is not valid") case schemapb.DataType_Array: if err := validateElementType(field.GetElementType()); err != nil { return err @@ -585,7 +586,7 @@ func validateFieldType(schema *schemapb.CollectionSchema) error { for _, structArrayField := range schema.StructArrayFields { for _, field := range structArrayField.Fields { if field.GetDataType() != schemapb.DataType_Array && field.GetDataType() != schemapb.DataType_ArrayOfVector { - return errors.Newf("fields in StructArrayField must be Array or ArrayOfVector, field name = %s, field type = %s", + return merr.WrapErrParameterInvalidMsg("fields in StructArrayField must be Array or ArrayOfVector, field name = %s, field type = %s", field.GetName(), field.GetDataType().String()) } } @@ -599,18 +600,18 @@ func ValidateFieldAutoID(coll *schemapb.CollectionSchema) error { for i, field := range coll.Fields { if field.AutoID { if idx != -1 { - return fmt.Errorf("only one field can speficy AutoID with true, field name = %s, %s", coll.Fields[idx].Name, field.Name) + return merr.WrapErrParameterInvalidMsg("only one field can speficy AutoID with true, field name = %s, %s", coll.Fields[idx].Name, field.Name) } idx = i if !field.IsPrimaryKey { - return fmt.Errorf("only primary field can speficy AutoID with true, field name = %s", field.Name) + return merr.WrapErrParameterInvalidMsg("only primary field can speficy AutoID with true, field name = %s", field.Name) } } } for _, structArrayField := range coll.StructArrayFields { for _, field := range structArrayField.Fields { if field.AutoID { - return errors.Newf("autoID is not supported for struct field, field name = %s", field.Name) + return merr.WrapErrParameterInvalidMsg("autoID is not supported for struct field, field name = %s", field.Name) } } } @@ -652,7 +653,7 @@ func ValidateField(field *schemapb.FieldSchema, schema *schemapb.CollectionSchem } if field.DataType == schemapb.DataType_ArrayOfVector { - return fmt.Errorf("array of vector can only be in the struct array field, field name: %s", field.Name) + return merr.WrapErrParameterInvalidMsg("array of vector can only be in the struct array field, field name: %s", field.Name) } // TODO should remove the index params in the field schema @@ -679,12 +680,12 @@ func ValidateFieldsInStruct(field *schemapb.FieldSchema, schema *schemapb.Collec } if field.DataType != schemapb.DataType_Array && field.DataType != schemapb.DataType_ArrayOfVector { - return fmt.Errorf("fields in StructArrayField can only be array or array of struct, but field %s is %s", field.Name, field.DataType.String()) + return merr.WrapErrParameterInvalidMsg("fields in StructArrayField can only be array or array of struct, but field %s is %s", field.Name, field.DataType.String()) } if field.ElementType == schemapb.DataType_ArrayOfStruct || field.ElementType == schemapb.DataType_ArrayOfVector || field.ElementType == schemapb.DataType_Array { - return fmt.Errorf("nested array is not supported %s", field.Name) + return merr.WrapErrParameterInvalidMsg("nested array is not supported %s", field.Name) } if field.DataType == schemapb.DataType_Array { @@ -694,7 +695,7 @@ func ValidateFieldsInStruct(field *schemapb.FieldSchema, schema *schemapb.Collec } else { // ArrayOfVector: support FloatVector, Float16Vector, BFloat16Vector, Int8Vector, BinaryVector if !typeutil.IsFixDimVectorType(field.GetElementType()) { - return fmt.Errorf("unsupported element type %s of ArrayOfVector field %s, only fixed dimension vector types are supported", field.GetElementType().String(), field.Name) + return merr.WrapErrParameterInvalidMsg("Unsupported element type %s of ArrayOfVector field %s, only fixed dimension vector types are supported", field.GetElementType().String(), field.Name) } err = validateDimension(field) @@ -754,7 +755,7 @@ func validateStructArrayFieldMaxCapacity(structArrayField *schemapb.StructArrayF // When the struct is nullable, sub-field schemas are mutated in-place to set Nullable=true. func ValidateStructArrayField(structArrayField *schemapb.StructArrayFieldSchema, schema *schemapb.CollectionSchema) error { if len(structArrayField.Fields) == 0 { - return fmt.Errorf("struct array field %s has no sub-fields", structArrayField.Name) + return merr.WrapErrParameterInvalidMsg("struct array field %s has no sub-fields", structArrayField.Name) } // Validate warmup policy if specified in struct field TypeParams @@ -796,19 +797,19 @@ func validatePrimaryKey(coll *schemapb.CollectionSchema) error { for i, field := range coll.Fields { if field.IsPrimaryKey { if idx != -1 { - return fmt.Errorf("there are more than one primary key, field name = %s, %s", coll.Fields[idx].Name, field.Name) + return merr.WrapErrParameterInvalidMsg("there are more than one primary key, field name = %s, %s", coll.Fields[idx].Name, field.Name) } // The type of the primary key field can only be int64 and varchar if field.DataType != schemapb.DataType_Int64 && field.DataType != schemapb.DataType_VarChar { - return errors.New("the data type of primary key should be Int64 or VarChar") + return merr.WrapErrParameterInvalidMsg("the data type of primary key should be Int64 or VarChar") } // varchar field do not support autoID // If autoID is required, it is recommended to use int64 field as the primary key //if field.DataType == schemapb.DataType_VarChar { // if field.AutoID { - // return errors.New("autoID is not supported when the VarChar field is the primary key") + // return merr.WrapErrParameterInvalidMsg("autoID is not supported when the VarChar field is the primary key") // } //} @@ -818,14 +819,14 @@ func validatePrimaryKey(coll *schemapb.CollectionSchema) error { if idx == -1 { // External collections may not have a primary key if !typeutil.IsExternalCollection(coll) { - return errors.New("primary key is not specified") + return merr.WrapErrParameterMissingMsg("primary key is not specified") } } for _, structArrayField := range coll.StructArrayFields { for _, field := range structArrayField.Fields { if field.IsPrimaryKey { - return errors.Newf("primary key is not supported for struct field, field name = %s", field.Name) + return merr.WrapErrParameterInvalidMsg("primary key is not supported for struct field, field name = %s", field.Name) } } } @@ -900,7 +901,7 @@ func injectVirtualPKForExternalCollection(schema *schemapb.CollectionSchema) err func validateDynamicField(coll *schemapb.CollectionSchema) error { for _, field := range coll.Fields { if field.IsDynamic { - return errors.New("cannot explicitly set a field as a dynamic field") + return merr.WrapErrParameterInvalidMsg("cannot explicitly set a field as a dynamic field") } } return nil @@ -912,7 +913,7 @@ func RepeatedKeyValToMap(kvPairs []*commonpb.KeyValuePair) (map[string]string, e for _, kv := range kvPairs { _, ok := resMap[kv.Key] if ok { - return nil, fmt.Errorf("duplicated param key: %s", kv.Key) + return nil, merr.WrapErrParameterInvalidMsg("duplicated param key: %s", kv.Key) } resMap[kv.Key] = kv.Value } @@ -932,7 +933,7 @@ func isVector(dataType schemapb.DataType) (bool, error) { return true, nil } - return false, fmt.Errorf("invalid data type: %d", dataType) + return false, merr.WrapErrParameterInvalidMsg("invalid data type: %d", dataType) } func validateMetricType(dataType schemapb.DataType, metricTypeStrRaw string) error { @@ -947,7 +948,7 @@ func validateMetricType(dataType schemapb.DataType, metricTypeStrRaw string) err return nil } } - return fmt.Errorf("data_type %s mismatch with metric_type %s", dataType.String(), metricTypeStrRaw) + return merr.WrapErrParameterInvalidMsg("data_type %s mismatch with metric_type %s", dataType.String(), metricTypeStrRaw) } func validateFunction(coll *schemapb.CollectionSchema, needValidateFunctionName string, disableRuntimeCheck bool) error { @@ -963,7 +964,7 @@ func validateFunction(coll *schemapb.CollectionSchema, needValidateFunctionName } if usedFunctionName.Contain(function.GetName()) { - return fmt.Errorf("duplicate function name: %s", function.GetName()) + return merr.WrapErrParameterInvalidMsg("duplicate function name: %s", function.GetName()) } usedFunctionName.Insert(function.GetName()) @@ -971,7 +972,7 @@ func validateFunction(coll *schemapb.CollectionSchema, needValidateFunctionName for _, name := range function.GetInputFieldNames() { inputField, ok := nameMap[name] if !ok { - return fmt.Errorf("function input field not found: %s", name) + return merr.WrapErrParameterInvalidMsg("function input field not found: %s", name) } inputFields = append(inputFields, inputField) } @@ -984,24 +985,24 @@ func validateFunction(coll *schemapb.CollectionSchema, needValidateFunctionName for i, name := range function.GetOutputFieldNames() { outputField, ok := nameMap[name] if !ok { - return fmt.Errorf("function output field not found: %s", name) + return merr.WrapErrParameterInvalidMsg("function output field not found: %s", name) } if outputField.GetIsPrimaryKey() { - return fmt.Errorf("function output field cannot be primary key: function %s, field %s", function.GetName(), outputField.GetName()) + return merr.WrapErrParameterInvalidMsg("function output field cannot be primary key: function %s, field %s", function.GetName(), outputField.GetName()) } if outputField.GetIsPartitionKey() || outputField.GetIsClusteringKey() { - return fmt.Errorf("function output field cannot be partition key or clustering key: function %s, field %s", function.GetName(), outputField.GetName()) + return merr.WrapErrParameterInvalidMsg("function output field cannot be partition key or clustering key: function %s, field %s", function.GetName(), outputField.GetName()) } if outputField.GetNullable() { - return fmt.Errorf("function output field cannot be nullable: function %s, field %s", function.GetName(), outputField.GetName()) + return merr.WrapErrParameterInvalidMsg("function output field cannot be nullable: function %s, field %s", function.GetName(), outputField.GetName()) } outputFields[i] = outputField if usedOutputField.Contain(name) { - return fmt.Errorf("duplicate function output field: function %s, field %s", function.GetName(), name) + return merr.WrapErrParameterInvalidMsg("duplicate function output field: function %s, field %s", function.GetName(), name) } usedOutputField.Insert(name) } @@ -1040,7 +1041,7 @@ func normalizeFunctionOutputFields(coll *schemapb.CollectionSchema) error { } field, ok := fieldsByID[fieldID] if !ok { - return fmt.Errorf("function output field id %d not found in schema", fieldID) + return merr.WrapErrParameterInvalidMsg("function output field id %d not found in schema", fieldID) } field.IsFunctionOutput = true } @@ -1050,7 +1051,7 @@ func normalizeFunctionOutputFields(coll *schemapb.CollectionSchema) error { } field, ok := fieldsByName[name] if !ok { - return fmt.Errorf("function output field not found: %s", name) + return merr.WrapErrParameterInvalidMsg("function output field not found: %s", name) } field.IsFunctionOutput = true } @@ -1062,11 +1063,11 @@ func checkFunctionOutputField(fSchema *schemapb.FunctionSchema, fields []*schema switch fSchema.GetType() { case schemapb.FunctionType_BM25: if len(fields) != 1 { - return fmt.Errorf("BM25 function only need 1 output field, but got %d", len(fields)) + return merr.WrapErrParameterInvalidMsg("BM25 function only need 1 output field, but got %d", len(fields)) } if !typeutil.IsSparseFloatVectorType(fields[0].GetDataType()) { - return fmt.Errorf("BM25 function output field must be a SparseFloatVector field, but got %s", fields[0].DataType.String()) + return merr.WrapErrParameterInvalidMsg("BM25 function output field must be a SparseFloatVector field, but got %s", fields[0].DataType.String()) } case schemapb.FunctionType_TextEmbedding: if err := embedding.TextEmbeddingOutputsCheck(fields); err != nil { @@ -1074,13 +1075,13 @@ func checkFunctionOutputField(fSchema *schemapb.FunctionSchema, fields []*schema } case schemapb.FunctionType_MinHash: if len(fields) != 1 { - return fmt.Errorf("MinHash function only need 1 output field, but got %d", len(fields)) + return merr.WrapErrParameterInvalidMsg("MinHash function only need 1 output field, but got %d", len(fields)) } if fields[0].GetDataType() != schemapb.DataType_BinaryVector { - return fmt.Errorf("MinHash function output field must be a BinaryVector field, but got %s", fields[0].DataType.String()) + return merr.WrapErrParameterInvalidMsg("MinHash function output field must be a BinaryVector field, but got %s", fields[0].DataType.String()) } default: - return errors.New("check output field for unknown function type") + return merr.WrapErrParameterInvalidMsg("check output field for unknown function type") } return nil } @@ -1089,12 +1090,12 @@ func checkFunctionInputField(function *schemapb.FunctionSchema, fields []*schema switch function.GetType() { case schemapb.FunctionType_BM25: if len(fields) != 1 || (fields[0].DataType != schemapb.DataType_VarChar && fields[0].DataType != schemapb.DataType_Text) { - return fmt.Errorf("BM25 function input field must be a VARCHAR/TEXT field, got %d field with type %s", + return merr.WrapErrParameterInvalidMsg("BM25 function input field must be a VARCHAR/TEXT field, got %d field with type %s", len(fields), fields[0].DataType.String()) } h := typeutil.CreateFieldSchemaHelper(fields[0]) if !h.EnableAnalyzer() { - return errors.New("BM25 function input field must set enable_analyzer to true") + return merr.WrapErrParameterInvalidMsg("BM25 function input field must set enable_analyzer to true") } case schemapb.FunctionType_TextEmbedding: if err := embedding.TextEmbeddingInputsCheck(function.GetName(), fields); err != nil { @@ -1102,59 +1103,59 @@ func checkFunctionInputField(function *schemapb.FunctionSchema, fields []*schema } case schemapb.FunctionType_MinHash: if len(fields) != 1 || (fields[0].DataType != schemapb.DataType_VarChar && fields[0].DataType != schemapb.DataType_Text) { - return fmt.Errorf("MinHash function input field must be a VARCHAR/TEXT field, got %d field with type %s", + return merr.WrapErrParameterInvalidMsg("MinHash function input field must be a VARCHAR/TEXT field, got %d field with type %s", len(fields), fields[0].DataType.String()) } default: - return errors.New("check input field with unknown function type") + return merr.WrapErrParameterInvalidMsg("check input field with unknown function type") } return nil } func checkFunctionBasicParams(function *schemapb.FunctionSchema) error { if function.GetName() == "" { - return errors.New("function name cannot be empty") + return merr.WrapErrParameterMissingMsg("function name cannot be empty") } if len(function.GetInputFieldNames()) == 0 { - return fmt.Errorf("function input field names cannot be empty, function: %s", function.GetName()) + return merr.WrapErrParameterMissingMsg("function input field names cannot be empty, function: %s", function.GetName()) } if len(function.GetOutputFieldNames()) == 0 { - return fmt.Errorf("function output field names cannot be empty, function: %s", function.GetName()) + return merr.WrapErrParameterMissingMsg("function output field names cannot be empty, function: %s", function.GetName()) } for _, input := range function.GetInputFieldNames() { if input == "" { - return fmt.Errorf("function input field name cannot be empty string, function: %s", function.GetName()) + return merr.WrapErrParameterMissingMsg("function input field name cannot be empty string, function: %s", function.GetName()) } // if input occurs more than once, error if lo.Count(function.GetInputFieldNames(), input) > 1 { - return fmt.Errorf("each function input field should be used exactly once in the same function, function: %s, input field: %s", function.GetName(), input) + return merr.WrapErrParameterInvalidMsg("each function input field should be used exactly once in the same function, function: %s, input field: %s", function.GetName(), input) } } for _, output := range function.GetOutputFieldNames() { if output == "" { - return fmt.Errorf("function output field name cannot be empty string, function: %s", function.GetName()) + return merr.WrapErrParameterMissingMsg("function output field name cannot be empty string, function: %s", function.GetName()) } if lo.Count(function.GetInputFieldNames(), output) > 0 { - return fmt.Errorf("a single field cannot be both input and output in the same function, function: %s, field: %s", function.GetName(), output) + return merr.WrapErrParameterInvalidMsg("a single field cannot be both input and output in the same function, function: %s, field: %s", function.GetName(), output) } if lo.Count(function.GetOutputFieldNames(), output) > 1 { - return fmt.Errorf("each function output field should be used exactly once in the same function, function: %s, output field: %s", function.GetName(), output) + return merr.WrapErrParameterInvalidMsg("each function output field should be used exactly once in the same function, function: %s, output field: %s", function.GetName(), output) } } switch function.GetType() { case schemapb.FunctionType_BM25: if len(function.GetParams()) != 0 { - return errors.New("BM25 function accepts no params") + return merr.WrapErrParameterInvalidMsg("BM25 function accepts no params") } case schemapb.FunctionType_TextEmbedding: if len(function.GetParams()) == 0 { - return errors.New("TextEmbedding function accepts no params") + return merr.WrapErrParameterInvalidMsg("TextEmbedding function accepts no params") } case schemapb.FunctionType_MinHash: // MinHash function can accept optional params return nil default: - return errors.New("check function params with unknown function type") + return merr.WrapErrParameterInvalidMsg("check function params with unknown function type") } return nil } @@ -1169,7 +1170,7 @@ func validateMultipleVectorFields(schema *schemapb.CollectionSchema) error { dType := schema.Fields[i].DataType isVec := typeutil.IsVectorType(dType) if isVec && vecExist && !enableMultipleVectorFields { - return fmt.Errorf( + return merr.WrapErrParameterInvalidMsg( "multiple vector fields is not supported, fields name: %s, %s", vecName, name, @@ -1291,7 +1292,7 @@ func CheckDuplicatePkExist(primaryFieldSchema *schemapb.FieldSchema, fieldsData } if primaryFieldData == nil { - return false, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("must assign pk when upsert, primary field: %v", primaryFieldSchema.GetName())) + return false, merr.WrapErrParameterInvalidMsg("must assign pk when upsert, primary field: %v", primaryFieldSchema.GetName()) } // check for duplicates based on primary key type @@ -1346,10 +1347,10 @@ func autoGenPrimaryFieldData(fieldSchema *schemapb.FieldSchema, data interface{} }, } default: - return nil, errors.New("currently only support autoID for int64 and varchar PrimaryField") + return nil, merr.WrapErrParameterInvalidMsg("currently only support autoID for int64 and varchar PrimaryField") } default: - return nil, errors.New("currently only int64 is supported as the data source for the autoID of a PrimaryField") + return nil, merr.WrapErrParameterInvalidMsg("currently only int64 is supported as the data source for the autoID of a PrimaryField") } return &fieldData, nil @@ -1408,7 +1409,7 @@ func validateFieldDataColumns(columns []*schemapb.FieldData, schema *schemaInfo) // Validate column count if len(columns) != expectColumnNum { - return fmt.Errorf("len(columns) mismatch the expectColumnNum, expectColumnNum: %d, len(columns): %d", + return merr.WrapErrParameterInvalidMsg("len(columns) mismatch the expectColumnNum, expectColumnNum: %d, len(columns): %d", expectColumnNum, len(columns)) } @@ -1416,7 +1417,7 @@ func validateFieldDataColumns(columns []*schemapb.FieldData, schema *schemaInfo) for _, fieldData := range columns { _, err := schema.schemaHelper.GetFieldFromNameDefaultJSON(fieldData.FieldName) if err != nil { - return fmt.Errorf("fieldName %v not exist in collection schema", fieldData.FieldName) + return merr.WrapErrParameterInvalidMsg("fieldName %v not exist in collection schema", fieldData.FieldName) } } @@ -1431,7 +1432,7 @@ func fillFieldPropertiesOnly(columns []*schemapb.FieldData, schema *schemaInfo) // Use schemaHelper to get field schema, automatically handles dynamic fields fieldSchema, err := schema.schemaHelper.GetFieldFromNameDefaultJSON(fieldData.FieldName) if err != nil { - return fmt.Errorf("fieldName %v not exist in collection schema", fieldData.FieldName) + return merr.WrapErrParameterInvalidMsg("fieldName %v not exist in collection schema", fieldData.FieldName) } fieldData.FieldId = fieldSchema.FieldID @@ -1442,14 +1443,14 @@ func fillFieldPropertiesOnly(columns []*schemapb.FieldData, schema *schemaInfo) case schemapb.DataType_Array: fd, ok := fieldData.Field.(*schemapb.FieldData_Scalars) if !ok || fd.Scalars.GetArrayData() == nil { - return fmt.Errorf("field convert FieldData_Scalars fail in fieldData, fieldName: %s, collectionName: %s", + return merr.WrapErrParameterInvalidMsg("field convert FieldData_Scalars fail in fieldData, fieldName: %s, collectionName: %s", fieldData.FieldName, schema.Name) } fd.Scalars.GetArrayData().ElementType = fieldSchema.ElementType case schemapb.DataType_ArrayOfVector: fd, ok := fieldData.Field.(*schemapb.FieldData_Vectors) if !ok || fd.Vectors.GetVectorArray() == nil { - return fmt.Errorf("field convert FieldData_Vectors fail in fieldData, fieldName: %s, collectionName: %s", + return merr.WrapErrParameterInvalidMsg("field convert FieldData_Vectors fail in fieldData, fieldName: %s, collectionName: %s", fieldData.FieldName, schema.Name) } fd.Vectors.GetVectorArray().ElementType = fieldSchema.ElementType @@ -1467,7 +1468,7 @@ func ValidateUsername(username string) error { } if len(username) > Params.ProxyCfg.MaxUsernameLength.GetAsInt() { - return merr.WrapErrParameterInvalidMsg("invalid username %s with length %d, the length of username must be less than %d", username, len(username), Params.ProxyCfg.MaxUsernameLength.GetValue()) + return merr.WrapErrParameterInvalidMsg("invalid username %s with length %d, the length of username must be less than %d", username, len(username), Params.ProxyCfg.MaxUsernameLength.GetAsInt()) } firstChar := username[0] @@ -1741,7 +1742,7 @@ func recallCal[T string | int64](results []T, gts []T) float32 { func computeRecall(results *schemapb.SearchResultData, gts *schemapb.SearchResultData) error { if results.GetNumQueries() != gts.GetNumQueries() { - return fmt.Errorf("num of queries is inconsistent between search results(%d) and ground truth(%d)", results.GetNumQueries(), gts.GetNumQueries()) + return merr.WrapErrParameterInvalidMsg("num of queries is inconsistent between search results(%d) and ground truth(%d)", results.GetNumQueries(), gts.GetNumQueries()) } // When search returns no results, IDs field is nil. Set recalls to 0 for all queries. @@ -1769,9 +1770,9 @@ func computeRecall(results *schemapb.SearchResultData, gts *schemapb.SearchResul results.Recalls = recalls return nil case *schemapb.IDs_StrId: - return errors.New("pk type is inconsistent between search results(int64) and ground truth(string)") + return merr.WrapErrParameterInvalidMsg("pk type is inconsistent between search results(int64) and ground truth(string)") default: - return errors.New("unsupported pk type") + return merr.WrapErrParameterInvalidMsg("unsupported pk type") } case *schemapb.IDs_StrId: @@ -1791,12 +1792,12 @@ func computeRecall(results *schemapb.SearchResultData, gts *schemapb.SearchResul results.Recalls = recalls return nil case *schemapb.IDs_IntId: - return errors.New("pk type is inconsistent between search results(string) and ground truth(int64)") + return merr.WrapErrParameterInvalidMsg("pk type is inconsistent between search results(string) and ground truth(int64)") default: - return errors.New("unsupported pk type") + return merr.WrapErrParameterInvalidMsg("unsupported pk type") } default: - return errors.New("unsupported pk type") + return merr.WrapErrParameterInvalidMsg("unsupported pk type") } } @@ -1870,7 +1871,7 @@ func translateOutputFields(outputFields []string, schema *schemaInfo, removePkFi } else if aggFieldName == "*" { // only count(*) is allowed if aggregateName != "count" { - return nil, nil, nil, nil, false, fmt.Errorf("%s(*) is not supported, only count(*) is allowed", aggregateName) + return nil, nil, nil, nil, false, merr.WrapErrParameterInvalidMsg("%s(*) is not supported, only count(*) is allowed", aggregateName) } if err := agg.ValidateAggFieldType(aggregateName, schemapb.DataType_None); err != nil { return nil, nil, nil, nil, false, err @@ -1881,7 +1882,7 @@ func translateOutputFields(outputFields []string, schema *schemaInfo, removePkFi } aggregates = append(aggregates, aggFuncs...) } else { - return nil, nil, nil, nil, false, fmt.Errorf("target field %s for aggregation:%s does not exist", aggFieldName, aggregateName) + return nil, nil, nil, nil, false, merr.WrapErrParameterInvalidMsg("target field %s for aggregation:%s does not exist", aggFieldName, aggregateName) } userOutputFieldsMap[outputFieldName] = true continue @@ -1898,7 +1899,7 @@ func translateOutputFields(outputFields []string, schema *schemaInfo, removePkFi } if field, ok := allFieldNameMap[outputFieldName]; ok { if !schema.CanRetrieveRawFieldData(field) { - return nil, nil, nil, nil, false, fmt.Errorf("not allowed to retrieve raw data of field %s", outputFieldName) + return nil, nil, nil, nil, false, merr.WrapErrParameterInvalidMsg("not allowed to retrieve raw data of field %s", outputFieldName) } resultFieldNameMap[outputFieldName] = true userOutputFieldsMap[outputFieldName] = true @@ -1911,12 +1912,12 @@ func translateOutputFields(outputFields []string, schema *schemaInfo, removePkFi dynamicField, _ := schema.schemaHelper.GetDynamicField() // only $meta["xxx"] is allowed for now if dynamicField.GetFieldID() != columnInfo.GetFieldId() { - return errors.New("not support getting subkeys of json field yet") + return merr.WrapErrParameterInvalidMsg("not support getting subkeys of json field yet") } nestedPaths := columnInfo.GetNestedPath() // $meta["A"]["B"] not allowed for now if len(nestedPaths) != 1 { - return errors.New("not support getting multiple level of dynamic field for now") + return merr.WrapErrParameterInvalidMsg("not support getting multiple level of dynamic field for now") } // $meta["dyn_field"], output field name could be: // 1. "dyn_field", outputFieldName == nestedPath @@ -1929,13 +1930,13 @@ func translateOutputFields(outputFields []string, schema *schemaInfo, removePkFi }) if err != nil { log.Info("parse output field name failed", zap.String("field name", outputFieldName), zap.Error(err)) - return nil, nil, nil, nil, false, fmt.Errorf("parse output field name failed: %s", outputFieldName) + return nil, nil, nil, nil, false, merr.WrapErrParameterInvalidMsg("parse output field name failed: %s", outputFieldName) } resultFieldNameMap[common.MetaFieldName] = true userOutputFieldsMap[outputFieldName] = true userDynamicFieldsMap[dynamicNestedPath] = true } else { - return nil, nil, nil, nil, false, fmt.Errorf("field %s not exist", outputFieldName) + return nil, nil, nil, nil, false, merr.WrapErrParameterInvalidMsg("field %s not exist", outputFieldName) } } } @@ -1974,13 +1975,13 @@ func validateIndexName(indexName string) error { invalidMsg := "Invalid index name: " + indexName + ". " if len(indexName) > Params.ProxyCfg.MaxNameLength.GetAsInt() { msg := invalidMsg + "The length of a index name must be less than " + Params.ProxyCfg.MaxNameLength.GetValue() + " characters." - return errors.New(msg) + return merr.WrapErrParameterInvalidMsg("%s", msg) } firstChar := indexName[0] if firstChar != '_' && !isAlpha(firstChar) { msg := invalidMsg + "The first character of a index name must be an underscore or letter." - return errors.New(msg) + return merr.WrapErrParameterInvalidMsg("%s", msg) } indexNameSize := len(indexName) @@ -1988,7 +1989,7 @@ func validateIndexName(indexName string) error { c := indexName[i] if !validCharInIndexName(c) { msg := invalidMsg + "Index name can only contain numbers, letters, and underscores." - return errors.New(msg) + return merr.WrapErrParameterInvalidMsg("%s", msg) } } return nil @@ -2143,18 +2144,18 @@ func checkAndFlattenStructFieldData(schema *schemapb.CollectionSchema, insertMsg structName := fieldData.FieldName structSchema, ok := structSchemaMap[structName] if !ok { - return fmt.Errorf("fieldName %v not exist in collection schema, fieldType %v, fieldId %v", fieldData.FieldName, fieldData.Type, fieldData.FieldId) + return merr.WrapErrParameterInvalidMsg("fieldName %v not exist in collection schema, fieldType %v, fieldId %v", fieldData.FieldName, fieldData.Type, fieldData.FieldId) } structFieldCount++ structArrays, ok := fieldData.Field.(*schemapb.FieldData_StructArrays) if !ok { - return fmt.Errorf("field convert FieldData_StructArrays fail in fieldData, fieldName: %s,"+ + return merr.WrapErrParameterInvalidMsg("field convert FieldData_StructArrays fail in fieldData, fieldName: %s,"+ " collectionName:%s", structName, schema.Name) } if len(structArrays.StructArrays.Fields) != len(structSchema.GetFields()) { - return fmt.Errorf("length of fields of struct field mismatch length of the fields in schema, fieldName: %s,"+ + return merr.WrapErrParameterInvalidMsg("length of fields of struct field mismatch length of the fields in schema, fieldName: %s,"+ " collectionName:%s, fieldData fields length:%d, schema fields length:%d", structName, schema.Name, len(structArrays.StructArrays.Fields), len(structSchema.GetFields())) } @@ -2175,7 +2176,7 @@ func checkAndFlattenStructFieldData(schema *schemapb.CollectionSchema, insertMsg for _, subField := range structArrays.StructArrays.Fields { for j, v := range subField.ValidData { if v { - return fmt.Errorf("sub-field '%s' in struct '%s' claims row %d is valid but no payload is provided", + return merr.WrapErrParameterInvalidMsg("sub-field '%s' in struct '%s' claims row %d is valid but no payload is provided", subField.FieldName, structName, j) } } @@ -2186,7 +2187,7 @@ func checkAndFlattenStructFieldData(schema *schemapb.CollectionSchema, insertMsg continue } if hasDataCount != totalSubFields { - return fmt.Errorf("inconsistent sub-field data in struct '%s': %d of %d sub-fields have data, all must be present or all absent", + return merr.WrapErrParameterInvalidMsg("inconsistent sub-field data in struct '%s': %d of %d sub-fields have data, all must be present or all absent", structName, hasDataCount, totalSubFields) } @@ -2204,12 +2205,12 @@ func checkAndFlattenStructFieldData(schema *schemapb.CollectionSchema, insertMsg continue } if len(subField.ValidData) != len(refValidData) { - return fmt.Errorf("sub-field ValidData length mismatch in struct '%s': '%s' has %d, '%s' has %d", + return merr.WrapErrParameterInvalidMsg("sub-field ValidData length mismatch in struct '%s': '%s' has %d, '%s' has %d", structName, refFieldName, len(refValidData), subField.FieldName, len(subField.ValidData)) } for j := range refValidData { if subField.ValidData[j] != refValidData[j] { - return fmt.Errorf("sub-field ValidData mismatch in struct '%s' at row %d: '%s'=%v, '%s'=%v", + return merr.WrapErrParameterInvalidMsg("sub-field ValidData mismatch in struct '%s' at row %d: '%s'=%v, '%s'=%v", structName, j, refFieldName, refValidData[j], subField.FieldName, subField.ValidData[j]) } } @@ -2232,10 +2233,10 @@ func checkAndFlattenStructFieldData(schema *schemapb.CollectionSchema, insertMsg vectorElementWidth := func(subField *schemapb.FieldData, subFieldSchema *schemapb.FieldSchema) (int, error) { dim, err := typeutil.GetDim(subFieldSchema) if err != nil { - return 0, fmt.Errorf("sub-field '%s' in struct '%s': %w", subField.GetFieldName(), structName, err) + return 0, merr.WrapErrParameterInvalidErr(err, "sub-field '%s' in struct '%s'", subField.GetFieldName(), structName) } if dim <= 0 { - return 0, fmt.Errorf("sub-field '%s' in struct '%s': invalid dim %d", subField.GetFieldName(), structName, dim) + return 0, merr.WrapErrParameterInvalidMsg("sub-field '%s' in struct '%s': invalid dim %d", subField.GetFieldName(), structName, dim) } switch subFieldSchema.GetElementType() { case schemapb.DataType_FloatVector, schemapb.DataType_Int8Vector: @@ -2245,7 +2246,7 @@ func checkAndFlattenStructFieldData(schema *schemapb.CollectionSchema, insertMsg case schemapb.DataType_Float16Vector, schemapb.DataType_BFloat16Vector: return int(dim * 2), nil default: - return 0, fmt.Errorf("sub-field '%s' in struct '%s': unsupported array-of-vector element type %s", + return 0, merr.WrapErrParameterInvalidMsg("sub-field '%s' in struct '%s': unsupported array-of-vector element type %s", subField.GetFieldName(), structName, subFieldSchema.GetElementType().String()) } } @@ -2264,7 +2265,7 @@ func checkAndFlattenStructFieldData(schema *schemapb.CollectionSchema, insertMsg for _, subField := range structArrays.StructArrays.Fields { subFieldSchema := subFieldSchemaByName[subField.GetFieldName()] if subFieldSchema == nil { - return fmt.Errorf("sub-field '%s' not found in struct schema '%s'", subField.GetFieldName(), structName) + return merr.WrapErrParameterInvalidMsg("sub-field '%s' not found in struct schema '%s'", subField.GetFieldName(), structName) } var currentArrayLen int @@ -2279,7 +2280,7 @@ func checkAndFlattenStructFieldData(schema *schemapb.CollectionSchema, insertMsg count: func(physicalRow int) (int, error) { row := scalarArray.GetData()[physicalRow] if row.GetData() == nil { - return 0, fmt.Errorf("nil array data") + return 0, merr.WrapErrParameterInvalidMsg("nil array data") } switch subFieldSchema.GetElementType() { case schemapb.DataType_Bool: @@ -2295,13 +2296,13 @@ func checkAndFlattenStructFieldData(schema *schemapb.CollectionSchema, insertMsg case schemapb.DataType_VarChar, schemapb.DataType_String: return len(row.GetStringData().GetData()), nil default: - return 0, fmt.Errorf("unsupported array element type %s", subFieldSchema.GetElementType().String()) + return 0, merr.WrapErrParameterInvalidMsg("unsupported array element type %s", subFieldSchema.GetElementType().String()) } }, }) } } else { - return fmt.Errorf("scalar array data is nil in struct field '%s', sub-field '%s'", + return merr.WrapErrParameterInvalidMsg("scalar array data is nil in struct field '%s', sub-field '%s'", structName, subField.FieldName) } case *schemapb.FieldData_Vectors: @@ -2321,7 +2322,7 @@ func checkAndFlattenStructFieldData(schema *schemapb.CollectionSchema, insertMsg } row := vectorArray.GetData()[physicalRow] if row.GetData() == nil { - return 0, fmt.Errorf("nil vector array data") + return 0, merr.WrapErrParameterInvalidMsg("nil vector array data") } var payloadLen int switch subFieldSchema.GetElementType() { @@ -2337,25 +2338,25 @@ func checkAndFlattenStructFieldData(schema *schemapb.CollectionSchema, insertMsg payloadLen = len(row.GetInt8Vector()) } if payloadLen%vectorWidth != 0 { - return 0, fmt.Errorf("payload length %d is not divisible by vector width %d", payloadLen, vectorWidth) + return 0, merr.WrapErrParameterInvalidMsg("payload length %d is not divisible by vector width %d", payloadLen, vectorWidth) } return payloadLen / vectorWidth, nil }, }) } } else { - return fmt.Errorf("vector array data is nil in struct field '%s', sub-field '%s'", + return merr.WrapErrParameterInvalidMsg("vector array data is nil in struct field '%s', sub-field '%s'", structName, subField.FieldName) } default: - return fmt.Errorf("unexpected field data type in struct array field, fieldName: %s", structName) + return merr.WrapErrParameterInvalidMsg("unexpected field data type in struct array field, fieldName: %s", structName) } if expectedArrayLen == -1 { expectedArrayLen = currentArrayLen firstValidData = subField.GetValidData() } else if currentArrayLen != expectedArrayLen { - return fmt.Errorf("inconsistent array length in struct field '%s': expected %d, got %d for sub-field '%s'", + return merr.WrapErrParameterInvalidMsg("inconsistent array length in struct field '%s': expected %d, got %d for sub-field '%s'", structName, expectedArrayLen, currentArrayLen, subField.FieldName) } } @@ -2370,7 +2371,7 @@ func checkAndFlattenStructFieldData(schema *schemapb.CollectionSchema, insertMsg } } if len(physicalToLogical) != expectedArrayLen { - return fmt.Errorf("invalid ValidData for struct '%s': true count %d does not match payload row count %d", + return merr.WrapErrParameterInvalidMsg("invalid ValidData for struct '%s': true count %d does not match payload row count %d", structName, len(physicalToLogical), expectedArrayLen) } } @@ -2386,8 +2387,8 @@ func checkAndFlattenStructFieldData(schema *schemapb.CollectionSchema, insertMsg for physicalRow := 0; physicalRow < expectedArrayLen; physicalRow++ { count, err := refCounter.count(physicalRow) if err != nil { - return fmt.Errorf("struct '%s' row %d sub-field '%s': %w", - structName, logicalRow(physicalRow), refCounter.name, err) + return merr.WrapErrParameterInvalidErr(err, "struct '%s' row %d sub-field '%s'", + structName, logicalRow(physicalRow), refCounter.name) } refElementCounts[physicalRow] = count } @@ -2395,11 +2396,11 @@ func checkAndFlattenStructFieldData(schema *schemapb.CollectionSchema, insertMsg for physicalRow := 0; physicalRow < expectedArrayLen; physicalRow++ { count, err := counter.count(physicalRow) if err != nil { - return fmt.Errorf("struct '%s' row %d sub-field '%s': %w", - structName, logicalRow(physicalRow), counter.name, err) + return merr.WrapErrParameterInvalidErr(err, "struct '%s' row %d sub-field '%s'", + structName, logicalRow(physicalRow), counter.name) } if count != refElementCounts[physicalRow] { - return fmt.Errorf("inconsistent struct element count in struct '%s' at row %d: '%s' has %d, '%s' has %d", + return merr.WrapErrParameterInvalidMsg("inconsistent struct element count in struct '%s' at row %d: '%s' has %d, '%s' has %d", structName, logicalRow(physicalRow), refCounter.name, refElementCounts[physicalRow], counter.name, count) } } @@ -2430,7 +2431,7 @@ func checkAndFlattenStructFieldData(schema *schemapb.CollectionSchema, insertMsg } for _, sf := range schema.GetStructArrayFields() { if !sf.GetNullable() && !seenStructs[sf.Name] { - return fmt.Errorf("required struct array field '%s' is missing in insert data", sf.Name) + return merr.WrapErrParameterInvalidMsg("required struct array field '%s' is missing in insert data", sf.Name) } } @@ -2474,7 +2475,7 @@ func checkPrimaryFieldData(allFields []*schemapb.FieldSchema, schema *schemapb.C } else { // check primary key data not exist if typeutil.IsPrimaryFieldDataExist(insertMsg.GetFieldsData(), primaryFieldSchema) { - return nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("can not assign primary field data when auto id enabled and allow_insert_auto_id is false %v", primaryFieldSchema.Name)) + return nil, merr.WrapErrParameterInvalidMsg("can not assign primary field data when auto id enabled and allow_insert_auto_id is false %v", primaryFieldSchema.Name) } // if autoID == true, currently support autoID for int64 and varchar PrimaryField primaryFieldData, err = autoGenPrimaryFieldData(primaryFieldSchema, insertMsg.GetRowIDs()) @@ -2568,7 +2569,7 @@ func checkInputUtf8Compatiable(allFields []*schemapb.FieldSchema, insertMsg *msg ok := utf8.ValidString(data) if !ok { log.Warn("string field data not utf-8 format", zap.String("messageVersion", strData.ProtoReflect().Descriptor().Syntax().GoString())) - return merr.WrapErrAsInputError(fmt.Errorf("input with analyzer should be utf-8 format, but row: %d not utf-8 format. data: %s", row, data)) + return merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg("input with analyzer should be utf-8 format, but row: %d not utf-8 format. data: %s", row, data)) } } } @@ -2620,7 +2621,7 @@ func checkUpsertPrimaryFieldData(allFields []*schemapb.FieldSchema, schema *sche } // must assign primary field data when upsert if primaryFieldData == nil { - return nil, nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("must assign pk when upsert, primary field: %v", primaryFieldName)) + return nil, nil, merr.WrapErrParameterInvalidMsg("must assign pk when upsert, primary field: %v", primaryFieldName) } // parse primaryFieldData to result.IDs, and as returned primary keys @@ -2642,7 +2643,7 @@ func checkUpsertPrimaryFieldData(allFields []*schemapb.FieldSchema, schema *sche func getPartitionKeyFieldData(fieldSchema *schemapb.FieldSchema, insertMsg *msgstream.InsertMsg) (*schemapb.FieldData, error) { if len(insertMsg.GetPartitionName()) > 0 && !Params.ProxyCfg.SkipPartitionKeyCheck.GetAsBool() { - return nil, errors.New("not support manually specifying the partition names if partition key mode is used") + return nil, merr.WrapErrParameterInvalidMsg("not support manually specifying the partition names if partition key mode is used") } for _, fieldData := range insertMsg.GetFieldsData() { @@ -2651,7 +2652,7 @@ func getPartitionKeyFieldData(fieldSchema *schemapb.FieldSchema, insertMsg *msgs } } - return nil, errors.New("partition key not specify when insert") + return nil, merr.WrapErrParameterInvalidMsg("partition key not specify when insert") } func getCollectionProgress( @@ -2855,7 +2856,7 @@ func verifyDynamicFieldData(schema *schemapb.CollectionSchema, insertMsg *msgstr for _, field := range insertMsg.FieldsData { if field.GetFieldName() == common.MetaFieldName { if !schema.EnableDynamicField { - return fmt.Errorf("without dynamic schema enabled, the field name cannot be set to %s", common.MetaFieldName) + return merr.WrapErrParameterInvalidMsg("without dynamic schema enabled, the field name cannot be set to %s", common.MetaFieldName) } for _, rowData := range field.GetScalars().GetJsonData().GetData() { jsonData := make(map[string]interface{}) @@ -2864,16 +2865,16 @@ func verifyDynamicFieldData(schema *schemapb.CollectionSchema, insertMsg *msgstr zap.ByteString("data", rowData), zap.Error(err), ) - return merr.WrapErrIoFailedReason(err.Error()) + return merr.WrapErrParameterInvalidMsg("invalid dynamic field data, only json map is supported: %s", err.Error()) } if _, ok := jsonData[common.MetaFieldName]; ok { - return fmt.Errorf("cannot set json key to: %s", common.MetaFieldName) + return merr.WrapErrParameterInvalidMsg("cannot set json key to: %s", common.MetaFieldName) } if !skipStaticFieldNameCheck { for _, f := range schema.GetFields() { if _, ok := jsonData[f.GetName()]; ok { log.Info("dynamic field name include the static field name", zap.String("fieldName", f.GetName())) - return fmt.Errorf("dynamic field name cannot include the static field name: %s", f.GetName()) + return merr.WrapErrParameterInvalidMsg("dynamic field name cannot include the static field name: %s", f.GetName()) } } } @@ -2930,7 +2931,7 @@ func addNamespaceData(schema *schemapb.CollectionSchema, insertMsg *msgstream.In // check namespace field exists namespaceField := typeutil.GetFieldByName(schema, common.NamespaceFieldName) if namespaceField == nil { - return fmt.Errorf("namespace field not found") + return merr.WrapErrParameterInvalidMsg("namespace field not found") } // If namespace field data is already present, validate it instead of rejecting outright. @@ -2942,15 +2943,15 @@ func addNamespaceData(schema *schemapb.CollectionSchema, insertMsg *msgstream.In } scalars := fieldData.GetScalars() if scalars == nil { - return fmt.Errorf("invalid namespace field data layout") + return merr.WrapErrParameterInvalidMsg("invalid namespace field data layout") } strData := scalars.GetStringData() if strData == nil { - return fmt.Errorf("invalid namespace field data layout") + return merr.WrapErrParameterInvalidMsg("invalid namespace field data layout") } for _, v := range strData.GetData() { if v != ns { - return fmt.Errorf("namespace field value %q mismatches namespace %q", v, ns) + return merr.WrapErrParameterInvalidMsg("namespace field value %q mismatches namespace %q", v, ns) } } // Values are consistent with the namespace; nothing more to do. @@ -3169,7 +3170,7 @@ func GetRequestInfo(ctx context.Context, req proto.Message) (int64, map[int64][] return util.InvalidDBID, map[int64][]int64{}, internalpb.RateType_DDLDB, 1, nil default: // TODO: support more request if req == nil { - return util.InvalidDBID, map[int64][]int64{}, 0, 0, errors.New("null request") + return util.InvalidDBID, map[int64][]int64{}, 0, 0, merr.WrapErrParameterInvalidMsg("null request") } log.RatedWarn(60, "not supported request type for rate limiter", zap.String("type", reflect.TypeOf(req).String())) return util.InvalidDBID, map[int64][]int64{}, 0, 0, nil @@ -3528,3 +3529,19 @@ func getBM25FunctionOfAnnsField(fieldID int64, functions []*schemapb.FunctionSch return function.GetType() == schemapb.FunctionType_BM25 && function.OutputFieldIds[0] == fieldID }) } + +// failMetricLabel classifies a request failure for the ProxyFunctionCall +// metric, matching the fail_input/fail_system split that +// requestutil.ParseMetricLabel emits at the gRPC interceptor; the legacy +// bare "fail" value must not reappear in this metric's value domain. +func failMetricLabel(err error) string { + // Client cancellation is neither party's failure; keep it out of the + // fail_system bucket (parity with ParseMetricLabel). + if errors.Is(err, context.Canceled) { + return metrics.CancelLabel + } + if merr.GetErrorType(err) == merr.InputError { + return metrics.FailInputLabel + } + return metrics.FailSystemLabel +} diff --git a/internal/proxy/util_test.go b/internal/proxy/util_test.go index f205f79a01..0f916e2410 100644 --- a/internal/proxy/util_test.go +++ b/internal/proxy/util_test.go @@ -42,6 +42,7 @@ import ( "github.com/milvus-io/milvus/internal/util/function/embedding" "github.com/milvus-io/milvus/pkg/v3/common" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/metrics" "github.com/milvus-io/milvus/pkg/v3/mq/msgstream" "github.com/milvus-io/milvus/pkg/v3/proto/internalpb" "github.com/milvus-io/milvus/pkg/v3/proto/planpb" @@ -6653,3 +6654,16 @@ func TestInjectVirtualPKForExternalCollection(t *testing.T) { assert.Equal(t, int64(0), schema.Fields[0].FieldID) }) } + +func TestFailMetricLabel(t *testing.T) { + // untyped / nil errors must take the conservative system bucket + assert.Equal(t, metrics.FailSystemLabel, failMetricLabel(nil)) + assert.Equal(t, metrics.FailSystemLabel, failMetricLabel(errors.New("plain error"))) + assert.Equal(t, metrics.FailSystemLabel, failMetricLabel(merr.WrapErrServiceInternalMsg("internal failure"))) + // input-classified merr errors go to the user bucket, also through wrapping + assert.Equal(t, metrics.FailInputLabel, failMetricLabel(merr.WrapErrParameterInvalidMsg("bad parameter"))) + assert.Equal(t, metrics.FailInputLabel, failMetricLabel(merr.Wrap(merr.WrapErrParameterInvalidMsg("bad parameter"), "context"))) + // client cancellation is neither party's failure + assert.Equal(t, metrics.CancelLabel, failMetricLabel(context.Canceled)) + assert.Equal(t, metrics.CancelLabel, failMetricLabel(errors.Wrap(context.Canceled, "rpc aborted"))) +} diff --git a/internal/proxy/validate_util.go b/internal/proxy/validate_util.go index 2858802962..cac4325cdb 100644 --- a/internal/proxy/validate_util.go +++ b/internal/proxy/validate_util.go @@ -560,7 +560,7 @@ func FillWithNullValue(field *schemapb.FieldData, fieldSchema *schemapb.FieldSch return err } default: - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("undefined data type:%s", field.Type.String())) + return merr.WrapErrParameterInvalidMsg("undefined data type:%s", field.Type.String()) } case *schemapb.FieldData_Vectors: @@ -572,7 +572,7 @@ func FillWithNullValue(field *schemapb.FieldData, fieldSchema *schemapb.FieldSch if field.Type == schemapb.DataType_ArrayOfVector { vectorArray := field.GetVectors().GetVectorArray() if vectorArray == nil { - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("array of vector data is nil, field: %s", field.GetFieldName())) + return merr.WrapErrParameterInvalidMsg("array of vector data is nil, field: %s", field.GetFieldName()) } expanded, err := fillVectorArrayNullValueImpl(vectorArray.GetData(), field.GetValidData(), vectorArray.GetDim(), vectorArray.GetElementType()) if err != nil { @@ -581,7 +581,7 @@ func FillWithNullValue(field *schemapb.FieldData, fieldSchema *schemapb.FieldSch vectorArray.Data = expanded } default: - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("undefined data type:%s", field.Type.String())) + return merr.WrapErrParameterInvalidMsg("undefined data type:%s", field.Type.String()) } return nil @@ -748,7 +748,7 @@ func FillWithDefaultValue(field *schemapb.FieldData, fieldSchema *schemapb.Field } default: - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("undefined data type:%s", field.Type.String())) + return merr.WrapErrParameterInvalidMsg("undefined data type:%s", field.Type.String()) } case *schemapb.FieldData_Vectors: @@ -756,7 +756,7 @@ func FillWithDefaultValue(field *schemapb.FieldData, fieldSchema *schemapb.Field return merr.WrapErrParameterInvalidMsg("vector type not support default value") default: - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("undefined data type:%s", field.Type.String())) + return merr.WrapErrParameterInvalidMsg("undefined data type:%s", field.Type.String()) } if !typeutil.IsVectorType(field.Type) { @@ -958,7 +958,7 @@ func (v *validateUtil) checkGeometryFieldData(field *schemapb.FieldData, fieldSc wkbArray[index], err = common.ConvertWKTToWKB(wktdata) if err != nil { log.Warn("insert invalid Geometry data!! Transform to wkb failed, has errors", zap.Error(err)) - return merr.WrapErrIoFailedReason(err.Error()) + return merr.WrapErrParameterInvalidMsg("invalid Geometry data, transform to wkb failed: %s", err.Error()) } } // replace the field data with wkb data array diff --git a/internal/proxy/vector_type_convert.go b/internal/proxy/vector_type_convert.go index f9aa8ce293..9da50795ac 100644 --- a/internal/proxy/vector_type_convert.go +++ b/internal/proxy/vector_type_convert.go @@ -18,13 +18,13 @@ package proxy import ( "encoding/binary" - "fmt" "math" "google.golang.org/protobuf/proto" "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" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -69,7 +69,7 @@ func isVectorTypeMatch(placeholderType commonpb.PlaceholderType, fieldType schem func ConvertPlaceholderGroup(phgBytes []byte, fieldSchema *schemapb.FieldSchema) ([]byte, commonpb.PlaceholderType, error) { var phg commonpb.PlaceholderGroup if err := proto.Unmarshal(phgBytes, &phg); err != nil { - return nil, 0, fmt.Errorf("failed to unmarshal placeholder group: %w", err) + return nil, 0, merr.WrapErrParameterInvalidMsg("failed to unmarshal placeholder group: %v", err) } if len(phg.Placeholders) == 0 { @@ -100,7 +100,7 @@ func ConvertPlaceholderGroup(phgBytes []byte, fieldSchema *schemapb.FieldSchema) // Check if conversion is supported (fp32 -> fp16/bf16) if placeholder.Type != commonpb.PlaceholderType_FloatVector { - return nil, phType, fmt.Errorf("vector type must be the same: field type %s, search type %s", + return nil, phType, merr.WrapErrParameterInvalidMsg("vector type must be the same: field type %s, search type %s", fieldType.String(), placeholder.Type.String()) } @@ -129,7 +129,7 @@ func convertPlaceholder( for i, valueBytes := range placeholder.Values { floats, err := bytesToFloat32Array(valueBytes) if err != nil { - return nil, fmt.Errorf("failed to parse float32 vector at index %d: %w", i, err) + return nil, merr.WrapErrParameterInvalidMsg("failed to parse float32 vector at index %d: %v", i, err) } convertedValues[i], err = typeutil.ConvertFloat32ToFP16BF16Bytes(floats, fieldType) @@ -182,7 +182,7 @@ func normalizeFP32ToFP16BF16VectorField(fieldData *schemapb.FieldData, fieldSche // bytesToFloat32Array converts byte slice to float32 array. func bytesToFloat32Array(data []byte) ([]float32, error) { if len(data)%4 != 0 { - return nil, fmt.Errorf("invalid float32 vector data length: %d", len(data)) + return nil, merr.WrapErrParameterInvalidMsg("invalid float32 vector data length: %d", len(data)) } dim := len(data) / 4 diff --git a/internal/querycoordv2/ddl_callbacks.go b/internal/querycoordv2/ddl_callbacks.go index c9bf4ba902..2cf3337475 100644 --- a/internal/querycoordv2/ddl_callbacks.go +++ b/internal/querycoordv2/ddl_callbacks.go @@ -25,6 +25,7 @@ import ( "github.com/milvus-io/milvus/internal/streamingcoord/server/broadcaster/broadcast" "github.com/milvus-io/milvus/internal/streamingcoord/server/broadcaster/registry" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -78,7 +79,7 @@ func (c *Server) startBroadcastWithCollectionIDLock(ctx context.Context, collect message.NewExclusiveCollectionNameResourceKey(coll.GetDbName(), coll.GetCollectionName()), ) if err != nil { - return nil, errors.Wrap(err, "failed to start broadcast with collection lock") + return nil, merr.Wrap(err, "failed to start broadcast with collection lock") } return broadcaster, nil } diff --git a/internal/querycoordv2/ddl_callbacks_alter_load_info_load_collection.go b/internal/querycoordv2/ddl_callbacks_alter_load_info_load_collection.go index a477a9d651..cfa61fd3ba 100644 --- a/internal/querycoordv2/ddl_callbacks_alter_load_info_load_collection.go +++ b/internal/querycoordv2/ddl_callbacks_alter_load_info_load_collection.go @@ -77,6 +77,12 @@ func (s *Server) broadcastAlterLoadConfigCollectionV2ForLoadCollection(ctx conte if err != nil { return err } + if msg == nil { + // load config unchanged, the collection is already loaded as requested. + log.Ctx(ctx).Info("load collection ignored, load config is unchanged", + zap.Int64("collectionID", req.GetCollectionID())) + return nil + } _, err = broadcaster.Broadcast(ctx, msg) return err } diff --git a/internal/querycoordv2/ddl_callbacks_alter_load_info_load_partitions.go b/internal/querycoordv2/ddl_callbacks_alter_load_info_load_partitions.go index 50b2fef71b..ea62423762 100644 --- a/internal/querycoordv2/ddl_callbacks_alter_load_info_load_partitions.go +++ b/internal/querycoordv2/ddl_callbacks_alter_load_info_load_partitions.go @@ -19,8 +19,11 @@ package querycoordv2 import ( "context" + "go.uber.org/zap" + "github.com/milvus-io/milvus/internal/querycoordv2/job" "github.com/milvus-io/milvus/internal/querycoordv2/utils" + "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/querypb" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -75,6 +78,13 @@ func (s *Server) broadcastAlterLoadConfigCollectionV2ForLoadPartitions(ctx conte if err != nil { return err } + if msg == nil { + // load config unchanged, the partitions are already loaded as requested. + log.Ctx(ctx).Info("load partitions ignored, load config is unchanged", + zap.Int64("collectionID", req.GetCollectionID()), + zap.Int64s("partitionIDs", req.GetPartitionIDs())) + return nil + } _, err = broadcaster.Broadcast(ctx, msg) return err } diff --git a/internal/querycoordv2/ddl_callbacks_alter_load_info_release_partitions.go b/internal/querycoordv2/ddl_callbacks_alter_load_info_release_partitions.go index 13c889a3bc..934248bd0b 100644 --- a/internal/querycoordv2/ddl_callbacks_alter_load_info_release_partitions.go +++ b/internal/querycoordv2/ddl_callbacks_alter_load_info_release_partitions.go @@ -55,7 +55,7 @@ func (s *Server) broadcastAlterLoadConfigCollectionV2ForReleasePartitions(ctx co // no partition to be released, return success directly. if len(partitionIDsSet) == previousLength { - return false, job.ErrIgnoredAlterLoadConfig + return false, nil } var msg message.BroadcastMutableMessage @@ -88,6 +88,10 @@ func (s *Server) broadcastAlterLoadConfigCollectionV2ForReleasePartitions(ctx co if msg, err = job.GenerateAlterLoadConfigMessage(ctx, alterLoadConfigReq); err != nil { return false, err } + if msg == nil { + // load config unchanged, nothing to broadcast. + return false, nil + } collectionReleased = false } _, err = broadcaster.Broadcast(ctx, msg) diff --git a/internal/querycoordv2/ddl_callbacks_alter_load_info_transfer_replica.go b/internal/querycoordv2/ddl_callbacks_alter_load_info_transfer_replica.go index 6640eae0a3..bf6d28c00f 100644 --- a/internal/querycoordv2/ddl_callbacks_alter_load_info_transfer_replica.go +++ b/internal/querycoordv2/ddl_callbacks_alter_load_info_transfer_replica.go @@ -20,8 +20,11 @@ import ( "context" "fmt" + "go.uber.org/zap" + "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus/internal/querycoordv2/job" + "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/querypb" "github.com/milvus-io/milvus/pkg/v3/util/merr" ) @@ -91,6 +94,15 @@ func (s *Server) broadcastAlterLoadConfigCollectionV2ForTransferReplica(ctx cont if err != nil { return err } + if msg == nil { + // The replica distribution already matches the request; the transfer is + // an idempotent no-op and succeeds without broadcasting. + log.Ctx(ctx).Info("transfer replica ignored, load config is unchanged", + zap.Int64("collectionID", req.GetCollectionID()), + zap.String("sourceResourceGroup", req.GetSourceResourceGroup()), + zap.String("targetResourceGroup", req.GetTargetResourceGroup())) + return nil + } _, err = broadcaster.Broadcast(ctx, msg) return err } diff --git a/internal/querycoordv2/ddl_callbacks_alter_resource_group.go b/internal/querycoordv2/ddl_callbacks_alter_resource_group.go index 5050b5c72f..173f11d263 100644 --- a/internal/querycoordv2/ddl_callbacks_alter_resource_group.go +++ b/internal/querycoordv2/ddl_callbacks_alter_resource_group.go @@ -32,11 +32,11 @@ import ( "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" ) -func (s *Server) broadcastCreateResourceGroup(ctx context.Context, req *milvuspb.CreateResourceGroupRequest) error { +func (s *Server) broadcastCreateResourceGroup(ctx context.Context, req *milvuspb.CreateResourceGroupRequest) (ignored bool, err error) { broadcaster, err := broadcast.StartBroadcastWithResourceKeys(ctx, message.NewExclusiveClusterResourceKey()) if err != nil { if !shouldApplyLocallyOnNonPrimary(err, message.MessageTypeAlterResourceGroup) { - return err + return false, err } } if broadcaster != nil { @@ -48,8 +48,8 @@ func (s *Server) broadcastCreateResourceGroup(ctx context.Context, req *milvuspb // Use default config if not set, compatible with old client. cfg = meta.NewResourceGroupConfig(0, 0) } - if err := s.meta.CheckIfResourceGroupAddable(ctx, req.GetResourceGroup(), cfg); err != nil { - return err + if ignored, err := s.meta.CheckIfResourceGroupAddable(ctx, req.GetResourceGroup(), cfg); err != nil || ignored { + return ignored, err } msg := message.NewAlterResourceGroupMessageBuilderV2(). @@ -60,10 +60,10 @@ func (s *Server) broadcastCreateResourceGroup(ctx context.Context, req *milvuspb WithBroadcast([]string{streaming.WAL().ControlChannel()}). MustBuildBroadcast() if broadcaster == nil { - return registry.CallMessageAckCallback(ctx, msg, nil) + return false, registry.CallMessageAckCallback(ctx, msg, nil) } _, err = broadcaster.Broadcast(ctx, msg) - return err + return false, err } func (s *Server) broadcastUpdateResourceGroups(ctx context.Context, req *querypb.UpdateResourceGroupsRequest) error { diff --git a/internal/querycoordv2/ddl_callbacks_drop_resource_group.go b/internal/querycoordv2/ddl_callbacks_drop_resource_group.go index 8be7d3c6c0..fd1927e949 100644 --- a/internal/querycoordv2/ddl_callbacks_drop_resource_group.go +++ b/internal/querycoordv2/ddl_callbacks_drop_resource_group.go @@ -20,8 +20,6 @@ import ( "context" "fmt" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb" "github.com/milvus-io/milvus/internal/distributed/streaming" "github.com/milvus-io/milvus/internal/streamingcoord/server/broadcaster/broadcast" @@ -30,11 +28,11 @@ import ( "github.com/milvus-io/milvus/pkg/v3/util/merr" ) -func (s *Server) broadcastDropResourceGroup(ctx context.Context, req *milvuspb.DropResourceGroupRequest) error { +func (s *Server) broadcastDropResourceGroup(ctx context.Context, req *milvuspb.DropResourceGroupRequest) (ignored bool, err error) { broadcaster, err := broadcast.StartBroadcastWithResourceKeys(ctx, message.NewExclusiveClusterResourceKey()) if err != nil { if !shouldApplyLocallyOnNonPrimary(err, message.MessageTypeDropResourceGroup) { - return err + return false, err } } if broadcaster != nil { @@ -44,11 +42,10 @@ func (s *Server) broadcastDropResourceGroup(ctx context.Context, req *milvuspb.D replicas := s.meta.GetByResourceGroup(ctx, req.GetResourceGroup()) if len(replicas) > 0 { err := merr.WrapErrParameterInvalid("empty resource group", fmt.Sprintf("resource group %s has collection %d loaded", req.GetResourceGroup(), replicas[0].GetCollectionID())) - return errors.Wrap(err, - fmt.Sprintf("some replicas still loaded in resource group[%s], release it first", req.GetResourceGroup())) + return false, merr.Wrapf(err, "some replicas still loaded in resource group[%s], release it first", req.GetResourceGroup()) } - if err := s.meta.CheckIfResourceGroupDropable(ctx, req.GetResourceGroup()); err != nil { - return err + if ignored, err := s.meta.CheckIfResourceGroupDropable(ctx, req.GetResourceGroup()); err != nil || ignored { + return ignored, err } msg := message.NewDropResourceGroupMessageBuilderV2(). @@ -59,10 +56,10 @@ func (s *Server) broadcastDropResourceGroup(ctx context.Context, req *milvuspb.D WithBroadcast([]string{streaming.WAL().ControlChannel()}). MustBuildBroadcast() if broadcaster == nil { - return registry.CallMessageAckCallback(ctx, msg, nil) + return false, registry.CallMessageAckCallback(ctx, msg, nil) } _, err = broadcaster.Broadcast(ctx, msg) - return err + return false, err } func (c *DDLCallbacks) dropResourceGroupV2AckCallback(ctx context.Context, result message.BroadcastResultDropResourceGroupMessageV2) error { diff --git a/internal/querycoordv2/handlers.go b/internal/querycoordv2/handlers.go index 7468efc8ca..d5be150d10 100644 --- a/internal/querycoordv2/handlers.go +++ b/internal/querycoordv2/handlers.go @@ -18,11 +18,9 @@ package querycoordv2 import ( "context" - "fmt" "sync" "time" - "github.com/cockroachdb/errors" "github.com/samber/lo" "github.com/tidwall/gjson" "go.uber.org/zap" @@ -166,7 +164,7 @@ func (s *Server) balanceSegments(ctx context.Context, if err != nil { msg := "failed to wait all balance task finished" log.Warn(msg, zap.Error(err)) - return errors.Wrap(err, msg) + return merr.Wrapf(err, "%s", msg) } } @@ -246,7 +244,7 @@ func (s *Server) balanceChannels(ctx context.Context, if err != nil { msg := "failed to wait all balance task finished" log.Warn(msg, zap.Error(err)) - return errors.Wrap(err, msg) + return merr.Wrapf(err, "%s", msg) } } @@ -322,7 +320,7 @@ func (s *Server) getSegmentsJSON(ctx context.Context, req *milvuspb.GetMetricsRe } return string(bs), nil } - return "", fmt.Errorf("invalid param value in=[%s], it should be qc or qn", in) + return "", merr.WrapErrParameterInvalidMsg("invalid param value in=[%s], it should be qc or qn", in) } // TODO(dragondriver): add more detail metrics diff --git a/internal/querycoordv2/job/job_load.go b/internal/querycoordv2/job/job_load.go index 058e83db12..8f449619ce 100644 --- a/internal/querycoordv2/job/job_load.go +++ b/internal/querycoordv2/job/job_load.go @@ -182,14 +182,14 @@ func (job *LoadCollectionJob) Execute() error { if len(toReleasePartitions) > 0 { job.targetObserver.ReleasePartition(req.GetCollectionId(), toReleasePartitions...) if err := job.meta.RemovePartition(job.ctx, req.GetCollectionId(), toReleasePartitions...); err != nil { - return errors.Wrap(err, "failed to remove partitions") + return merr.Wrap(err, "failed to remove partitions") } } if err = job.meta.PutCollection(job.ctx, collection, partitions...); err != nil { msg := "failed to store collection and partitions" log.Warn(msg, zap.Error(err)) - return errors.Wrap(err, msg) + return merr.Wrapf(err, "%s", msg) } eventlog.Record(eventlog.NewRawEvt(eventlog.Level_Info, fmt.Sprintf("Start load collection %d", collection.CollectionID))) metrics.QueryCoordNumPartitions.WithLabelValues().Add(float64(len(partitions))) diff --git a/internal/querycoordv2/job/job_release.go b/internal/querycoordv2/job/job_release.go index b833924506..5f13b9b2c9 100644 --- a/internal/querycoordv2/job/job_release.go +++ b/internal/querycoordv2/job/job_release.go @@ -19,7 +19,6 @@ package job import ( "context" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" @@ -31,6 +30,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/proxypb" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type ReleaseCollectionJob struct { @@ -84,7 +84,7 @@ func (job *ReleaseCollectionJob) Execute() error { if err != nil { msg := "failed to remove collection" log.Warn(msg, zap.Error(err)) - return errors.Wrap(err, msg) + return merr.Wrapf(err, "%s", msg) } job.targetObserver.ReleaseCollection(collectionID) @@ -109,13 +109,13 @@ func (job *ReleaseCollectionJob) Execute() error { if err := WaitCollectionReleased(job.ctx, job.dist, job.checkerController, collectionID); err != nil { log.Warn("failed to wait collection released", zap.Error(err)) - return errors.Wrap(err, "failed to wait collection released") + return merr.Wrap(err, "failed to wait collection released") } if err := job.meta.ReplicaManager.RemoveCollection(job.ctx, collectionID); err != nil { msg := "failed to remove replicas" log.Warn(msg, zap.Error(err)) - return errors.Wrap(err, msg) + return merr.Wrapf(err, "%s", msg) } log.Info("release collection job done", zap.Int64("collectionID", collectionID)) return nil diff --git a/internal/querycoordv2/job/job_sync.go b/internal/querycoordv2/job/job_sync.go index 0d89ca85aa..01540fcf87 100644 --- a/internal/querycoordv2/job/job_sync.go +++ b/internal/querycoordv2/job/job_sync.go @@ -20,7 +20,6 @@ import ( "context" "time" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus/internal/querycoordv2/meta" @@ -28,6 +27,7 @@ import ( "github.com/milvus-io/milvus/internal/querycoordv2/session" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/querypb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type SyncNewCreatedPartitionJob struct { @@ -92,7 +92,7 @@ func (job *SyncNewCreatedPartitionJob) Execute() error { if err != nil { msg := "failed to store partitions" log.Warn(msg, zap.Error(err)) - return errors.Wrap(err, msg) + return merr.Wrapf(err, "%s", msg) } return WaitUpdatePartition(job.ctx, job.targetObserver, job.req.GetCollectionID(), job.req.GetPartitionID()) diff --git a/internal/querycoordv2/job/load_config.go b/internal/querycoordv2/job/load_config.go index d0b6a62720..3a463c9a51 100644 --- a/internal/querycoordv2/job/load_config.go +++ b/internal/querycoordv2/job/load_config.go @@ -20,7 +20,6 @@ import ( "context" "sort" - "github.com/cockroachdb/errors" "github.com/samber/lo" "google.golang.org/protobuf/proto" @@ -33,8 +32,6 @@ import ( "github.com/milvus-io/milvus/pkg/v3/util/merr" ) -var ErrIgnoredAlterLoadConfig = errors.New("ignored alter load config") - type AlterLoadConfigRequest struct { Meta *meta.Meta CollectionInfo *milvuspb.DescribeCollectionResponse @@ -142,6 +139,8 @@ func (c *CurrentLoadConfig) IntoLoadConfigMessageHeader() *messagespb.AlterLoadC } // GenerateAlterLoadConfigMessage generates the alter load config message for the collection. +// It returns a nil message (with a nil error) when the expected load config is identical to +// the current one, i.e. there is nothing to broadcast and the operation is a no-op. func GenerateAlterLoadConfigMessage(ctx context.Context, req *AlterLoadConfigRequest) (message.BroadcastMutableMessage, error) { loadFields := generateLoadFields(req.Expected.ExpectedLoadFields, req.Expected.ExpectedFieldIndexID) loadReplicaConfigs, err := req.generateReplicas(ctx) @@ -162,9 +161,9 @@ func GenerateAlterLoadConfigMessage(ctx context.Context, req *AlterLoadConfigReq Replicas: loadReplicaConfigs, UserSpecifiedReplicaMode: req.Expected.ExpectedUserSpecifiedReplicaMode, } - // check if the load configuration is changed + // check if the load configuration is changed; nothing to broadcast if not. if previousHeader := req.Current.IntoLoadConfigMessageHeader(); proto.Equal(previousHeader, header) { - return nil, ErrIgnoredAlterLoadConfig + return nil, nil } return message.NewAlterLoadConfigMessageBuilderV2(). WithHeader(header). diff --git a/internal/querycoordv2/job/utils.go b/internal/querycoordv2/job/utils.go index f4182296ff..2640254203 100644 --- a/internal/querycoordv2/job/utils.go +++ b/internal/querycoordv2/job/utils.go @@ -20,7 +20,6 @@ import ( "context" "time" - "github.com/cockroachdb/errors" "github.com/samber/lo" "go.uber.org/zap" @@ -28,6 +27,7 @@ import ( "github.com/milvus-io/milvus/internal/querycoordv2/meta" "github.com/milvus-io/milvus/internal/querycoordv2/observers" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -46,7 +46,7 @@ func WaitCollectionReleased(ctx context.Context, dist *meta.DistributionManager, for { if err := ctx.Err(); err != nil { - return errors.Wrapf(err, "context error while waiting for release, collection=%d", collection) + return merr.Wrapf(err, "context error while waiting for release, collection=%d", collection) } var ( @@ -74,7 +74,7 @@ func WaitCollectionReleased(ctx context.Context, dist *meta.DistributionManager, // If release is not in progress for a while, return error if time.Since(lastChangeTime) > waitCollectionReleasedTimeout { - return errors.Errorf("wait collection released timeout, collection=%d, channels=%d, segments=%d", + return merr.WrapErrServiceUnavailableMsg("wait collection released timeout, collection=%d, channels=%d, segments=%d", collection, currentChannelCount, currentSegmentCount) } @@ -99,7 +99,7 @@ func WaitCurrentTargetUpdated(ctx context.Context, targetObserver *observers.Tar // manual trigger update next target ready, err := targetObserver.UpdateNextTarget(collection) if err != nil { - return errors.Wrapf(err, "failed to update next target, collection=%d", collection) + return merr.Wrapf(err, "failed to update next target, collection=%d", collection) } // accelerate check @@ -110,9 +110,9 @@ func WaitCurrentTargetUpdated(ctx context.Context, targetObserver *observers.Tar case <-ready: return nil case <-ctx.Done(): - return errors.Wrapf(ctx.Err(), "context error while waiting for current target updated, collection=%d", collection) + return merr.Wrapf(ctx.Err(), "context error while waiting for current target updated, collection=%d", collection) case <-time.After(waitCollectionReleasedTimeout): - return errors.Errorf("wait current target updated timeout, collection=%d", collection) + return merr.WrapErrServiceUnavailableMsg("wait current target updated timeout, collection=%d", collection) } } @@ -120,7 +120,7 @@ func WaitUpdatePartition(ctx context.Context, targetObserver *observers.TargetOb // manual trigger update next target ready, err := targetObserver.UpdatePartition(collection, partition) if err != nil { - return errors.Wrapf(err, "failed to update next target, collection=%d", collection) + return merr.Wrapf(err, "failed to update next target, collection=%d", collection) } select { case <-ready: @@ -136,8 +136,8 @@ func WaitUpdatePartition(ctx context.Context, targetObserver *observers.TargetOb case <-ready: return nil case <-ctx.Done(): - return errors.Wrapf(ctx.Err(), "context error while waiting for current target updated, collection=%d", collection) + return merr.Wrapf(ctx.Err(), "context error while waiting for current target updated, collection=%d", collection) case <-time.After(waitCollectionReleasedTimeout): - return errors.Errorf("wait current target updated timeout, collection=%d", collection) + return merr.WrapErrServiceUnavailableMsg("wait current target updated timeout, collection=%d", collection) } } diff --git a/internal/querycoordv2/meta/coordinator_broker.go b/internal/querycoordv2/meta/coordinator_broker.go index b7b714e6f8..712a89295b 100644 --- a/internal/querycoordv2/meta/coordinator_broker.go +++ b/internal/querycoordv2/meta/coordinator_broker.go @@ -274,7 +274,7 @@ func (broker *CoordinatorBroker) GetSegmentInfo(ctx context.Context, ids ...Uniq if len(resp.Infos) == 0 { log.Warn("No such segment in DataCoord") - return nil, errors.New("no such segment in DataCoord") + return nil, merr.WrapErrSegmentNotFound(ids[0], "no such segment in DataCoord") } err = binlog.DecompressMultiBinLogs(resp.GetInfos()) diff --git a/internal/querycoordv2/meta/replica_manager.go b/internal/querycoordv2/meta/replica_manager.go index f887fb4a32..cf7e79f6d4 100644 --- a/internal/querycoordv2/meta/replica_manager.go +++ b/internal/querycoordv2/meta/replica_manager.go @@ -22,7 +22,6 @@ import ( "sort" "time" - "github.com/cockroachdb/errors" "github.com/samber/lo" "go.uber.org/zap" @@ -108,7 +107,7 @@ func NewReplicaManager(idAllocator func() (int64, error), catalog metastore.Quer func (m *ReplicaManager) Recover(ctx context.Context, collections []int64) error { replicas, err := m.catalog.GetReplicas(ctx) if err != nil { - return fmt.Errorf("failed to recover replicas, err=%w", err) + return merr.Wrap(err, "failed to recover replicas") } collectionSet := typeutil.NewUniqueSet(collections...) @@ -245,10 +244,10 @@ func (m *ReplicaManager) SpawnWithReplicaConfig(ctx context.Context, params Spaw ) } if err := m.put(ctx, params.CollectionID, replicas...); err != nil { - return nil, errors.Wrap(err, "failed to put replicas") + return nil, merr.Wrap(err, "failed to put replicas") } if err := m.removeRedundantReplicas(ctx, params); err != nil { - return nil, errors.Wrap(err, "failed to remove redundant replicas") + return nil, merr.Wrap(err, "failed to remove redundant replicas") } return replicas, nil } @@ -683,7 +682,7 @@ func (m *ReplicaManager) RecoverNodesInCollection(ctx context.Context, collectio defer m.collLock.Unlock(collectionID) if _, ok := m.coll2Replicas.Get(collectionID); !ok { - return errors.Errorf("collection %d not loaded", collectionID) + return merr.WrapErrCollectionNotLoaded(collectionID) } // create a helper to do the recover. @@ -760,7 +759,7 @@ func (m *ReplicaManager) validateResourceGroups(rgs map[string]typeutil.UniqueSe for _, rg := range rgs { for id := range rg { if node.Contain(id) { - return errors.New("node in resource group is not mutual exclusive") + return merr.WrapErrServiceInternalMsg("node in resource group is not mutual exclusive") } node.Insert(id) } @@ -774,14 +773,14 @@ func (m *ReplicaManager) getCollectionAssignmentHelper(collectionID typeutil.Uni // check if the collection is exist. replicas, ok := m.coll2Replicas.Get(collectionID) if !ok { - return nil, errors.Errorf("collection %d not loaded", collectionID) + return nil, merr.WrapErrCollectionNotLoaded(collectionID) } rgToReplicas := make(map[string][]*Replica) for _, replica := range replicas { rgName := replica.GetResourceGroup() if _, ok := rgs[rgName]; !ok { - return nil, errors.Errorf("lost resource group info, collectionID: %d, replicaID: %d, resourceGroup: %s", collectionID, replica.GetID(), rgName) + return nil, merr.WrapErrServiceInternalMsg("lost resource group info, collectionID: %d, replicaID: %d, resourceGroup: %s", collectionID, replica.GetID(), rgName) } rgToReplicas[rgName] = append(rgToReplicas[rgName], replica) } @@ -876,7 +875,7 @@ func (m *ReplicaManager) RecoverSQNodesInCollection(ctx context.Context, collect replicas, ok := m.coll2Replicas.Get(collectionID) if !ok { - return errors.Errorf("collection %d not loaded", collectionID) + return merr.WrapErrCollectionNotLoaded(collectionID) } // Build helpers based on whether we can use resource group isolation. diff --git a/internal/querycoordv2/meta/resource_group.go b/internal/querycoordv2/meta/resource_group.go index c7b8765b53..f5b747c5e6 100644 --- a/internal/querycoordv2/meta/resource_group.go +++ b/internal/querycoordv2/meta/resource_group.go @@ -1,13 +1,13 @@ package meta import ( - "github.com/cockroachdb/errors" "google.golang.org/protobuf/proto" "github.com/milvus-io/milvus-proto/go-api/v3/rgpb" "github.com/milvus-io/milvus/internal/querycoordv2/session" "github.com/milvus-io/milvus/pkg/v3/common" "github.com/milvus-io/milvus/pkg/v3/proto/querypb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -281,7 +281,7 @@ func (rg *ResourceGroup) Snapshot() *ResourceGroup { func (rg *ResourceGroup) MeetRequirement() error { // if len(node) is less than requests, new node need to be assigned. if rg.MissingNumOfNodes() > 0 { - return errors.Errorf( + return merr.WrapErrServiceInternalMsg( "has %d nodes, less than request %d", rg.NodeNum(), rg.cfg.Requests.NodeNum, @@ -289,7 +289,7 @@ func (rg *ResourceGroup) MeetRequirement() error { } // if len(node) is greater than limits, node need to be removed. if rg.RedundantNumOfNodes() > 0 { - return errors.Errorf( + return merr.WrapErrServiceInternalMsg( "has %d nodes, greater than limit %d", rg.NodeNum(), rg.cfg.Requests.NodeNum, diff --git a/internal/querycoordv2/meta/resource_manager.go b/internal/querycoordv2/meta/resource_manager.go index d45b267fb3..f044b9b6c9 100644 --- a/internal/querycoordv2/meta/resource_manager.go +++ b/internal/querycoordv2/meta/resource_manager.go @@ -43,10 +43,10 @@ import ( "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) -var ( - ErrNodeNotEnough = errors.New("nodes not enough") - ErrResourceGroupOperationIgnored = errors.New("operation ignored") -) +// errNodeNotEnough is an INTERNAL sentinel: observed only inside the +// resource manager / resource observer recovery loop, never serialized +// across any gRPC boundary. See docs/dev/error_sentinel_convention.md. +var errNodeNotEnough = errors.New("nodes not enough") type ResourceManager struct { incomingNode typeutil.UniqueSet // incomingNode is a temporary set for incoming hangup node, @@ -91,7 +91,7 @@ func (rm *ResourceManager) Recover(ctx context.Context) error { rgs, err := rm.catalog.GetResourceGroups(ctx) if err != nil { - return errors.Wrap(err, "failed to recover resource group from store") + return merr.Wrap(err, "failed to recover resource group from store") } // Resource group meta upgrade to latest version. @@ -127,17 +127,21 @@ func (rm *ResourceManager) Recover(ctx context.Context) error { } // Deprecated: only for compatibility with unittest. -func (rm *ResourceManager) AddResourceGroup(ctx context.Context, rgName string, cfg *rgpb.ResourceGroupConfig) error { - if err := rm.CheckIfResourceGroupAddable(ctx, rgName, cfg); err != nil { - return err +// AddResourceGroup adds a resource group. Returns ignored=true if the +// resource group already exists with the same config (idempotent no-op). +func (rm *ResourceManager) AddResourceGroup(ctx context.Context, rgName string, cfg *rgpb.ResourceGroupConfig) (ignored bool, err error) { + if ignored, err := rm.CheckIfResourceGroupAddable(ctx, rgName, cfg); err != nil || ignored { + return ignored, err } - return rm.AlterResourceGroups(ctx, map[string]*rgpb.ResourceGroupConfig{rgName: cfg}) + return false, rm.AlterResourceGroups(ctx, map[string]*rgpb.ResourceGroupConfig{rgName: cfg}) } // CheckIfResourceGroupAddable check if a resource group can be added. -func (rm *ResourceManager) CheckIfResourceGroupAddable(ctx context.Context, rgName string, cfg *rgpb.ResourceGroupConfig) error { +// Returns ignored=true if the resource group already exists with the +// same config (idempotent no-op for callers to translate to success). +func (rm *ResourceManager) CheckIfResourceGroupAddable(ctx context.Context, rgName string, cfg *rgpb.ResourceGroupConfig) (ignored bool, err error) { if len(rgName) == 0 { - return merr.WrapErrParameterMissing("resource group name couldn't be empty") + return false, merr.WrapErrParameterMissing("resource group name couldn't be empty") } rm.rwmutex.Lock() @@ -146,20 +150,20 @@ func (rm *ResourceManager) CheckIfResourceGroupAddable(ctx context.Context, rgNa // Idempotent promise. // If resource group already exist, check if configuration is the same, if proto.Equal(rm.groups[rgName].GetConfig(), cfg) { - return ErrResourceGroupOperationIgnored + return true, nil } - return merr.WrapErrResourceGroupAlreadyExist(rgName) + return false, merr.WrapErrResourceGroupAlreadyExist(rgName) } maxResourceGroup := paramtable.Get().QuotaConfig.MaxResourceGroupNumOfQueryNode.GetAsInt() if len(rm.groups) >= maxResourceGroup { - return merr.WrapErrResourceGroupReachLimit(rgName, maxResourceGroup) + return false, merr.WrapErrResourceGroupReachLimit(rgName, maxResourceGroup) } if err := rm.validateResourceGroupConfig(rgName, cfg); err != nil { - return err + return false, err } - return nil + return false, nil } // AlterResourceGroups alter resource group configuration. @@ -237,7 +241,7 @@ func (rm *ResourceManager) updateResourceGroups(ctx context.Context, rgs map[str zap.Error(err), ) } - return merr.WrapErrResourceGroupServiceAvailable() + return merr.WrapErrResourceGroupServiceUnAvailable() } // Commit updates to memory. @@ -387,22 +391,25 @@ func (rm *ResourceManager) CheckIfTransferNode(ctx context.Context, sourceRGName // Deprecated: only for compatibility with unittest. func (rm *ResourceManager) RemoveResourceGroup(ctx context.Context, rgName string) error { - if err := rm.CheckIfResourceGroupDropable(ctx, rgName); err != nil { + ignored, err := rm.CheckIfResourceGroupDropable(ctx, rgName) + if err != nil || ignored { return err } return rm.DropResourceGroup(ctx, rgName) } // CheckIfResourceGroupDropable check if resource group can be dropped. -func (rm *ResourceManager) CheckIfResourceGroupDropable(ctx context.Context, rgName string) error { +// Returns ignored=true if the resource group doesn't exist (idempotent +// no-op for callers to translate to success). +func (rm *ResourceManager) CheckIfResourceGroupDropable(ctx context.Context, rgName string) (ignored bool, err error) { if rm.groups[rgName] == nil { // Idempotent promise: delete a non-exist rg should be ok - return ErrResourceGroupOperationIgnored + return true, nil } // validateResourceGroupIsDeletable will check if rg is deletable. if err := rm.validateResourceGroupIsDeletable(rgName); err != nil { - return err + return false, err } // Nodes may be still assign to these group, @@ -413,10 +420,10 @@ func (rm *ResourceManager) CheckIfResourceGroupDropable(ctx context.Context, rgN zap.String("rgName", rgName), zap.Error(err), ) - return err + return false, err } } - return nil + return false, nil } // DropResourceGroup drop resource group. @@ -434,7 +441,7 @@ func (rm *ResourceManager) DropResourceGroup(ctx context.Context, rgName string) zap.String("rgName", rgName), zap.Error(err), ) - return merr.WrapErrResourceGroupServiceAvailable() + return merr.WrapErrResourceGroupServiceUnAvailable() } // After recovering, all node assigned to these rg has been removed. @@ -489,7 +496,7 @@ func (rm *ResourceManager) VerifyNodeCount(ctx context.Context, requiredNodeCoun return merr.WrapErrResourceGroupNotFound(rgName) } if rm.groups[rgName].NodeNum() != nodeCount { - return ErrNodeNotEnough + return errNodeNotEnough } } @@ -729,7 +736,7 @@ func (rm *ResourceManager) recoverMissingNodeRG(ctx context.Context, rgName stri node, sourceRG := rm.selectNodeForMissingRecover(targetRG) if sourceRG == nil { log.Warn("fail to select source resource group", zap.String("rgName", targetRG.GetName())) - return ErrNodeNotEnough + return errNodeNotEnough } err := rm.transferNode(ctx, targetRG.GetName(), node) @@ -804,7 +811,7 @@ func (rm *ResourceManager) recoverRedundantNodeRG(ctx context.Context, rgName st if node == -1 { log.Info("failed to select redundant recover target resource group, please check resource group configuration if as expected.", zap.String("rgName", sourceRG.GetName())) - return errors.New("all resource group reach limits") + return merr.WrapErrServiceInternalMsg("all resource group reach limits") } if err := rm.transferNode(ctx, targetRG.GetName(), node); err != nil { @@ -884,12 +891,12 @@ func (rm *ResourceManager) assignIncomingNodeWithNodeCheck(ctx context.Context, nodeInfo := rm.nodeMgr.Get(node) if nodeInfo == nil { rm.incomingNode.Remove(node) - return "", errors.New("node is not online") + return "", merr.WrapErrServiceInternalMsg("node is not online") } if nodeInfo.IsStoppingState() { rm.incomingNode.Remove(node) - return "", errors.New("node has been stopped") + return "", merr.WrapErrServiceInternalMsg("node has been stopped") } rgName, err := rm.assignIncomingNode(ctx, nodeInfo) @@ -922,7 +929,7 @@ func (rm *ResourceManager) assignIncomingNode(ctx context.Context, nodeInfo *ses // select a resource group to assign incoming node. rg = rm.mustSelectAssignIncomingNodeTargetRG(nodeInfo) if err := rm.transferNode(ctx, rg.GetName(), node); err != nil { - return "", errors.Wrap(err, "at finally assign to default resource group") + return "", merr.Wrap(err, "at finally assign to default resource group") } return rg.GetName(), nil } @@ -1050,7 +1057,7 @@ func (rm *ResourceManager) transferNode(ctx context.Context, rgName string, node zap.Int64("node", node), zap.Error(err), ) - return merr.WrapErrResourceGroupServiceAvailable() + return merr.WrapErrResourceGroupServiceUnAvailable() } // Commit updates to memory. @@ -1096,7 +1103,7 @@ func (rm *ResourceManager) unassignNode(ctx context.Context, node int64) (string return rg.GetName(), nil } - return "", errors.Errorf("node %d not found in any resource group", node) + return "", merr.WrapErrNodeNotFound(node, "not found in any resource group") } // validateResourceGroupConfig validate resource group config. diff --git a/internal/querycoordv2/meta/resource_manager_test.go b/internal/querycoordv2/meta/resource_manager_test.go index 490d6ca749..58fa456da9 100644 --- a/internal/querycoordv2/meta/resource_manager_test.go +++ b/internal/querycoordv2/meta/resource_manager_test.go @@ -116,7 +116,7 @@ func (suite *ResourceManagerSuite) TestValidateConfiguration() { err = suite.manager.validateResourceGroupConfig("rg1", cfg) suite.ErrorIs(err, merr.ErrResourceGroupIllegalConfig) - err = suite.manager.AddResourceGroup(ctx, "rg2", newResourceGroupConfig(0, 0)) + _, err = suite.manager.AddResourceGroup(ctx, "rg2", newResourceGroupConfig(0, 0)) suite.NoError(err) err = suite.manager.RemoveResourceGroup(ctx, "rg2") @@ -126,7 +126,7 @@ func (suite *ResourceManagerSuite) TestValidateConfiguration() { func (suite *ResourceManagerSuite) TestValidateDelete() { ctx := suite.ctx // Non empty resource group can not be removed. - err := suite.manager.AddResourceGroup(ctx, "rg1", newResourceGroupConfig(1, 1)) + _, err := suite.manager.AddResourceGroup(ctx, "rg1", newResourceGroupConfig(1, 1)) suite.NoError(err) err = suite.manager.validateResourceGroupIsDeletable(DefaultResourceGroupName) @@ -167,31 +167,32 @@ func (suite *ResourceManagerSuite) TestValidateDelete() { func (suite *ResourceManagerSuite) TestManipulateResourceGroup() { ctx := suite.ctx // test add rg - err := suite.manager.AddResourceGroup(ctx, "rg1", newResourceGroupConfig(0, 0)) + _, err := suite.manager.AddResourceGroup(ctx, "rg1", newResourceGroupConfig(0, 0)) suite.NoError(err) suite.True(suite.manager.ContainResourceGroup(ctx, "rg1")) suite.Len(suite.manager.ListResourceGroups(ctx), 2) - // test add duplicate rg but same configuration is ok - err = suite.manager.AddResourceGroup(ctx, "rg1", newResourceGroupConfig(0, 0)) - suite.ErrorIs(err, ErrResourceGroupOperationIgnored) + // test add duplicate rg but same configuration is ok (signaled via ignored=true) + ignored, err := suite.manager.AddResourceGroup(ctx, "rg1", newResourceGroupConfig(0, 0)) + suite.NoError(err) + suite.True(ignored) - err = suite.manager.AddResourceGroup(ctx, "rg1", newResourceGroupConfig(1, 1)) + _, err = suite.manager.AddResourceGroup(ctx, "rg1", newResourceGroupConfig(1, 1)) suite.Error(err) // test delete rg err = suite.manager.RemoveResourceGroup(ctx, "rg1") suite.NoError(err) - // test delete rg which doesn't exist + // test delete rg which doesn't exist — RemoveResourceGroup swallows ignored, returns nil err = suite.manager.RemoveResourceGroup(ctx, "rg1") - suite.ErrorIs(err, ErrResourceGroupOperationIgnored) + suite.NoError(err) // test delete default rg err = suite.manager.RemoveResourceGroup(ctx, DefaultResourceGroupName) suite.ErrorIs(err, merr.ErrParameterInvalid) // test delete a rg not empty. - err = suite.manager.AddResourceGroup(ctx, "rg2", newResourceGroupConfig(1, 1)) + _, err = suite.manager.AddResourceGroup(ctx, "rg2", newResourceGroupConfig(1, 1)) suite.NoError(err) err = suite.manager.RemoveResourceGroup(ctx, "rg2") suite.ErrorIs(err, merr.ErrParameterInvalid) @@ -204,7 +205,7 @@ func (suite *ResourceManagerSuite) TestManipulateResourceGroup() { suite.NoError(err) // assign a node to rg. - err = suite.manager.AddResourceGroup(ctx, "rg2", newResourceGroupConfig(1, 1)) + _, err = suite.manager.AddResourceGroup(ctx, "rg2", newResourceGroupConfig(1, 1)) suite.NoError(err) suite.manager.nodeMgr.Add(session.NewNodeInfo(session.ImmutableNodeInfo{ NodeID: 1, @@ -252,7 +253,7 @@ func (suite *ResourceManagerSuite) TestNodeUpAndDown() { Address: "localhost", Hostname: "localhost", })) - err := suite.manager.AddResourceGroup(ctx, "rg1", newResourceGroupConfig(1, 1)) + _, err := suite.manager.AddResourceGroup(ctx, "rg1", newResourceGroupConfig(1, 1)) suite.NoError(err) // test add node to rg suite.manager.HandleNodeUp(ctx, 1) diff --git a/internal/querycoordv2/ops_services.go b/internal/querycoordv2/ops_services.go index 47269d6525..8611967735 100644 --- a/internal/querycoordv2/ops_services.go +++ b/internal/querycoordv2/ops_services.go @@ -84,7 +84,7 @@ func (s *Server) ActivateChecker(ctx context.Context, req *querypb.ActivateCheck } if err := s.checkerController.Activate(utils.CheckerType(req.CheckerID)); err != nil { log.Warn("failed to activate checker", zap.Error(err)) - return merr.Status(merr.WrapErrServiceInternal(err.Error())), nil + return merr.Status(merr.WrapErrParameterInvalidMsg("invalid checker type %d: %v", req.CheckerID, err)), nil } return merr.Success(), nil } @@ -98,7 +98,7 @@ func (s *Server) DeactivateChecker(ctx context.Context, req *querypb.DeactivateC } if err := s.checkerController.Deactivate(utils.CheckerType(req.CheckerID)); err != nil { log.Warn("failed to deactivate checker", zap.Error(err)) - return merr.Status(merr.WrapErrServiceInternal(err.Error())), nil + return merr.Status(merr.WrapErrParameterInvalidMsg("invalid checker type %d: %v", req.CheckerID, err)), nil } return merr.Success(), nil } @@ -112,7 +112,7 @@ func (s *Server) ListQueryNode(ctx context.Context, req *querypb.ListQueryNodeRe if err := merr.CheckHealthy(s.State()); err != nil { log.Warn(errMsg, zap.Error(err)) return &querypb.ListQueryNodeResponse{ - Status: merr.Status(errors.Wrap(err, errMsg)), + Status: merr.Status(merr.Wrapf(err, "%s", errMsg)), }, nil } @@ -159,7 +159,7 @@ func (s *Server) GetQueryNodeDistribution(ctx context.Context, req *querypb.GetQ if err := merr.CheckHealthy(s.State()); err != nil { log.Warn(errMsg, zap.Error(err)) return &querypb.GetQueryNodeDistributionResponse{ - Status: merr.Status(errors.Wrap(err, errMsg)), + Status: merr.Status(merr.Wrapf(err, "%s", errMsg)), }, nil } @@ -349,7 +349,7 @@ func (s *Server) ResumeNode(ctx context.Context, req *querypb.ResumeNodeRequest) errMsg := "failed to resume query node" if err := merr.CheckHealthy(s.State()); err != nil { log.Warn(errMsg, zap.Error(err)) - return merr.Status(errors.Wrap(err, errMsg)), nil + return merr.Status(merr.Wrapf(err, "%s", errMsg)), nil } info := s.nodeMgr.Get(req.GetNodeID()) @@ -383,7 +383,7 @@ func (s *Server) TransferSegment(ctx context.Context, req *querypb.TransferSegme if err := merr.CheckHealthy(s.State()); err != nil { msg := "failed to load balance" log.Warn(msg, zap.Error(err)) - return merr.Status(errors.Wrap(err, msg)), nil + return merr.Status(merr.Wrapf(err, "%s", msg)), nil } // check whether srcNode is healthy @@ -442,7 +442,7 @@ func (s *Server) TransferSegment(ctx context.Context, req *querypb.TransferSegme if err != nil { msg := "failed to balance segments" log.Warn(msg, zap.Error(err)) - return merr.Status(errors.Wrap(err, msg)), nil + return merr.Status(merr.Wrapf(err, "%s", msg)), nil } } return merr.Success(), nil @@ -462,7 +462,7 @@ func (s *Server) TransferChannel(ctx context.Context, req *querypb.TransferChann if err := merr.CheckHealthy(s.State()); err != nil { msg := "failed to load balance" log.Warn(msg, zap.Error(err)) - return merr.Status(errors.Wrap(err, msg)), nil + return merr.Status(merr.Wrapf(err, "%s", msg)), nil } // check whether srcNode is healthy @@ -516,7 +516,7 @@ func (s *Server) TransferChannel(ctx context.Context, req *querypb.TransferChann if err != nil { msg := "failed to balance channels" log.Warn(msg, zap.Error(err)) - return merr.Status(errors.Wrap(err, msg)), nil + return merr.Status(merr.Wrapf(err, "%s", msg)), nil } } return merr.Success(), nil @@ -553,7 +553,7 @@ func (s *Server) ClearReadTaskQueue(ctx context.Context, req *internalpb.ClearRe Role: typeutil.QueryNodeRole, NodeID: nodeID, } - return errors.Wrapf(err, "ClearReadTaskQueue failed, queryNodeID = %d", nodeID) + return merr.Wrapf(err, "ClearReadTaskQueue failed, queryNodeID = %d", nodeID) } if len(nodeResp.GetResults()) > 0 { @@ -570,7 +570,7 @@ func (s *Server) ClearReadTaskQueue(ctx context.Context, req *internalpb.ClearRe } } if !merr.Ok(nodeResp.GetStatus()) { - return errors.Wrapf(merr.Error(nodeResp.GetStatus()), "ClearReadTaskQueue failed, queryNodeID = %d", nodeID) + return merr.Wrapf(merr.Error(nodeResp.GetStatus()), "ClearReadTaskQueue failed, queryNodeID = %d", nodeID) } return nil }) diff --git a/internal/querycoordv2/params/params.go b/internal/querycoordv2/params/params.go index 594728c8d6..59de601315 100644 --- a/internal/querycoordv2/params/params.go +++ b/internal/querycoordv2/params/params.go @@ -30,6 +30,9 @@ import ( var Params *paramtable.ComponentParam = paramtable.Get() +// ErrFailedAllocateID is an identity sentinel returned by the test-only +// ErrorIDAllocator; keep it a plain errors.New (not merr) so errors.Is +// matches by identity instead of by merr error code. var ErrFailedAllocateID = errors.New("failed to allocate ID") // GenerateEtcdConfig returns a etcd config with a random root path, diff --git a/internal/querycoordv2/server.go b/internal/querycoordv2/server.go index 7cb8066a7e..7f42800e28 100644 --- a/internal/querycoordv2/server.go +++ b/internal/querycoordv2/server.go @@ -24,7 +24,6 @@ import ( "time" "github.com/blang/semver/v4" - "github.com/cockroachdb/errors" "github.com/samber/lo" "github.com/tidwall/gjson" "github.com/tikv/client-go/v2/txnkv" @@ -60,6 +59,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/metrics" "github.com/milvus-io/milvus/pkg/v3/util" "github.com/milvus-io/milvus/pkg/v3/util/expr" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/metricsinfo" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/timerecord" @@ -161,7 +161,7 @@ func (s *Server) Register() error { func (s *Server) SetSession(session sessionutil.SessionInterface) error { s.session = session if s.session == nil { - return errors.New("session is nil, the etcd client connection may have failed") + return merr.WrapErrServiceNotReadyMsg("session is nil, the etcd client connection may have failed") } return nil } @@ -281,7 +281,7 @@ func (s *Server) initQueryCoord() error { etcdkv.WithRequestTimeout(paramtable.Get().EtcdCfg.RequestTimeout.GetAsDuration(time.Millisecond))) idAllocatorKV = tsoutil.NewTSOKVBase(s.etcdCli, Params.EtcdCfg.KvRootPath.GetValue(), "querycoord-id-allocator") default: - return fmt.Errorf("not supported meta store: %s", metaType) + return merr.WrapErrServiceInternalMsg("unsupported meta store: %s", metaType) } log.Info(fmt.Sprintf("query coordinator successfully connected to %s.", metaType)) @@ -892,7 +892,7 @@ func (s *Server) GetInternalReplicasByCollection(ctx context.Context, collection func (s *Server) CheckAllReplicasServiceable(ctx context.Context, collectionID int64) error { replicas := s.meta.GetByCollection(ctx, collectionID) if len(replicas) == 0 { - return errors.New("no replica found") + return merr.WrapErrServiceInternalMsg("no replica found") } for _, replica := range replicas { if err := s.checkReplicaServiceable(ctx, replica); err != nil { @@ -905,17 +905,16 @@ func (s *Server) CheckAllReplicasServiceable(ctx context.Context, collectionID i func (s *Server) checkReplicaServiceable(ctx context.Context, replica *meta.Replica) error { channels := s.targetMgr.GetDmChannelsByCollection(ctx, replica.GetCollectionID(), meta.CurrentTarget) if len(channels) == 0 { - return errors.New("no channels in current target") + return merr.WrapErrServiceInternalMsg("no channels in current target") } for channelName := range channels { leader := s.dist.ChannelDistManager.GetShardLeader(channelName, replica) if leader == nil || leader.View == nil { - return fmt.Errorf("replica %d (rg=%s): no leader for channel %s", + return merr.WrapErrServiceInternalMsg("replica %d (rg=%s): no leader for channel %s", replica.GetID(), replica.GetResourceGroup(), channelName) } if err := utils.CheckDelegatorDataReady(s.nodeMgr, s.targetMgr, leader.View, meta.CurrentTarget); err != nil { - return fmt.Errorf("replica %d (rg=%s) channel %s not serviceable: %w", - replica.GetID(), replica.GetResourceGroup(), channelName, err) + return merr.Wrapf(err, "replica %d (rg=%s) channel %s not serviceable", replica.GetID(), replica.GetResourceGroup(), channelName) } } return nil diff --git a/internal/querycoordv2/services.go b/internal/querycoordv2/services.go index 632bf1f83a..5efb8c7cbd 100644 --- a/internal/querycoordv2/services.go +++ b/internal/querycoordv2/services.go @@ -46,13 +46,13 @@ import ( ) var ( -// ErrRemoveNodeFromRGFailed = errors.New("failed to remove node from resource group") -// ErrTransferNodeFailed = errors.New("failed to transfer node between resource group") -// ErrTransferReplicaFailed = errors.New("failed to transfer replica between resource group") -// ErrListResourceGroupsFailed = errors.New("failed to list resource group") -// ErrDescribeResourceGroupFailed = errors.New("failed to describe resource group") -// ErrLoadUseWrongRG = errors.New("load operation should use collection's resource group") -// ErrLoadWithDefaultRG = errors.New("load operation can't use default resource group and other resource group together") +// ErrRemoveNodeFromRGFailed = merr.WrapErrServiceInternalMsg("failed to remove node from resource group") +// ErrTransferNodeFailed = merr.WrapErrServiceInternalMsg("failed to transfer node between resource group") +// ErrTransferReplicaFailed = merr.WrapErrServiceInternalMsg("failed to transfer replica between resource group") +// ErrListResourceGroupsFailed = merr.WrapErrServiceInternalMsg("failed to list resource group") +// ErrDescribeResourceGroupFailed = merr.WrapErrServiceInternalMsg("failed to describe resource group") +// ErrLoadUseWrongRG = merr.WrapErrServiceInternalMsg("load operation should use collection's resource group") +// ErrLoadWithDefaultRG = merr.WrapErrServiceInternalMsg("load operation can't use default resource group and other resource group together") ) func (s *Server) ShowLoadCollections(ctx context.Context, req *querypb.ShowCollectionsRequest) (*querypb.ShowCollectionsResponse, error) { @@ -61,7 +61,7 @@ func (s *Server) ShowLoadCollections(ctx context.Context, req *querypb.ShowColle msg := "failed to show collections" log.Warn(msg, zap.Error(err)) return &querypb.ShowCollectionsResponse{ - Status: merr.Status(errors.Wrap(err, msg)), + Status: merr.Status(merr.Wrapf(err, "%s", msg)), }, nil } defer meta.GlobalFailedLoadCache.TryExpire() @@ -138,7 +138,7 @@ func (s *Server) ShowLoadPartitions(ctx context.Context, req *querypb.ShowPartit msg := "failed to show partitions" log.Warn(msg, zap.Error(err)) return &querypb.ShowPartitionsResponse{ - Status: merr.Status(errors.Wrap(err, msg)), + Status: merr.Status(merr.Wrapf(err, "%s", msg)), }, nil } defer meta.GlobalFailedLoadCache.TryExpire() @@ -227,11 +227,6 @@ func (s *Server) LoadCollection(ctx context.Context, req *querypb.LoadCollection } if err := s.broadcastAlterLoadConfigCollectionV2ForLoadCollection(ctx, req); err != nil { - if errors.Is(err, job.ErrIgnoredAlterLoadConfig) { - logger.Info("load collection ignored, collection is already loaded") - metrics.QueryCoordLoadCount.WithLabelValues(metrics.SuccessLabel).Inc() - return merr.Success(), nil - } logger.Warn("failed to load collection", zap.Error(err)) metrics.QueryCoordLoadCount.WithLabelValues(metrics.FailLabel).Inc() return merr.Status(err), nil @@ -305,11 +300,6 @@ func (s *Server) LoadPartitions(ctx context.Context, req *querypb.LoadPartitions } if err := s.broadcastAlterLoadConfigCollectionV2ForLoadPartitions(ctx, req); err != nil { - if errors.Is(err, job.ErrIgnoredAlterLoadConfig) { - logger.Info("load partitions ignored, partitions are already loaded") - metrics.QueryCoordLoadCount.WithLabelValues(metrics.SuccessLabel).Inc() - return merr.Success(), nil - } logger.Warn("failed to load partitions", zap.Error(err)) metrics.QueryCoordLoadCount.WithLabelValues(metrics.FailLabel).Inc() return merr.Status(err), nil @@ -343,11 +333,6 @@ func (s *Server) ReleasePartitions(ctx context.Context, req *querypb.ReleasePart collectionReleased, err := s.broadcastAlterLoadConfigCollectionV2ForReleasePartitions(ctx, req) if err != nil { - if errors.Is(err, job.ErrIgnoredAlterLoadConfig) { - logger.Info("release partitions ignored, partitions are already released") - metrics.QueryCoordReleaseCount.WithLabelValues(metrics.SuccessLabel).Inc() - return merr.Success(), nil - } logger.Warn("failed to release partitions", zap.Error(err)) metrics.QueryCoordReleaseCount.WithLabelValues(metrics.FailLabel).Inc() return merr.Status(err), nil @@ -369,7 +354,7 @@ func (s *Server) GetPartitionStates(ctx context.Context, req *querypb.GetPartiti msg := "failed to get partition states" log.Warn(msg, zap.Error(err)) return &querypb.GetPartitionStatesResponse{ - Status: merr.Status(errors.Wrap(err, msg)), + Status: merr.Status(merr.Wrapf(err, "%s", msg)), }, nil } @@ -437,7 +422,7 @@ func (s *Server) GetLoadSegmentInfo(ctx context.Context, req *querypb.GetSegment msg := "failed to get segment info" log.Warn(msg, zap.Error(err)) return &querypb.GetSegmentInfoResponse{ - Status: merr.Status(errors.Wrap(err, msg)), + Status: merr.Status(merr.Wrapf(err, "%s", msg)), }, nil } @@ -452,7 +437,7 @@ func (s *Server) GetLoadSegmentInfo(ctx context.Context, req *querypb.GetSegment msg := fmt.Sprintf("segment %v not found in any node", segmentID) log.Warn(msg, zap.Int64("segment", segmentID)) return &querypb.GetSegmentInfoResponse{ - Status: merr.Status(errors.Wrap(err, msg)), + Status: merr.Status(merr.Wrapf(err, "%s", msg)), }, nil } info := &querypb.SegmentInfo{} @@ -621,7 +606,7 @@ func (s *Server) isStoppingNode(ctx context.Context, nodeID int64) error { if isStopping { msg := fmt.Sprintf("failed to balance due to the source/destination node[%d] is stopping", nodeID) log.Ctx(ctx).Warn(msg) - return errors.New(msg) + return merr.WrapErrServiceUnavailable(msg) } return nil } @@ -639,7 +624,7 @@ func (s *Server) LoadBalance(ctx context.Context, req *querypb.LoadBalanceReques if err := merr.CheckHealthy(s.State()); err != nil { msg := "failed to load balance" log.Warn(msg, zap.Error(err)) - return merr.Status(errors.Wrap(err, msg)), nil + return merr.Status(merr.Wrapf(err, "%s", msg)), nil } // Verify request @@ -664,8 +649,7 @@ func (s *Server) LoadBalance(ctx context.Context, req *querypb.LoadBalanceReques return merr.Status(err), nil } if err := s.isStoppingNode(ctx, srcNode); err != nil { - return merr.Status(errors.Wrap(err, - fmt.Sprintf("can't balance, because the source node[%d] is invalid", srcNode))), nil + return merr.Status(merr.Wrapf(err, "can't balance, because the source node[%d] is invalid", srcNode)), nil } // when no dst node specified, default to use all other nodes in same @@ -686,8 +670,7 @@ func (s *Server) LoadBalance(ctx context.Context, req *querypb.LoadBalanceReques // check whether dstNode is healthy for dstNode := range dstNodeSet { if err := s.isStoppingNode(ctx, dstNode); err != nil { - return merr.Status(errors.Wrap(err, - fmt.Sprintf("can't balance, because the destination node[%d] is invalid", dstNode))), nil + return merr.Status(merr.Wrapf(err, "can't balance, because the destination node[%d] is invalid", dstNode)), nil } } @@ -723,7 +706,7 @@ func (s *Server) LoadBalance(ctx context.Context, req *querypb.LoadBalanceReques if err != nil { msg := "failed to balance segments" log.Warn(msg, zap.Error(err)) - return merr.Status(errors.Wrap(err, msg)), nil + return merr.Status(merr.Wrapf(err, "%s", msg)), nil } return merr.Success(), nil @@ -738,7 +721,7 @@ func (s *Server) ShowConfigurations(ctx context.Context, req *internalpb.ShowCon msg := "failed to show configurations" log.Warn(msg, zap.Error(err)) return &internalpb.ShowConfigurationsResponse{ - Status: merr.Status(errors.Wrap(err, msg)), + Status: merr.Status(merr.Wrapf(err, "%s", msg)), }, nil } configList := make([]*commonpb.KeyValuePair, 0) @@ -766,7 +749,7 @@ func (s *Server) GetMetrics(ctx context.Context, req *milvuspb.GetMetricsRequest msg := "failed to get metrics" log.Warn(msg, zap.Error(err)) return &milvuspb.GetMetricsResponse{ - Status: merr.Status(errors.Wrap(err, msg)), + Status: merr.Status(merr.Wrapf(err, "%s", msg)), }, nil } @@ -797,7 +780,7 @@ func (s *Server) GetReplicas(ctx context.Context, req *milvuspb.GetReplicasReque msg := "failed to get replicas" log.Warn(msg, zap.Error(err)) return &milvuspb.GetReplicasResponse{ - Status: merr.Status(errors.Wrap(err, msg)), + Status: merr.Status(merr.Wrapf(err, "%s", msg)), }, nil } @@ -827,7 +810,7 @@ func (s *Server) GetShardLeaders(ctx context.Context, req *querypb.GetShardLeade msg := "failed to get shard leaders" log.Warn(msg, zap.Error(err)) return &querypb.GetShardLeadersResponse{ - Status: merr.Status(errors.Wrap(err, msg)), + Status: merr.Status(merr.Wrapf(err, "%s", msg)), }, nil } @@ -903,14 +886,15 @@ func (s *Server) CreateResourceGroup(ctx context.Context, req *milvuspb.CreateRe return merr.Status(err), nil } - if err := s.broadcastCreateResourceGroup(ctx, req); err != nil { - if errors.Is(err, meta.ErrResourceGroupOperationIgnored) { - log.Info("create resource group request ignored") - return merr.Success(), nil - } + ignored, err := s.broadcastCreateResourceGroup(ctx, req) + if err != nil { log.Warn("failed to create resource group", zap.Error(err)) return merr.Status(err), nil } + if ignored { + log.Info("create resource group request ignored") + return merr.Success(), nil + } log.Info("create resource group done") return merr.Success(), nil } @@ -945,14 +929,15 @@ func (s *Server) DropResourceGroup(ctx context.Context, req *milvuspb.DropResour return merr.Status(err), nil } - if err := s.broadcastDropResourceGroup(ctx, req); err != nil { - if errors.Is(err, meta.ErrResourceGroupOperationIgnored) { - log.Info("drop resource group request ignored") - return merr.Success(), nil - } + ignored, err := s.broadcastDropResourceGroup(ctx, req) + if err != nil { log.Warn("failed to drop resource group", zap.Error(err)) return merr.Status(err), nil } + if ignored { + log.Info("drop resource group request ignored") + return merr.Success(), nil + } log.Info("drop resource group done") return merr.Success(), nil } @@ -1105,14 +1090,14 @@ func (s *Server) UpdateLoadConfig(ctx context.Context, req *querypb.UpdateLoadCo if err := merr.CheckHealthy(s.State()); err != nil { msg := "failed to update load config" log.Warn(msg, zap.Error(err)) - return merr.Status(errors.Wrap(err, msg)), nil + return merr.Status(merr.Wrapf(err, "%s", msg)), nil } err := s.updateLoadConfig(ctx, req.GetCollectionIDs(), req.GetReplicaNumber(), req.GetResourceGroups()) if err != nil { msg := "failed to update load config" log.Warn(msg, zap.Error(err)) - return merr.Status(errors.Wrap(err, msg)), nil + return merr.Status(merr.Wrapf(err, "%s", msg)), nil } log.Info("update load config request finished") @@ -1186,7 +1171,7 @@ func (s *Server) updateLoadConfig(ctx context.Context, collectionIDs []int64, ne func (s *Server) ListLoadedSegments(ctx context.Context, req *querypb.ListLoadedSegmentsRequest) (*querypb.ListLoadedSegmentsResponse, error) { if err := merr.CheckHealthy(s.State()); err != nil { return &querypb.ListLoadedSegmentsResponse{ - Status: merr.Status(errors.Wrap(err, "failed to list loaded segments")), + Status: merr.Status(merr.Wrap(err, "failed to list loaded segments")), }, nil } segmentIDs := typeutil.NewUniqueSet() @@ -1218,7 +1203,7 @@ func (s *Server) ListLoadedSegments(ctx context.Context, req *querypb.ListLoaded func (s *Server) RunAnalyzer(ctx context.Context, req *querypb.RunAnalyzerRequest) (*milvuspb.RunAnalyzerResponse, error) { if err := merr.CheckHealthy(s.State()); err != nil { return &milvuspb.RunAnalyzerResponse{ - Status: merr.Status(errors.Wrap(err, "failed to run analyzer")), + Status: merr.Status(merr.Wrap(err, "failed to run analyzer")), }, nil } @@ -1226,7 +1211,7 @@ func (s *Server) RunAnalyzer(ctx context.Context, req *querypb.RunAnalyzerReques if len(nodeIDs) == 0 { return &milvuspb.RunAnalyzerResponse{ - Status: merr.Status(errors.New("failed to validate analyzer, no delegator")), + Status: merr.Status(merr.WrapErrServiceUnavailable("failed to validate analyzer, no delegator")), }, nil } @@ -1242,13 +1227,13 @@ func (s *Server) RunAnalyzer(ctx context.Context, req *querypb.RunAnalyzerReques func (s *Server) ValidateAnalyzer(ctx context.Context, req *querypb.ValidateAnalyzerRequest) (*querypb.ValidateAnalyzerResponse, error) { if err := merr.CheckHealthy(s.State()); err != nil { - return &querypb.ValidateAnalyzerResponse{Status: merr.Status(errors.Wrap(err, "failed to validate analyzer"))}, nil + return &querypb.ValidateAnalyzerResponse{Status: merr.Status(merr.Wrap(err, "failed to validate analyzer"))}, nil } nodeIDs := snmanager.StaticStreamingNodeManager.GetStreamingQueryNodeIDs().Collect() if len(nodeIDs) == 0 { - return &querypb.ValidateAnalyzerResponse{Status: merr.Status(errors.New("failed to validate analyzer, no delegator"))}, nil + return &querypb.ValidateAnalyzerResponse{Status: merr.Status(merr.WrapErrServiceUnavailable("failed to validate analyzer, no delegator"))}, nil } idx := s.nodeIdx.Inc() % uint32(len(nodeIDs)) @@ -1262,7 +1247,7 @@ func (s *Server) ValidateAnalyzer(ctx context.Context, req *querypb.ValidateAnal func (s *Server) ComputePhraseMatchSlop(ctx context.Context, req *querypb.ComputePhraseMatchSlopRequest) (*querypb.ComputePhraseMatchSlopResponse, error) { if err := merr.CheckHealthy(s.State()); err != nil { return &querypb.ComputePhraseMatchSlopResponse{ - Status: merr.Status(errors.Wrap(err, "failed to compute phrase match slop")), + Status: merr.Status(merr.Wrap(err, "failed to compute phrase match slop")), }, nil } @@ -1270,7 +1255,7 @@ func (s *Server) ComputePhraseMatchSlop(ctx context.Context, req *querypb.Comput if len(nodeIDs) == 0 { return &querypb.ComputePhraseMatchSlopResponse{ - Status: merr.Status(errors.New("failed to compute phrase match slop, no query node available")), + Status: merr.Status(merr.WrapErrServiceUnavailable("failed to compute phrase match slop, no query node available")), }, nil } diff --git a/internal/querycoordv2/services_test.go b/internal/querycoordv2/services_test.go index 798c8d4339..a0e201f441 100644 --- a/internal/querycoordv2/services_test.go +++ b/internal/querycoordv2/services_test.go @@ -714,12 +714,12 @@ func (suite *ServiceSuite) TestTransferNode() { defer server.resourceObserver.Stop() defer server.replicaObserver.Stop() - err := server.meta.AddResourceGroup(ctx, "rg1", &rgpb.ResourceGroupConfig{ + _, err := server.meta.AddResourceGroup(ctx, "rg1", &rgpb.ResourceGroupConfig{ Requests: &rgpb.ResourceGroupLimit{NodeNum: 0}, Limits: &rgpb.ResourceGroupLimit{NodeNum: 0}, }) suite.NoError(err) - err = server.meta.AddResourceGroup(ctx, "rg2", &rgpb.ResourceGroupConfig{ + _, err = server.meta.AddResourceGroup(ctx, "rg2", &rgpb.ResourceGroupConfig{ Requests: &rgpb.ResourceGroupLimit{NodeNum: 0}, Limits: &rgpb.ResourceGroupLimit{NodeNum: 0}, }) @@ -778,12 +778,12 @@ func (suite *ServiceSuite) TestTransferNode() { suite.NoError(err) suite.Equal(commonpb.ErrorCode_IllegalArgument, resp.ErrorCode) - err = server.meta.AddResourceGroup(ctx, "rg3", &rgpb.ResourceGroupConfig{ + _, err = server.meta.AddResourceGroup(ctx, "rg3", &rgpb.ResourceGroupConfig{ Requests: &rgpb.ResourceGroupLimit{NodeNum: 4}, Limits: &rgpb.ResourceGroupLimit{NodeNum: 4}, }) suite.NoError(err) - err = server.meta.AddResourceGroup(ctx, "rg4", &rgpb.ResourceGroupConfig{ + _, err = server.meta.AddResourceGroup(ctx, "rg4", &rgpb.ResourceGroupConfig{ Requests: &rgpb.ResourceGroupLimit{NodeNum: 0}, Limits: &rgpb.ResourceGroupLimit{NodeNum: 0}, }) @@ -862,17 +862,17 @@ func (suite *ServiceSuite) TestTransferReplica() { suite.loadAll() server := suite.server - err := server.meta.AddResourceGroup(ctx, "rg1", &rgpb.ResourceGroupConfig{ + _, err := server.meta.AddResourceGroup(ctx, "rg1", &rgpb.ResourceGroupConfig{ Requests: &rgpb.ResourceGroupLimit{NodeNum: 1}, Limits: &rgpb.ResourceGroupLimit{NodeNum: 1}, }) suite.NoError(err) - err = server.meta.AddResourceGroup(ctx, "rg2", &rgpb.ResourceGroupConfig{ + _, err = server.meta.AddResourceGroup(ctx, "rg2", &rgpb.ResourceGroupConfig{ Requests: &rgpb.ResourceGroupLimit{NodeNum: 1}, Limits: &rgpb.ResourceGroupLimit{NodeNum: 1}, }) suite.NoError(err) - err = server.meta.AddResourceGroup(ctx, "rg3", &rgpb.ResourceGroupConfig{ + _, err = server.meta.AddResourceGroup(ctx, "rg3", &rgpb.ResourceGroupConfig{ Requests: &rgpb.ResourceGroupLimit{NodeNum: 3}, Limits: &rgpb.ResourceGroupLimit{NodeNum: 3}, }) diff --git a/internal/querycoordv2/session/cluster.go b/internal/querycoordv2/session/cluster.go index 4b259ab8d6..b42c6af714 100644 --- a/internal/querycoordv2/session/cluster.go +++ b/internal/querycoordv2/session/cluster.go @@ -18,11 +18,9 @@ package session import ( "context" - "fmt" "sync" "time" - "github.com/cockroachdb/errors" "go.uber.org/zap" "google.golang.org/protobuf/proto" @@ -33,15 +31,10 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/internalpb" "github.com/milvus-io/milvus/pkg/v3/proto/querypb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) -var ErrNodeNotFound = errors.New("NodeNotFound") - -func WrapErrNodeNotFound(nodeID int64) error { - return fmt.Errorf("%w(%v)", ErrNodeNotFound, nodeID) -} - type Cluster interface { WatchDmChannels(ctx context.Context, nodeID int64, req *querypb.WatchDmChannelsRequest) (*commonpb.Status, error) UnsubDmChannel(ctx context.Context, nodeID int64, req *querypb.UnsubDmChannelRequest) (*commonpb.Status, error) @@ -368,7 +361,7 @@ func (c *QueryCluster) ComputePhraseMatchSlop(ctx context.Context, nodeID int64, func (c *QueryCluster) send(ctx context.Context, nodeID int64, fn func(cli types.QueryNodeClient)) error { node := c.nodeManager.Get(nodeID) if node == nil { - return WrapErrNodeNotFound(nodeID) + return merr.WrapErrNodeNotFound(nodeID) } cli, err := c.getOrCreate(ctx, node) diff --git a/internal/querycoordv2/session/cluster_test.go b/internal/querycoordv2/session/cluster_test.go index c7a299546d..c7ca7b6b84 100644 --- a/internal/querycoordv2/session/cluster_test.go +++ b/internal/querycoordv2/session/cluster_test.go @@ -238,7 +238,7 @@ func (suite *ClusterTestSuite) TestLoadSegments() { Infos: []*querypb.SegmentLoadInfo{{}}, }) suite.Error(err) - suite.IsType(WrapErrNodeNotFound(3), err) + suite.IsType(merr.WrapErrNodeNotFound(3), err) } func (suite *ClusterTestSuite) TestWatchDmChannels() { diff --git a/internal/querycoordv2/session/node_manager.go b/internal/querycoordv2/session/node_manager.go index ed333d6308..5a99bd362c 100644 --- a/internal/querycoordv2/session/node_manager.go +++ b/internal/querycoordv2/session/node_manager.go @@ -18,7 +18,6 @@ package session import ( "context" - "fmt" "sync" "time" @@ -28,6 +27,7 @@ import ( "github.com/milvus-io/milvus/internal/util/sessionutil" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/metrics" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -78,7 +78,7 @@ func (m *NodeManager) IsStoppingNode(nodeID int64) (bool, error) { node := m.nodes[nodeID] if node == nil { - return false, fmt.Errorf("nodeID[%d] isn't existed", nodeID) + return false, merr.WrapErrNodeNotFound(nodeID) } return node.IsStoppingState(), nil } diff --git a/internal/querycoordv2/task/executor.go b/internal/querycoordv2/task/executor.go index 1a3c2e12f8..beb6685a3a 100644 --- a/internal/querycoordv2/task/executor.go +++ b/internal/querycoordv2/task/executor.go @@ -18,7 +18,6 @@ package task import ( "context" - "fmt" "math" "sync" "time" @@ -322,11 +321,11 @@ func (ex *Executor) loadSegment(task *SegmentTask, step int) error { // // node := ex.nodeMgr.Get(view.Node) // if node == nil { -// return merr.WrapErrServiceInternal(fmt.Sprintf("node %d is not found", view.Node)) +// return merr.WrapErrServiceInternalMsg("node %d is not found", view.Node) // } // nodes := snmanager.StaticStreamingNodeManager.GetStreamingQueryNodeIDs() // if !nodes.Contain(view.Node) { -// return merr.WrapErrServiceInternal(fmt.Sprintf("channel %s at node %d is not working at streamingnode, skip load segment", view.GetChannelName(), view.Node)) +// return merr.WrapErrServiceInternalMsg("channel %s at node %d is not working at streamingnode, skip load segment", view.GetChannelName(), view.Node) // } // return nil // } @@ -390,7 +389,7 @@ func (ex *Executor) releaseSegment(task *SegmentTask, step int) { // NOTE: for balance segment task, expected load and release execution on the same shard leader if GetTaskType(task) == TaskTypeMove && task.ShardLeaderID() != view.Node { msg := "shard leader changed, skip release" - err = merr.WrapErrServiceInternal(fmt.Sprintf("shard leader changed from %d to %d", task.ShardLeaderID(), view.Node)) + err = merr.WrapErrServiceInternalMsg("shard leader changed from %d to %d", task.ShardLeaderID(), view.Node) log.Warn(msg, zap.Error(err)) return } @@ -484,7 +483,7 @@ func (ex *Executor) subscribeChannel(task *ChannelTask, step int) error { partitions, err = utils.GetPartitions(ctx, ex.targetMgr, task.collectionID) if err != nil { log.Warn("failed to get partitions", zap.Error(err)) - return merr.WrapErrServiceInternal(fmt.Sprintf("failed to get partitions for collection=%d", task.CollectionID())) + return merr.Wrapf(err, "failed to get partitions for collection=%d", task.CollectionID()) } version := ex.targetMgr.GetCollectionTargetVersion(ctx, task.CollectionID(), meta.NextTargetFirst) diff --git a/internal/querycoordv2/utils/util.go b/internal/querycoordv2/utils/util.go index ac7baab763..7d04c75e80 100644 --- a/internal/querycoordv2/utils/util.go +++ b/internal/querycoordv2/utils/util.go @@ -56,7 +56,7 @@ func CheckDelegatorDataReady(nodeMgr *session.NodeManager, targetMgr meta.Target if info == nil { err := merr.WrapErrNodeOffline(leader.ID) log.Info("leader is not available", zap.Error(err)) - return fmt.Errorf("leader not available: %w", err) + return merr.Wrap(err, "leader not available") } // Check if delegator is still catching up with streaming data diff --git a/internal/querynodev2/cluster/manager.go b/internal/querynodev2/cluster/manager.go index 738496a673..d8c4ec68c9 100644 --- a/internal/querynodev2/cluster/manager.go +++ b/internal/querynodev2/cluster/manager.go @@ -18,13 +18,13 @@ package cluster import ( context "context" - "fmt" "strconv" "go.uber.org/zap" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/util/conc" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -70,7 +70,7 @@ func (m *grpcWorkerManager) GetWorker(ctx context.Context, nodeID int64) (Worker } if !worker.IsHealthy() { // TODO wrap error - return nil, fmt.Errorf("node is not healthy: %d", nodeID) + return nil, merr.WrapErrNodeNotAvailable(nodeID, "node is not healthy") } return worker, nil } diff --git a/internal/querynodev2/delegator/delegator.go b/internal/querynodev2/delegator/delegator.go index 66f4abdd4e..0d2fe851f4 100644 --- a/internal/querynodev2/delegator/delegator.go +++ b/internal/querynodev2/delegator/delegator.go @@ -458,7 +458,7 @@ func (sd *shardDelegator) Search(ctx context.Context, req *querypb.SearchRequest log.Warn("delegator received search request not belongs to it", zap.Strings("reqChannels", req.GetDmlChannels()), ) - return nil, fmt.Errorf("dml channel not match, delegator channel %s, search channels %v", sd.vchannelName, req.GetDmlChannels()) + return nil, merr.WrapErrChannelMisrouted(sd.vchannelName, fmt.Sprintf("request channels %v", req.GetDmlChannels())) } req.Req.GuaranteeTimestamp = sd.speedupGuranteeTS( @@ -591,14 +591,14 @@ func (sd *shardDelegator) Search(ctx context.Context, req *querypb.SearchRequest func (sd *shardDelegator) QueryStream(ctx context.Context, req *querypb.QueryRequest, srv streamrpc.QueryStreamServer) error { log := sd.getLogger(ctx) if !sd.Serviceable() { - return errors.New("delegator is not serviceable") + return merr.WrapErrServiceUnavailable("delegator", "not serviceable") } if !funcutil.SliceContain(req.GetDmlChannels(), sd.vchannelName) { log.Warn("deletgator received query request not belongs to it", zap.Strings("reqChannels", req.GetDmlChannels()), ) - return fmt.Errorf("dml channel not match, delegator channel %s, search channels %v", sd.vchannelName, req.GetDmlChannels()) + return merr.WrapErrChannelMisrouted(sd.vchannelName, fmt.Sprintf("request channels %v", req.GetDmlChannels())) } req.Req.GuaranteeTimestamp = sd.speedupGuranteeTS( @@ -686,7 +686,7 @@ func (sd *shardDelegator) Query(ctx context.Context, req *querypb.QueryRequest) log.Warn("delegator received query request not belongs to it", zap.Strings("reqChannels", req.GetDmlChannels()), ) - return nil, fmt.Errorf("dml channel not match, delegator channel %s, search channels %v", sd.vchannelName, req.GetDmlChannels()) + return nil, merr.WrapErrChannelMisrouted(sd.vchannelName, fmt.Sprintf("request channels %v", req.GetDmlChannels())) } req.Req.GuaranteeTimestamp = sd.speedupGuranteeTS( @@ -802,7 +802,7 @@ func (sd *shardDelegator) GetStatistics(ctx context.Context, req *querypb.GetSta log.Warn("delegator received GetStatistics request not belongs to it", zap.Strings("reqChannels", req.GetDmlChannels()), ) - return nil, fmt.Errorf("dml channel not match, delegator channel %s, GetStatistics channels %v", sd.vchannelName, req.GetDmlChannels()) + return nil, merr.WrapErrChannelMisrouted(sd.vchannelName, fmt.Sprintf("GetStatistics channels %v", req.GetDmlChannels())) } // wait tsafe @@ -952,11 +952,11 @@ func executeSubTasks[T any, R interface { } else { segments = []int64{} } - err = fmt.Errorf("segments not loaded in any worker: %v", segments[:min(len(segments), 10)]) + err = merr.WrapErrServiceInternalMsg("segments not loaded in any worker: %v", segments[:min(len(segments), 10)]) } else { result, err = execute(ctx, task.req, task.worker) if result.GetStatus().GetErrorCode() != commonpb.ErrorCode_Success { - err = fmt.Errorf("worker(%d) query failed: %s", task.targetID, result.GetStatus().GetReason()) + err = merr.Wrapf(merr.Error(result.GetStatus()), "worker(%d) query failed", task.targetID) } } @@ -1408,7 +1408,7 @@ func NewShardDelegator(ctx context.Context, collectionID UniqueID, replicaID Uni collection := manager.Collection.Get(collectionID) if collection == nil { - return nil, fmt.Errorf("collection(%d) not found in manager", collectionID) + return nil, merr.WrapErrCollectionNotFound(collectionID, "not in delegator manager") } sizePerBlock := paramtable.Get().QueryNodeCfg.DeleteBufferBlockSize.GetAsInt64() @@ -1547,7 +1547,7 @@ func (sd *shardDelegator) RunAnalyzer(ctx context.Context, req *querypb.RunAnaly analyzerNames := req.GetAnalyzerNames() if len(analyzerNames) == 0 { - return merr.WrapErrAsInputError(fmt.Errorf("analyzer names must be set for multi analyzer")) + return merr.WrapErrParameterMissingMsg("analyzer names must be set for multi analyzer") } if len(analyzerNames) == 1 && len(texts) > 1 { @@ -1563,7 +1563,7 @@ func (sd *shardDelegator) RunAnalyzer(ctx context.Context, req *querypb.RunAnaly return nil, err } if !ok { - return nil, fmt.Errorf("analyzer runner for field %d not exist, now only support run analyzer by field if field was bm25/minhash input field", req.GetFieldId()) + return nil, merr.WrapErrParameterInvalidMsg("analyzer runner for field %d not exist, now only support run analyzer by field if field was bm25/minhash input field", req.GetFieldId()) } return lo.Map(result, func(tokens []*milvuspb.AnalyzerToken, _ int) *milvuspb.AnalyzerResult { diff --git a/internal/querynodev2/delegator/delegator_data.go b/internal/querynodev2/delegator/delegator_data.go index 2f98eabab4..a0322192e0 100644 --- a/internal/querynodev2/delegator/delegator_data.go +++ b/internal/querynodev2/delegator/delegator_data.go @@ -1201,7 +1201,7 @@ func (sd *shardDelegator) buildBM25IDF(ctx context.Context, req *internalpb.Sear pb := &commonpb.PlaceholderGroup{} if err := proto.Unmarshal(req.GetPlaceholderGroup(), pb); err != nil { - return 0, merr.WrapErrServiceInternal("failed to unmarshal BM25 IDF placeholder group", err.Error()) + return 0, merr.WrapErrParameterInvalidMsg("failed to unmarshal BM25 IDF placeholder group: %v", err) } if len(pb.Placeholders) != 1 || len(pb.Placeholders[0].Values) == 0 { @@ -1210,7 +1210,7 @@ func (sd *shardDelegator) buildBM25IDF(ctx context.Context, req *internalpb.Sear holder := pb.Placeholders[0] if holder.Type != commonpb.PlaceholderType_VarChar { - return 0, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("please provide varchar/text for BM25 Function based search, got %s", holder.Type.String())) + return 0, merr.WrapErrParameterInvalidMsg("please provide varchar/text for BM25 Function based search, got %s", holder.Type.String()) } texts := funcutil.GetVarCharFromPlaceholder(holder) @@ -1218,7 +1218,7 @@ func (sd *shardDelegator) buildBM25IDF(ctx context.Context, req *internalpb.Sear schemaVersion := sd.collection.Schema().GetVersion() ok, err := function.RunWithRunner(ctx, sd.collectionID, schemaVersion, req.GetFieldId(), func(functionType schemapb.FunctionType, functionRunner function.FunctionRunner) error { if functionType != schemapb.FunctionType_BM25 { - return fmt.Errorf("functionRunner not found for field: %d", req.GetFieldId()) + return merr.WrapErrServiceInternalMsg("functionRunner not found for field: %d", req.GetFieldId()) } datas := []any{texts} @@ -1242,13 +1242,13 @@ func (sd *shardDelegator) buildBM25IDF(ctx context.Context, req *internalpb.Sear return err } if len(output) == 0 { - return errors.New("BM25 embedding failed: runner returned empty output") + return merr.WrapErrFunctionFailedMsg("BM25 embedding failed: runner returned empty output") } var ok bool tfArray, ok = output[0].(*schemapb.SparseFloatArray) if !ok { - return errors.New("functionRunner return unknown data") + return merr.WrapErrFunctionFailedMsg("functionRunner return unknown data") } return nil }) @@ -1256,7 +1256,9 @@ func (sd *shardDelegator) buildBM25IDF(ctx context.Context, req *internalpb.Sear return 0, err } if !ok { - return 0, fmt.Errorf("functionRunner not found for field: %d", req.GetFieldId()) + // internal invariant: runners are populated with the schema, never by + // the request — classified system, keeps cross-replica failover + return 0, merr.WrapErrServiceInternalMsg("functionRunner not found for field: %d", req.GetFieldId()) } idfSparseVector, avgdl, err := idfOracle.BuildIDF(req.GetFieldId(), tfArray) @@ -1284,7 +1286,7 @@ func (sd *shardDelegator) buildBM25IDF(ctx context.Context, req *internalpb.Sear func (sd *shardDelegator) parseMinHash(ctx context.Context, req *internalpb.SearchRequest) error { pb := &commonpb.PlaceholderGroup{} if err := proto.Unmarshal(req.GetPlaceholderGroup(), pb); err != nil { - return merr.WrapErrServiceInternal("failed to unmarshal MinHash placeholder group", err.Error()) + return merr.WrapErrParameterInvalidMsg("failed to unmarshal MinHash placeholder group: %v", err) } if len(pb.Placeholders) != 1 || len(pb.Placeholders[0].Values) == 0 { @@ -1293,7 +1295,7 @@ func (sd *shardDelegator) parseMinHash(ctx context.Context, req *internalpb.Sear holder := pb.Placeholders[0] if holder.Type != commonpb.PlaceholderType_VarChar { - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("please provide varchar/text for MinHash Function based search, got %s", holder.Type.String())) + return merr.WrapErrParameterInvalidMsg("please provide varchar/text for MinHash Function based search, got %s", holder.Type.String()) } texts := funcutil.GetVarCharFromPlaceholder(holder) @@ -1301,7 +1303,7 @@ func (sd *shardDelegator) parseMinHash(ctx context.Context, req *internalpb.Sear schemaVersion := sd.collection.Schema().GetVersion() ok, err := function.RunWithRunner(ctx, sd.collectionID, schemaVersion, req.GetFieldId(), func(functionType schemapb.FunctionType, functionRunner function.FunctionRunner) error { if functionType != schemapb.FunctionType_MinHash { - return fmt.Errorf("functionRunner not found for field: %d", req.GetFieldId()) + return merr.WrapErrServiceInternalMsg("functionRunner not found for field: %d", req.GetFieldId()) } output, err := functionRunner.BatchRun(texts) @@ -1309,13 +1311,13 @@ func (sd *shardDelegator) parseMinHash(ctx context.Context, req *internalpb.Sear return err } if len(output) == 0 { - return errors.New("MinHash embedding failed: runner returned empty output") + return merr.WrapErrFunctionFailedMsg("MinHash embedding failed: runner returned empty output") } var ok bool fieldData, ok = output[0].(*schemapb.FieldData) if !ok { - return errors.New("MinHash embedding failed: MinHash functionRunner return unknown data") + return merr.WrapErrFunctionFailedMsg("MinHash embedding failed: MinHash functionRunner return unknown data") } return nil }) @@ -1323,17 +1325,17 @@ func (sd *shardDelegator) parseMinHash(ctx context.Context, req *internalpb.Sear return err } if !ok { - return fmt.Errorf("functionRunner not found for field: %d", req.GetFieldId()) + return merr.WrapErrServiceInternalMsg("functionRunner not found for field: %d", req.GetFieldId()) } vectorField := fieldData.GetVectors() if vectorField == nil { - return errors.New("MinHash embedding failed: output is not a vector field") + return merr.WrapErrFunctionFailedMsg("MinHash embedding failed: output is not a vector field") } binaryVector := vectorField.GetBinaryVector() if binaryVector == nil { - return errors.New("MinHash embedding failed: output is not a binary vector") + return merr.WrapErrFunctionFailedMsg("MinHash embedding failed: output is not a binary vector") } req.PlaceholderGroup, err = funcutil.FieldDataToPlaceholderGroupBytes(fieldData) @@ -1357,7 +1359,7 @@ func (sd *shardDelegator) GetHighlight(ctx context.Context, req *querypb.GetHigh result := []*querypb.HighlightResult{} for _, task := range req.GetTasks() { if len(task.GetTexts()) != int(task.GetSearchTextNum()+task.GetCorpusTextNum())+len(task.GetQueries()) { - return nil, errors.Errorf("package highlight texts error, num of texts not equal the expected num %d:%d", len(task.GetTexts()), int(task.GetSearchTextNum()+task.GetCorpusTextNum())+len(task.GetQueries())) + return nil, merr.WrapErrServiceInternalMsg("package highlight texts error, num of texts not equal the expected num %d:%d", len(task.GetTexts()), int(task.GetSearchTextNum()+task.GetCorpusTextNum())+len(task.GetQueries())) } topks := req.GetTopks() var results [][]*milvuspb.AnalyzerToken diff --git a/internal/querynodev2/delegator/delegator_twostage.go b/internal/querynodev2/delegator/delegator_twostage.go index f4e3d355ab..b759fa31a1 100644 --- a/internal/querynodev2/delegator/delegator_twostage.go +++ b/internal/querynodev2/delegator/delegator_twostage.go @@ -28,6 +28,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/metrics" "github.com/milvus-io/milvus/pkg/v3/proto/internalpb" "github.com/milvus-io/milvus/pkg/v3/proto/querypb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -69,7 +70,7 @@ func (sd *shardDelegator) executeFilterStage( for _, result := range filterResults { for _, vc := range result.GetFilterValidCounts() { if vc < 0 { - return nil, fmt.Errorf("filter stage returned negative valid_count %d, segment may not support filter-only search", vc) + return nil, merr.WrapErrServiceInternalMsg("filter stage returned negative valid_count %d, segment may not support filter-only search", vc) } validCounts = append(validCounts, vc) } diff --git a/internal/querynodev2/delegator/function_runtime_state.go b/internal/querynodev2/delegator/function_runtime_state.go index ac985b7c9e..2b127b1e78 100644 --- a/internal/querynodev2/delegator/function_runtime_state.go +++ b/internal/querynodev2/delegator/function_runtime_state.go @@ -17,11 +17,11 @@ package delegator import ( - "fmt" "sync" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/util/function" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -59,7 +59,7 @@ func functionRuntimeOutputFieldIDs(schema *schemapb.CollectionSchema, fn *schema outputFieldIDs := append([]int64(nil), outputIDs...) for _, outputFieldID := range outputFieldIDs { if typeutil.GetField(schema, outputFieldID) == nil { - return nil, fmt.Errorf("function %s output field %d not found", fn.GetName(), outputFieldID) + return nil, merr.WrapErrFunctionFailedMsg("function %s output field %d not found", fn.GetName(), outputFieldID) } } return outputFieldIDs, nil @@ -67,14 +67,14 @@ func functionRuntimeOutputFieldIDs(schema *schemapb.CollectionSchema, fn *schema outputNames := fn.GetOutputFieldNames() if len(outputNames) == 0 { - return nil, fmt.Errorf("function %s output fields not found", fn.GetName()) + return nil, merr.WrapErrFunctionFailedMsg("function %s output fields not found", fn.GetName()) } outputFieldIDs := make([]int64, 0, len(outputNames)) for _, outputName := range outputNames { field := typeutil.GetFieldByName(schema, outputName) if field == nil { - return nil, fmt.Errorf("function %s output field %s not found", fn.GetName(), outputName) + return nil, merr.WrapErrFunctionFailedMsg("function %s output field %s not found", fn.GetName(), outputName) } outputFieldIDs = append(outputFieldIDs, field.GetFieldID()) } diff --git a/internal/querynodev2/delegator/growing_flush_source.go b/internal/querynodev2/delegator/growing_flush_source.go index f6b2ea37f7..c3a3b9c721 100644 --- a/internal/querynodev2/delegator/growing_flush_source.go +++ b/internal/querynodev2/delegator/growing_flush_source.go @@ -178,7 +178,7 @@ func (p *delegatorGrowingSourceProvider) registerRetained(segmentID int64, targe currentOffset := p.currentOffset(segment) if currentOffset < targetOffset { segment.Unpin() - return errors.Errorf("growing-source segment %d is behind target offset, current=%d target=%d", segmentID, currentOffset, targetOffset) + return merr.WrapErrServiceInternalMsg("growing-source segment %d is behind target offset, current=%d target=%d", segmentID, currentOffset, targetOffset) } p.mu.Lock() diff --git a/internal/querynodev2/delegator/idf_oracle.go b/internal/querynodev2/delegator/idf_oracle.go index 0edb218e23..763bc89da8 100644 --- a/internal/querynodev2/delegator/idf_oracle.go +++ b/internal/querynodev2/delegator/idf_oracle.go @@ -34,7 +34,6 @@ import ( "sync" "time" - "github.com/cockroachdb/errors" "go.uber.org/atomic" "go.uber.org/zap" @@ -47,6 +46,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/datapb" "github.com/milvus-io/milvus/pkg/v3/proto/querypb" "github.com/milvus-io/milvus/pkg/v3/util/conc" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -154,7 +154,7 @@ func (s bm25Stats) Minus(stats bm25Stats) { func (s bm25Stats) GetStats(fieldID int64) (*storage.BM25Stats, error) { stats, ok := s[fieldID] if !ok { - return nil, errors.New("field not found in idf oracle BM25 stats") + return nil, merr.WrapErrFieldNotFound(fieldID, "not in idf oracle BM25 stats") } return stats, nil } @@ -235,7 +235,7 @@ func (s *sealedBm25Stats) FetchStats() (map[int64]*storage.BM25Stats, error) { defer s.RUnlock() if s.removed { - return nil, errors.Newf("sealed bm25 stats for segment %d already removed", s.segmentID) + return nil, merr.WrapErrServiceInternalMsg("sealed bm25 stats for segment %d already removed", s.segmentID) } stats := make(map[int64]*storage.BM25Stats) @@ -243,7 +243,7 @@ func (s *sealedBm25Stats) FetchStats() (map[int64]*storage.BM25Stats, error) { fieldDir := path.Join(s.localDir, fmt.Sprintf("%d", fieldID)) entries, err := os.ReadDir(fieldDir) if err != nil { - return nil, errors.Newf("read local dir %s failed: %v", fieldDir, err) + return nil, merr.WrapErrIoFailed(fieldDir, err) } fieldStats := storage.NewBM25Stats() @@ -254,12 +254,12 @@ func (s *sealedBm25Stats) FetchStats() (map[int64]*storage.BM25Stats, error) { filePath := path.Join(fieldDir, entry.Name()) f, err := os.Open(filePath) if err != nil { - return nil, errors.Newf("open local file %s failed: %v", filePath, err) + return nil, merr.WrapErrIoFailed(filePath, err) } err = fieldStats.DeserializeFromReader(bufio.NewReader(f)) f.Close() if err != nil { - return nil, errors.Newf("deserialize local file %s failed: %v", filePath, err) + return nil, merr.WrapErrSerializationFailed(err, "deserialize local file %s", filePath) } } stats[fieldID] = fieldStats @@ -388,7 +388,7 @@ func (o *idfOracle) activateExistingSealedStats(segmentID int64, stats bm25Stats segStats.Lock() defer segStats.Unlock() if segStats.removed { - return false, errors.Newf("sealed bm25 stats for segment %d already removed", segmentID) + return false, merr.WrapErrServiceInternalMsg("sealed bm25 stats for segment %d already removed", segmentID) } return o.activateSealedStatsLocked(segStats, stats), nil } @@ -546,7 +546,7 @@ func (o *idfOracle) LoadSealedForReopen(ctx context.Context, segmentID int64, lo if segStats.removed { segStats.Unlock() o.Unlock() - return nil, errors.Newf("sealed bm25 stats for segment %d already removed", segmentID) + return nil, merr.WrapErrServiceInternalMsg("sealed bm25 stats for segment %d already removed", segmentID) } wasActive := segStats.activate.Load() @@ -643,7 +643,7 @@ func (o *idfOracle) streamLoad(ctx context.Context, segmentID int64, binlogPaths localFile := path.Join(fieldDir, fmt.Sprintf("%d.data", i)) written, err := streamOneFile(ctx, cm, remotePath, localFile, fieldStats) if err != nil { - return streamLoadResult{}, errors.Wrapf(err, "stream bm25 stats file %s", remotePath) + return streamLoadResult{}, merr.Wrapf(err, "stream bm25 stats file %s", remotePath) } totalDiskSize += written } @@ -942,7 +942,7 @@ func (o *idfOracle) SyncDistribution() error { if intarget && !activate { stats, err := stats.FetchStats() if err != nil { - rangeErr = fmt.Errorf("fetch stats failed with error: %v", err) + rangeErr = merr.Wrap(err, "fetch stats failed") return false } activateStats[segmentID] = stats @@ -951,7 +951,7 @@ func (o *idfOracle) SyncDistribution() error { if !intarget && activate { stats, err := stats.FetchStats() if err != nil { - rangeErr = fmt.Errorf("fetch stats failed with error: %v", err) + rangeErr = merr.Wrap(err, "fetch stats failed") return false } deactivateStats[segmentID] = stats diff --git a/internal/querynodev2/delegator/types.go b/internal/querynodev2/delegator/types.go index c62d672cd3..a0a843cc76 100644 --- a/internal/querynodev2/delegator/types.go +++ b/internal/querynodev2/delegator/types.go @@ -50,8 +50,6 @@ type TSafeUpdater interface { } // ErrTsLagTooLarge serviceable and guarantee lag too large. -var ErrTsLagTooLarge = merr.ErrChannelTSafeStalled - // WrapErrTsLagTooLarge wraps ErrTsLagTooLarge with lag and max value. func WrapErrTsLagTooLarge(channel string, duration time.Duration, maxLag time.Duration) error { return merr.WrapErrChannelTSafeStalled(channel, fmt.Sprintf("lag(%s) max(%s)", duration, maxLag)) diff --git a/internal/querynodev2/delegator/util.go b/internal/querynodev2/delegator/util.go index fbccfd6cf9..7c6a714f1c 100644 --- a/internal/querynodev2/delegator/util.go +++ b/internal/querynodev2/delegator/util.go @@ -5,7 +5,6 @@ import ( "sort" "unicode/utf8" - "github.com/cockroachdb/errors" "go.uber.org/zap" "google.golang.org/protobuf/proto" @@ -109,11 +108,11 @@ func bytesOffsetToRuneOffset(text string, spans SpanList) error { for i, span := range spans { startOffset, ok := offsetMap[span[0]] if !ok { - return errors.Errorf("start offset: %d not found (text: %d bytes)", span[0], len(text)) + return merr.WrapErrServiceInternalMsg("start offset: %d not found (text: %d bytes)", span[0], len(text)) } endOffset, ok := offsetMap[span[1]] if !ok { - return errors.Errorf("end offset: %d not found (text: %d bytes)", span[1], len(text)) + return merr.WrapErrServiceInternalMsg("end offset: %d not found (text: %d bytes)", span[1], len(text)) } spans[i][0] = startOffset spans[i][1] = endOffset diff --git a/internal/querynodev2/handlers.go b/internal/querynodev2/handlers.go index b6bc2c1a58..cd2c5345eb 100644 --- a/internal/querynodev2/handlers.go +++ b/internal/querynodev2/handlers.go @@ -556,7 +556,9 @@ func reduceStatisticResponse(results []*internalpb.GetStatisticsResponse) (*inte for _, pair := range partialResult.Stats { fn, ok := fieldMethod[pair.Key] if !ok { - return nil, fmt.Errorf("unknown statistic field: %s", pair.Key) + // Stats keys are produced by query nodes; an unrecognized key is an + // internal protocol mismatch (e.g. mixed-version upgrade), not input. + return nil, merr.WrapErrServiceInternalMsg("unknown statistic field: %s", pair.Key) } if err := fn(pair.Value); err != nil { return nil, err diff --git a/internal/querynodev2/pipeline/insert_node.go b/internal/querynodev2/pipeline/insert_node.go index e9cf9463c8..8e3369b379 100644 --- a/internal/querynodev2/pipeline/insert_node.go +++ b/internal/querynodev2/pipeline/insert_node.go @@ -32,6 +32,7 @@ import ( base "github.com/milvus-io/milvus/internal/util/pipeline" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/metrics" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -50,7 +51,7 @@ type insertNode struct { func (iNode *insertNode) addInsertData(insertDatas map[UniqueID]*delegator.InsertData, msg *InsertMsg, collection *Collection) { insertRecord, err := storage.TransferInsertMsgToInsertRecord(collection.Schema(), msg) if err != nil { - err = fmt.Errorf("failed to get primary keys, err = %v", err) + err = merr.Wrap(err, "failed to get primary keys") log.Error(err.Error(), zap.Int64("collectionID", iNode.collectionID), zap.String("channel", iNode.channel)) panic(err) } @@ -231,7 +232,7 @@ func (iNode *insertNode) appendBM25Stats(iData *delegator.InsertData, msg *Inser for _, outputFieldID := range outputFieldIDs { outputData := getFieldData(msg.FieldsData, outputFieldID) if outputData == nil { - return fmt.Errorf("BM25 output field %d not found in insert message", outputFieldID) + return merr.WrapErrFunctionFailedMsg("BM25 output field %d not found in insert message", outputFieldID) } if err := appendBM25StatsFromFieldData(iData.BM25Stats, outputFieldID, outputData); err != nil { return err @@ -249,7 +250,7 @@ func getBM25OutputFieldIDs(schema *schemapb.CollectionSchema) ([]int64, error) { outputField := typeutil.GetFunctionOutputField(schema, fn) if outputField == nil { - return nil, fmt.Errorf("function %s output field not found", fn.GetName()) + return nil, merr.WrapErrFunctionFailedMsg("function %s output field not found", fn.GetName()) } outputFieldIDs = append(outputFieldIDs, outputField.GetFieldID()) @@ -260,7 +261,7 @@ func getBM25OutputFieldIDs(schema *schemapb.CollectionSchema) ([]int64, error) { func appendBM25StatsFromFieldData(stats map[int64]*storage.BM25Stats, outputFieldID int64, fieldData *schemapb.FieldData) error { sparseArray := fieldData.GetVectors().GetSparseFloatVector() if sparseArray == nil { - return fmt.Errorf("BM25 output field %d is not sparse float vector data", outputFieldID) + return merr.WrapErrFunctionFailedMsg("BM25 output field %d is not sparse float vector data", outputFieldID) } if _, ok := stats[outputFieldID]; !ok { stats[outputFieldID] = storage.NewBM25Stats() diff --git a/internal/querynodev2/segments/ignore_non_pk_ops.go b/internal/querynodev2/segments/ignore_non_pk_ops.go index 8c111b83b5..a899c707aa 100644 --- a/internal/querynodev2/segments/ignore_non_pk_ops.go +++ b/internal/querynodev2/segments/ignore_non_pk_ops.go @@ -18,7 +18,6 @@ package segments import ( "context" - "fmt" "github.com/samber/lo" "go.opentelemetry.io/otel/trace" @@ -31,6 +30,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/segcorepb" "github.com/milvus-io/milvus/pkg/v3/util/conc" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -71,7 +71,7 @@ func NewMergeByPKWithOffsetsOperator( for _, r := range results { tr, err := NewTimestampedRetrieveResult(r) if err != nil { - return nil, fmt.Errorf("failed to create timestamped result: %w", err) + return nil, merr.Wrap(err, "failed to create timestamped result") } validResults = append(validResults, tr) } @@ -85,12 +85,12 @@ func NewMergeByPKWithOffsetsOperator( if isElementLevel { for i, r := range validResults { if r.Result.GetElementLevel() != isElementLevel { - return nil, fmt.Errorf("inconsistent element-level flag: result[0]=%v, result[%d]=%v", + return nil, merr.WrapErrServiceInternalMsg("inconsistent element-level flag: result[0]=%v, result[%d]=%v", isElementLevel, i, r.Result.GetElementLevel()) } size := typeutil.GetSizeOfIDs(r.GetIds()) if len(r.Result.GetElementIndices()) != size { - return nil, fmt.Errorf("element_indices length (%d) does not match ids length (%d)", + return nil, merr.WrapErrServiceInternalMsg("element_indices length (%d) does not match ids length (%d)", len(r.Result.GetElementIndices()), size) } } @@ -277,7 +277,7 @@ func NewFetchFieldsDataOperator( segmentResOffset[sel.SegmentIndex]++ if retSize > maxOutputSize { - return nil, fmt.Errorf("query results exceed the maxOutputSize Limit %d", maxOutputSize) + return nil, merr.WrapErrParameterInvalidMsg("query results exceed the maxOutputSize Limit %d", maxOutputSize) } } diff --git a/internal/querynodev2/segments/index_attr_cache.go b/internal/querynodev2/segments/index_attr_cache.go index 80eb13f630..2d4527d3b5 100644 --- a/internal/querynodev2/segments/index_attr_cache.go +++ b/internal/querynodev2/segments/index_attr_cache.go @@ -27,8 +27,6 @@ import ( "fmt" "unsafe" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus/internal/util/indexparamcheck" "github.com/milvus-io/milvus/internal/util/vecindexmgr" "github.com/milvus-io/milvus/pkg/v3/common" @@ -36,6 +34,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/querypb" "github.com/milvus-io/milvus/pkg/v3/util/conc" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -61,7 +60,7 @@ func NewIndexAttrCache() *IndexAttrCache { func (c *IndexAttrCache) GetIndexResourceUsage(indexInfo *querypb.FieldIndexInfo, memoryIndexLoadPredictMemoryUsageFactor float64, fieldBinlog *datapb.FieldBinlog) (memory uint64, disk uint64, err error) { indexType, err := funcutil.GetAttrByKeyFromRepeatedKV(common.IndexTypeKey, indexInfo.IndexParams) if err != nil { - return 0, 0, errors.New("index type not exist in index params") + return 0, 0, merr.WrapErrParameterInvalidMsg("index type not exist in index params") } if vecindexmgr.GetVecIndexMgrInstance().IsDiskANN(indexType) { neededMemSize := indexInfo.IndexSize / UsedDiskMemoryRatio diff --git a/internal/querynodev2/segments/query_pipeline.go b/internal/querynodev2/segments/query_pipeline.go index 468249086a..a28f79a5e2 100644 --- a/internal/querynodev2/segments/query_pipeline.go +++ b/internal/querynodev2/segments/query_pipeline.go @@ -18,7 +18,6 @@ package segments import ( "context" - "fmt" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/agg" @@ -31,6 +30,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/planpb" "github.com/milvus-io/milvus/pkg/v3/proto/querypb" "github.com/milvus-io/milvus/pkg/v3/proto/segcorepb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -77,7 +77,7 @@ func RunQNQueryPipeline( // Common: run pipeline finalMsg, err := pipeline.Run(ctx, nil, msg) if err != nil { - return nil, fmt.Errorf("QN query pipeline failed: %w", err) + return nil, merr.Wrap(err, "QN query pipeline failed") } // Common: extract output + aggregate stats + fill empty fields @@ -138,7 +138,7 @@ func buildStandardQNPipeline( req.GetReq().GetOrderByFields(), schema, ) if err != nil { - return nil, nil, fmt.Errorf("failed to convert ORDER BY fields: %w", err) + return nil, nil, merr.Wrap(err, "failed to convert ORDER BY fields") } // Convert segcore results to internal format. @@ -220,7 +220,7 @@ func extractSegcoreResult( } } default: - return nil, fmt.Errorf("unexpected pipeline output type: %T", rawOutput) + return nil, merr.WrapErrServiceInternalMsg("unexpected pipeline output type: %T", rawOutput) } merged := &segcorepb.RetrieveResults{ @@ -263,7 +263,7 @@ func RunDelegatorQueryPipeline( req.GetReq().GetOrderByFields(), schema, ) if err != nil { - return nil, fmt.Errorf("failed to convert ORDER BY fields: %w", err) + return nil, merr.Wrap(err, "failed to convert ORDER BY fields") } // Build and run pipeline @@ -277,13 +277,13 @@ func RunDelegatorQueryPipeline( maxOutputSize, ) if err != nil { - return nil, fmt.Errorf("failed to build delegator query pipeline: %w", err) + return nil, merr.Wrap(err, "failed to build delegator query pipeline") } msg := queryutil.OpMsg{queryutil.PipelineInput: results} finalMsg, err := pipeline.Run(ctx, nil, msg) if err != nil { - return nil, fmt.Errorf("delegator query pipeline failed: %w", err) + return nil, merr.Wrap(err, "delegator query pipeline failed") } output := finalMsg[queryutil.PipelineOutput].(*internalpb.RetrieveResults) diff --git a/internal/querynodev2/segments/search_reduce.go b/internal/querynodev2/segments/search_reduce.go index ee71b7df88..e0ab820bed 100644 --- a/internal/querynodev2/segments/search_reduce.go +++ b/internal/querynodev2/segments/search_reduce.go @@ -2,7 +2,6 @@ package segments import ( "context" - "fmt" "go.opentelemetry.io/otel" "go.uber.org/zap" @@ -30,7 +29,7 @@ func getSearchResultDedupKey(data *schemapb.SearchResultData, idx int64, pk inte } elementIndices := data.GetElementIndices() if elementIndices == nil || idx < 0 || idx >= int64(len(elementIndices.GetData())) { - return nil, fmt.Errorf("element-level search result missing element index at offset %d", idx) + return nil, merr.WrapErrServiceInternalMsg("element-level search result missing element index at offset %d", idx) } return elementSearchResultKey{ pk: pk, @@ -93,7 +92,7 @@ func (scr *SearchCommonReduce) ReduceSearchResultData(ctx context.Context, searc if ids == nil || typeutil.GetSizeOfIDs(ids) == 0 { data.ElementIndices = &schemapb.LongArray{} } else { - return nil, fmt.Errorf("inconsistent element-level flag in search results: result has data but missing ElementIndices at index %d", i) + return nil, merr.WrapErrServiceInternalMsg("inconsistent element-level flag in search results: result has data but missing ElementIndices at index %d", i) } } } @@ -106,7 +105,7 @@ func (scr *SearchCommonReduce) ReduceSearchResultData(ctx context.Context, searc totalOffsetElements := 0 for i, data := range searchResultData { if int64(len(data.Topks)) < nq { - return nil, fmt.Errorf("invalid search result topks length at index %d: got %d, expected at least %d", i, len(data.Topks), nq) + return nil, merr.WrapErrServiceInternalMsg("invalid search result topks length at index %d: got %d, expected at least %d", i, len(data.Topks), nq) } totalOffsetElements += len(data.Topks) } @@ -177,7 +176,7 @@ func (scr *SearchCommonReduce) ReduceSearchResultData(ctx context.Context, searc // limit search result to avoid oom if retSize > maxOutputSize { - return nil, fmt.Errorf("search results exceed the maxOutputSize Limit %d", maxOutputSize) + return nil, merr.WrapErrParameterInvalidMsg("search results exceed the maxOutputSize Limit %d", maxOutputSize) } } log.Debug("skip duplicated search result", zap.Int64("count", skipDupCnt)) @@ -231,7 +230,7 @@ func (sbr *SearchGroupByReduce) ReduceSearchResultData(ctx context.Context, sear if ids == nil || typeutil.GetSizeOfIDs(ids) == 0 { data.ElementIndices = &schemapb.LongArray{} } else { - return nil, fmt.Errorf("inconsistent element-level flag in search results: result has data but missing ElementIndices at index %d", i) + return nil, merr.WrapErrServiceInternalMsg("inconsistent element-level flag in search results: result has data but missing ElementIndices at index %d", i) } } } @@ -244,7 +243,7 @@ func (sbr *SearchGroupByReduce) ReduceSearchResultData(ctx context.Context, sear totalOffsetElements := 0 for i, data := range searchResultData { if int64(len(data.Topks)) < nq { - return nil, fmt.Errorf("invalid search result topks length at index %d: got %d, expected at least %d", i, len(data.Topks), nq) + return nil, merr.WrapErrServiceInternalMsg("invalid search result topks length at index %d: got %d, expected at least %d", i, len(data.Topks), nq) } totalOffsetElements += len(data.Topks) } @@ -309,11 +308,11 @@ func (sbr *SearchGroupByReduce) ReduceSearchResultData(ctx context.Context, sear ret.Topks = append(ret.Topks, j) if retSize > maxOutputSize { - return nil, fmt.Errorf("search results exceed the maxOutputSize Limit %d", maxOutputSize) + return nil, merr.WrapErrParameterInvalidMsg("search results exceed the maxOutputSize Limit %d", maxOutputSize) } } if err := writeGroupByOutput(ret, acceptedRows, searchResultData, info); err != nil { - return ret, merr.WrapErrServiceInternal("failed to construct group by output", err.Error()) + return ret, merr.Wrap(err, "failed to construct group by output") } if float64(filteredCount) >= 0.3*float64(groupBound) { log.Warn("GroupBy reduce filtered too many results, "+ diff --git a/internal/querynodev2/segments/segment.go b/internal/querynodev2/segments/segment.go index ee5dd89ad6..69fa594df3 100644 --- a/internal/querynodev2/segments/segment.go +++ b/internal/querynodev2/segments/segment.go @@ -424,7 +424,7 @@ func NewSegment(ctx context.Context, case SegmentTypeGrowing: locker = state.NewLoadStateLock(state.LoadStateDataLoaded) default: - return nil, fmt.Errorf("illegal segment type %d when create segment %d", segmentType, loadInfo.GetSegmentID()) + return nil, merr.WrapErrServiceInternalMsg("illegal segment type %d when create segment %d", segmentType, loadInfo.GetSegmentID()) } logger := log.With( @@ -875,7 +875,7 @@ func (s *LocalSegment) RetrieveByOffsets(ctx context.Context, plan *segcore.Retr func (s *LocalSegment) Insert(ctx context.Context, rowIDs []int64, timestamps []typeutil.Timestamp, record *segcorepb.InsertRecord) error { if s.Type() != SegmentTypeGrowing { - return fmt.Errorf("unexpected segmentType when segmentInsert, segmentType = %s", s.segmentType.String()) + return merr.WrapErrServiceInternalMsg("unexpected segmentType when segmentInsert, segmentType = %s", s.segmentType.String()) } if !s.ptrLock.PinIf(state.IsNotReleased) { return merr.WrapErrSegmentNotLoaded(s.ID(), "segment released") @@ -1247,8 +1247,7 @@ func (s *LocalSegment) innerLoadIndex(ctx context.Context, return err } if s.Type() != SegmentTypeSealed { - errMsg := fmt.Sprintln("updateSegmentIndex failed, illegal segment type ", s.segmentType, "segmentID = ", s.ID()) - return errors.New(errMsg) + return merr.WrapErrServiceInternalMsg("updateSegmentIndex failed, illegal segment type %s, segmentID = %d", s.segmentType, s.ID()) } appendLoadIndexInfoSpan := tr.RecordSpan() @@ -1648,12 +1647,12 @@ func (s *LocalSegment) GetFieldJSONIndexStats() map[int64]*querypb.JsonStatsInfo func (s *LocalSegment) FlushData(ctx context.Context, startOffset, endOffset int64, config *FlushConfig) (*FlushResult, error) { // currently only growing segments support FlushData if s.Type() != SegmentTypeGrowing { - return nil, errors.Errorf("FlushData is only supported for growing segments, got %s", s.Type().String()) + return nil, merr.WrapErrServiceInternalMsg("FlushData is only supported for growing segments, got %s", s.Type().String()) } // validate offsets if startOffset < 0 || endOffset < startOffset { - return nil, errors.Errorf("invalid offsets: start=%d, end=%d", startOffset, endOffset) + return nil, merr.WrapErrServiceInternalMsg("invalid offsets: start=%d, end=%d", startOffset, endOffset) } // no data to flush @@ -1706,7 +1705,7 @@ func (s *LocalSegment) FlushData(ctx context.Context, startOffset, endOffset int numBM25Fields := len(config.BM25FieldIDs) if numBM25Fields > 0 { if len(config.BM25StatsLogIDs) != numBM25Fields { - return nil, errors.Errorf("BM25 stats log IDs count mismatch, fields=%d logIDs=%d", numBM25Fields, len(config.BM25StatsLogIDs)) + return nil, merr.WrapErrServiceInternalMsg("BM25 stats log IDs count mismatch, fields=%d logIDs=%d", numBM25Fields, len(config.BM25StatsLogIDs)) } cBM25FieldIDs := (*C.int64_t)(C.malloc(C.size_t(numBM25Fields) * C.size_t(unsafe.Sizeof(C.int64_t(0))))) defer C.free(unsafe.Pointer(cBM25FieldIDs)) diff --git a/internal/querynodev2/segments/segment_loader.go b/internal/querynodev2/segments/segment_loader.go index 5978f328dd..04c0c8ea10 100644 --- a/internal/querynodev2/segments/segment_loader.go +++ b/internal/querynodev2/segments/segment_loader.go @@ -353,13 +353,13 @@ func (loader *segmentLoader) Load(ctx context.Context, if loadInfo.GetLevel() != datapb.SegmentLevel_L0 { // lazy load segment do not load segment at first time. if err = loader.LoadSegment(ctx, segment, loadInfo); err != nil { - return errors.Wrap(err, "At LoadSegment") + return merr.Wrap(err, "At LoadSegment") } } // Skip delta logs for external collections (they are read-only, no deletions) if !typeutil.IsExternalCollection(collection.Schema()) { if err = loader.loadDeltalogs(ctx, segment, loadInfo); err != nil { - return errors.Wrap(err, "At LoadDeltaLogs") + return merr.Wrap(err, "At LoadDeltaLogs") } } @@ -387,7 +387,7 @@ func (loader *segmentLoader) Load(ctx context.Context, } else if paramtable.Get().CommonCfg.BloomFilterEnabled.GetAsBool() { bfs, err := loader.loadSingleBloomFilterSet(ctx, loadInfo.GetCollectionID(), loadInfo, segment.Type()) if err != nil { - return errors.Wrap(err, "At LoadBloomFilter") + return merr.Wrap(err, "At LoadBloomFilter") } segment.SetPKCandidate(bfs) // Charge bloom filter resource @@ -514,7 +514,7 @@ func (loader *segmentLoader) requestResource(ctx context.Context, infos ...*quer physicalDiskUsage, err := loader.duf.GetDiskUsage() if err != nil { - return requestResourceResult{}, errors.Wrap(err, "get local used size failed") + return requestResourceResult{}, merr.Wrap(err, "get local used size failed") } diskCap := paramtable.Get().QueryNodeCfg.DiskCapacityLimit.GetAsUint64() @@ -597,7 +597,7 @@ func (loader *segmentLoader) waitSegmentLoadDone(ctx context.Context, segmentTyp result, ok := loader.loadingSegments.Get(segmentID) if !ok { log.Warn("segment was removed from the loading map early", zap.Int64("segmentID", segmentID)) - return errors.New("segment was removed from the loading map early") + return merr.WrapErrServiceInternalMsg("segment was removed from the loading map early") } log.Info("wait segment loaded...", zap.Int64("segmentID", segmentID)) @@ -748,8 +748,9 @@ func (loader *segmentLoader) LoadBloomFilterSet(ctx context.Context, collectionI memory_bytes: C.int64_t(totalMemorySize * 2), disk_bytes: C.int64_t(0), }, 1000); !ok { - return nil, fmt.Errorf("failed to reserve loading resource for bloom filters, totalMemorySize = %v MB", - logutil.ToMB(float64(totalMemorySize))) + return nil, merr.WrapErrSegmentRequestResourceFailed("memory", + fmt.Sprintf("failed to reserve loading resource for bloom filters, totalMemorySize = %v MB", + logutil.ToMB(float64(totalMemorySize)))) } log.Info("reserved loading resource for bloom filters", zap.Float64("totalMemorySizeMB", logutil.ToMB(float64(totalMemorySize)))) } @@ -1061,7 +1062,7 @@ func (loader *segmentLoader) loadSealedSegment(ctx context.Context, loadInfo *qu ) _, err = GetLoadPool().Submit(func() (any, error) { if err = segment.Load(ctx); err != nil { - return struct{}{}, errors.Wrap(err, "At Load") + return struct{}{}, merr.Wrap(err, "At Load") } return struct{}{}, nil @@ -1516,7 +1517,7 @@ func (loader *segmentLoader) patchEntryNumber(ctx context.Context, segment *Loca }) if rowIDField == nil { - return errors.New("rowID field binlog not found") + return merr.WrapErrDataIntegrityMsg("rowID field binlog not found") } counts := make([]int64, 0, len(rowIDField.GetBinlogs())) @@ -1549,7 +1550,7 @@ func (loader *segmentLoader) patchEntryNumber(ctx context.Context, segment *Loca var err error segment.fieldIndexes.Range(func(indexID int64, info *IndexedFieldInfo) bool { if len(info.FieldBinlog.GetBinlogs()) != len(counts) { - err = errors.New("rowID & index binlog number not matched") + err = merr.WrapErrDataIntegrityMsg("rowID & index binlog number not matched") return false } for i, binlog := range info.FieldBinlog.GetBinlogs() { @@ -1671,7 +1672,7 @@ func (loader *segmentLoader) checkSegmentSize(ctx context.Context, segmentLoadIn memUsage = memUsage + loader.committedResource.MemorySize if memUsage == 0 || totalMem == 0 { - return 0, 0, errors.New("get memory failed when checkSegmentSize") + return 0, 0, merr.WrapErrServiceInternalMsg("get memory failed when checkSegmentSize") } diskUsage := uint64(localDiskUsage) + loader.committedResource.DiskSize @@ -1737,14 +1738,15 @@ func (loader *segmentLoader) checkSegmentSize(ctx context.Context, segmentLoadIn memory_bytes: C.int64_t(predictMemUsage - memUsage), disk_bytes: C.int64_t(predictDiskUsage - diskUsage), }, 1000); !ok { - return 0, 0, fmt.Errorf("failed to reserve loading resource from caching layer, predictMemUsage = %v MB, predictDiskUsage = %v MB, memUsage = %v MB, diskUsage = %v MB, memoryThresholdFactor = %f, diskThresholdFactor = %f", - logutil.ToMB(float64(predictMemUsage)), - logutil.ToMB(float64(predictDiskUsage)), - logutil.ToMB(float64(memUsage)), - logutil.ToMB(float64(diskUsage)), - paramtable.Get().QueryNodeCfg.OverloadedMemoryThresholdPercentage.GetAsFloat(), - paramtable.Get().QueryNodeCfg.MaxDiskUsagePercentage.GetAsFloat(), - ) + return 0, 0, merr.WrapErrSegmentRequestResourceFailed("memory/disk", + fmt.Sprintf("failed to reserve loading resource from caching layer, predictMemUsage = %v MB, predictDiskUsage = %v MB, memUsage = %v MB, diskUsage = %v MB, memoryThresholdFactor = %f, diskThresholdFactor = %f", + logutil.ToMB(float64(predictMemUsage)), + logutil.ToMB(float64(predictDiskUsage)), + logutil.ToMB(float64(memUsage)), + logutil.ToMB(float64(diskUsage)), + paramtable.Get().QueryNodeCfg.OverloadedMemoryThresholdPercentage.GetAsFloat(), + paramtable.Get().QueryNodeCfg.MaxDiskUsagePercentage.GetAsFloat(), + )) } } else { // fallback to original segment loading logic @@ -1819,7 +1821,7 @@ func estimateLogicalResourceUsageOfSegment(schema *schemapb.CollectionSchema, lo return nil }) if err != nil { - return nil, errors.Wrapf(err, "failed to estimate logical resource usage of index, collection %d, segment %d, indexBuildID %d", + return nil, merr.Wrapf(err, "failed to estimate logical resource usage of index, collection %d, segment %d, indexBuildID %d", loadInfo.GetCollectionID(), loadInfo.GetSegmentID(), fieldIndexInfo.GetBuildID()) @@ -1843,7 +1845,7 @@ func estimateLogicalResourceUsageOfSegment(schema *schemapb.CollectionSchema, lo metricType, err := funcutil.GetAttrByKeyFromRepeatedKV(common.MetricTypeKey, fieldIndexInfo.IndexParams) if err != nil { - return nil, errors.Wrapf(err, "failed to estimate logical resource usage of index, metric type not found, collection %d, segment %d, indexBuildID %d", + return nil, merr.Wrapf(err, "failed to estimate logical resource usage of index, metric type not found, collection %d, segment %d, indexBuildID %d", loadInfo.GetCollectionID(), loadInfo.GetSegmentID(), fieldIndexInfo.GetBuildID()) @@ -2020,7 +2022,7 @@ func estimateLoadingResourceUsageOfSegment(schema *schemapb.CollectionSchema, lo return nil }) if err != nil { - return nil, errors.Wrapf(err, "failed to estimate loading resource usage of index, collection %d, segment %d, indexBuildID %d", + return nil, merr.Wrapf(err, "failed to estimate loading resource usage of index, collection %d, segment %d, indexBuildID %d", loadInfo.GetCollectionID(), loadInfo.GetSegmentID(), fieldIndexInfo.GetBuildID()) @@ -2051,7 +2053,7 @@ func estimateLoadingResourceUsageOfSegment(schema *schemapb.CollectionSchema, lo metricType, err := funcutil.GetAttrByKeyFromRepeatedKV(common.MetricTypeKey, fieldIndexInfo.IndexParams) if err != nil { - return nil, errors.Wrapf(err, "failed to estimate loading resource usage of index, metric type not found, collection %d, segment %d, indexBuildID %d", + return nil, merr.Wrapf(err, "failed to estimate loading resource usage of index, metric type not found, collection %d, segment %d, indexBuildID %d", loadInfo.GetCollectionID(), loadInfo.GetSegmentID(), fieldIndexInfo.GetBuildID()) diff --git a/internal/querynodev2/segments/state/load_state_lock.go b/internal/querynodev2/segments/state/load_state_lock.go index 1915980d81..63753f1ca2 100644 --- a/internal/querynodev2/segments/state/load_state_lock.go +++ b/internal/querynodev2/segments/state/load_state_lock.go @@ -5,11 +5,11 @@ import ( "sync" "time" - "github.com/cockroachdb/errors" "go.uber.org/atomic" "go.uber.org/zap" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -117,7 +117,7 @@ func (ls *LoadStateLock) StartLoadData() (LoadStateLockGuard, error) { return nil, nil } if ls.state != LoadStateOnlyMeta { - return nil, errors.New("segment is not in LoadStateOnlyMeta, cannot start to loading data") + return nil, merr.WrapErrServiceInternalMsg("segment is not in LoadStateOnlyMeta, cannot start to loading data") } ls.state = LoadStateDataLoading ls.cv.Broadcast() diff --git a/internal/querynodev2/segments/utils.go b/internal/querynodev2/segments/utils.go index e329157a7b..2e9cf701fe 100644 --- a/internal/querynodev2/segments/utils.go +++ b/internal/querynodev2/segments/utils.go @@ -3,11 +3,9 @@ package segments import ( "bytes" "encoding/binary" - "fmt" "io" "strconv" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" @@ -26,8 +24,6 @@ import ( "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) -var errLazyLoadTimeout = merr.WrapErrServiceInternal("lazy load time out") - func GetPkField(schema *schemapb.CollectionSchema) *schemapb.FieldSchema { for _, field := range schema.GetFields() { if field.GetIsPrimaryKey() { @@ -72,7 +68,7 @@ func getPKsFromRowBasedInsertMsg(msg *msgstream.InsertMsg, schema *schemapb.Coll if t.Key == common.DimKey { dim, err := strconv.Atoi(t.Value) if err != nil { - return nil, fmt.Errorf("strconv wrong on get dim, err = %s", err) + return nil, merr.WrapErrParameterInvalidMsg("strconv wrong on get dim, err = %s", err) } offset += dim * 4 break @@ -83,7 +79,7 @@ func getPKsFromRowBasedInsertMsg(msg *msgstream.InsertMsg, schema *schemapb.Coll if t.Key == common.DimKey { dim, err := strconv.Atoi(t.Value) if err != nil { - return nil, fmt.Errorf("strconv wrong on get dim, err = %s", err) + return nil, merr.WrapErrParameterInvalidMsg("strconv wrong on get dim, err = %s", err) } offset += dim / 8 break @@ -94,7 +90,7 @@ func getPKsFromRowBasedInsertMsg(msg *msgstream.InsertMsg, schema *schemapb.Coll if t.Key == common.DimKey { dim, err := strconv.Atoi(t.Value) if err != nil { - return nil, fmt.Errorf("strconv wrong on get dim, err = %s", err) + return nil, merr.WrapErrParameterInvalidMsg("strconv wrong on get dim, err = %s", err) } offset += dim * 2 break @@ -105,14 +101,14 @@ func getPKsFromRowBasedInsertMsg(msg *msgstream.InsertMsg, schema *schemapb.Coll if t.Key == common.DimKey { dim, err := strconv.Atoi(t.Value) if err != nil { - return nil, fmt.Errorf("strconv wrong on get dim, err = %s", err) + return nil, merr.WrapErrParameterInvalidMsg("strconv wrong on get dim, err = %s", err) } offset += dim * 2 break } } case schemapb.DataType_SparseFloatVector: - return nil, errors.New("SparseFloatVector not support in row based message") + return nil, merr.WrapErrParameterInvalidMsg("SparseFloatVector not support in row based message") } } @@ -253,7 +249,7 @@ func getFieldSchema(schema *schemapb.CollectionSchema, fieldID int64) (*schemapb } } } - return nil, fmt.Errorf("field %d not found in schema", fieldID) + return nil, merr.WrapErrFieldNotFound(fieldID, "not in schema") } func isIndexMmapEnable(fieldSchema *schemapb.FieldSchema, indexInfo *querypb.FieldIndexInfo) bool { diff --git a/internal/querynodev2/server.go b/internal/querynodev2/server.go index bd0733087b..882e7b86e6 100644 --- a/internal/querynodev2/server.go +++ b/internal/querynodev2/server.go @@ -38,7 +38,6 @@ import ( "sync" "time" - "github.com/cockroachdb/errors" "github.com/samber/lo" "github.com/tidwall/gjson" clientv3 "go.etcd.io/etcd/client/v3" @@ -77,6 +76,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/util/expr" "github.com/milvus-io/milvus/pkg/v3/util/lifetime" "github.com/milvus-io/milvus/pkg/v3/util/lock" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/metricsinfo" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" @@ -176,7 +176,7 @@ func (node *QueryNode) initSession() error { common.MaximumScalarIndexEngineVersion), sessionutil.WithIndexNonEncoding()) if node.session == nil { - return errors.New("session is nil, the etcd client connection may have failed") + return merr.WrapErrServiceNotReadyMsg("session is nil, the etcd client connection may have failed") } node.session.Init(typeutil.QueryNodeRole, node.address, false) sessionutil.SaveServerInfo(typeutil.QueryNodeRole, node.session.ServerID) @@ -586,7 +586,7 @@ func (node *QueryNode) SetAddress(address string) { func (node *QueryNode) initHook() error { path := paramtable.Get().QueryNodeCfg.SoPath.GetValue() if path == "" { - return errors.New("fail to set the plugin path") + return merr.WrapErrServiceInternalMsg("fail to set the plugin path") } hoo, err := hookutil.LoadPlugin[optimizers.QueryHook](path, "QueryNodePlugin") @@ -595,10 +595,10 @@ func (node *QueryNode) initHook() error { } if err = hoo.Init(paramtable.Get().AutoIndexConfig.AutoIndexSearchConfig.GetValue()); err != nil { - return fmt.Errorf("fail to init configs for the hook, error: %s", err.Error()) + return merr.Wrap(err, "fail to init configs for the hook") } if err = hoo.InitTuningConfig(paramtable.Get().AutoIndexConfig.AutoIndexTuningConfig.GetValue()); err != nil { - return fmt.Errorf("fail to init tuning configs for the hook, error: %s", err.Error()) + return merr.Wrap(err, "fail to init tuning configs for the hook") } node.queryHook = hoo diff --git a/internal/querynodev2/services.go b/internal/querynodev2/services.go index f216ae265a..b4fb59fe7b 100644 --- a/internal/querynodev2/services.go +++ b/internal/querynodev2/services.go @@ -899,7 +899,9 @@ func (node *QueryNode) Search(ctx context.Context, req *querypb.SearchRequest) ( } if len(req.GetDmlChannels()) != 1 { - err := merr.WrapErrParameterInvalid(1, len(req.GetDmlChannels()), "count of channel to be searched should only be 1, wrong code") + // internal protocol assertion: the proxy always targets exactly one + // channel per request, so a violation is a Milvus bug, not user input + err := merr.WrapErrServiceInternalMsg("count of channels to be searched should only be 1, got %d, wrong code", len(req.GetDmlChannels())) resp.Status = merr.Status(err) log.Warn("got wrong number of channels to be searched", zap.Error(err)) return resp, nil @@ -1042,8 +1044,10 @@ func (node *QueryNode) Query(ctx context.Context, req *querypb.QueryRequest) (*i node.manager.Collection.Unref(req.GetReq().GetCollectionID(), 1) }() if len(req.GetDmlChannels()) != 1 { + // internal protocol assertion: the proxy always targets exactly one + // channel per request, so a violation is a Milvus bug, not user input return &internalpb.RetrieveResults{ - Status: merr.Status(merr.WrapErrParameterInvalidMsg("query request to querynode should "+ + Status: merr.Status(merr.WrapErrServiceInternalMsg("query request to querynode should "+ "only target at one channel, but got:%d", len(req.GetDmlChannels()))), }, nil } diff --git a/internal/querynodev2/services_test.go b/internal/querynodev2/services_test.go index 002443d904..4702053036 100644 --- a/internal/querynodev2/services_test.go +++ b/internal/querynodev2/services_test.go @@ -1985,7 +1985,7 @@ func (suite *ServiceSuite) TestGetMetric_Failed() { req.Request = "---" resp, err = suite.node.GetMetrics(ctx, req) suite.NoError(err) - suite.Equal(commonpb.ErrorCode_UnexpectedError, resp.GetStatus().GetErrorCode()) + suite.Equal(commonpb.ErrorCode_IllegalArgument, resp.GetStatus().GetErrorCode()) // node unhealthy suite.node.UpdateStateCode(commonpb.StateCode_Abnormal) diff --git a/internal/querynodev2/tasks/arrow_import.go b/internal/querynodev2/tasks/arrow_import.go index 3f13c4126e..d10e96f39a 100644 --- a/internal/querynodev2/tasks/arrow_import.go +++ b/internal/querynodev2/tasks/arrow_import.go @@ -19,7 +19,6 @@ package tasks import ( - "fmt" "strconv" "github.com/apache/arrow/go/v17/arrow" @@ -46,12 +45,12 @@ func dataFrameFromArrowRecordBatch(rec arrow.Record, chunkSizes []int64) (*chain var totalRows int64 for _, size := range chunkSizes { if size < 0 { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("invalid negative Arrow chunk size: %d", size)) + return nil, merr.WrapErrServiceInternalMsg("invalid negative Arrow chunk size: %d", size) } totalRows += size } if totalRows != rec.NumRows() { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("Arrow record batch row count %d does not match chunk sizes total %d", rec.NumRows(), totalRows)) + return nil, merr.WrapErrServiceInternalMsg("Arrow record batch row count %d does not match chunk sizes total %d", rec.NumRows(), totalRows) } numCols := int(rec.NumCols()) @@ -84,13 +83,13 @@ func dataFrameFromArrowRecordBatch(rec arrow.Record, chunkSizes []int64) (*chain builder.SetFieldNullable(colName, nullable) if fieldID, ok, err := fieldMetadataInt64(field.Metadata, arrowMetadataFieldIDKey); err != nil { releaseChunks() - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("invalid Arrow field metadata %s for column %s: %v", arrowMetadataFieldIDKey, colName, err)) + return nil, merr.WrapErrServiceInternalMsg("invalid Arrow field metadata %s for column %s: %v", arrowMetadataFieldIDKey, colName, err) } else if ok { builder.SetFieldID(colName, fieldID) } if dataType, ok, err := fieldMetadataInt64(field.Metadata, arrowMetadataDataTypeKey); err != nil { releaseChunks() - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("invalid Arrow field metadata %s for column %s: %v", arrowMetadataDataTypeKey, colName, err)) + return nil, merr.WrapErrServiceInternalMsg("invalid Arrow field metadata %s for column %s: %v", arrowMetadataDataTypeKey, colName, err) } else if ok { builder.SetFieldType(colName, schemapb.DataType(dataType)) } diff --git a/internal/querynodev2/tasks/heap_merge_reduce.go b/internal/querynodev2/tasks/heap_merge_reduce.go index f2363a7278..a387fc46b6 100644 --- a/internal/querynodev2/tasks/heap_merge_reduce.go +++ b/internal/querynodev2/tasks/heap_merge_reduce.go @@ -287,7 +287,7 @@ func validateUniqueOutputColumns(outCols []string) error { seen := make(map[string]struct{}, len(outCols)) for _, name := range outCols { if _, ok := seen[name]; ok { - return merr.WrapErrServiceInternal(fmt.Sprintf("column %s already exists", name)) + return merr.WrapErrServiceInternalMsg("column %s already exists", name) } seen[name] = struct{}{} } @@ -823,7 +823,7 @@ func pickGroupByValues( return arr.(*array.String).Value(idx) }, array.NewStringBuilder), nil default: - return nil, fmt.Errorf("unsupported group-by arrow type %s at group index %d", firstArr.DataType(), groupIdx) + return nil, merr.WrapErrParameterInvalidMsg("unsupported group-by arrow type %s at group index %d", firstArr.DataType(), groupIdx) } } diff --git a/internal/querynodev2/tasks/search_task.go b/internal/querynodev2/tasks/search_task.go index f309c701ab..f26dbbbb06 100644 --- a/internal/querynodev2/tasks/search_task.go +++ b/internal/querynodev2/tasks/search_task.go @@ -194,7 +194,7 @@ func (t *SearchTask) Execute() error { // counts so the delegator can optimize search params for stage-2. if searchReq.FilterOnly() { if len(results) != len(searchedSegments) { - return fmt.Errorf("filter-only search: result count %d != segment count %d", len(results), len(searchedSegments)) + return merr.WrapErrServiceInternalMsg("filter-only search: result count %d != segment count %d", len(results), len(searchedSegments)) } segmentIDs := make([]int64, 0, len(searchedSegments)) validCounts := make([]int64, 0, len(searchedSegments)) diff --git a/internal/rootcoord/broker.go b/internal/rootcoord/broker.go index ec94e57ef5..375658f751 100644 --- a/internal/rootcoord/broker.go +++ b/internal/rootcoord/broker.go @@ -18,9 +18,7 @@ package rootcoord import ( "context" - "fmt" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" @@ -84,7 +82,7 @@ func (b *ServerBroker) ReleaseCollection(ctx context.Context, collectionID Uniqu } if resp.GetErrorCode() != commonpb.ErrorCode_Success { - return fmt.Errorf("failed to release collection, code: %s, reason: %s", resp.GetErrorCode(), resp.GetReason()) + return merr.Error(resp) } log.Ctx(ctx).Info("done to release collection", zap.Int64("collection", collectionID)) @@ -107,7 +105,7 @@ func (b *ServerBroker) ReleasePartitions(ctx context.Context, collectionID Uniqu } if resp.GetErrorCode() != commonpb.ErrorCode_Success { - return fmt.Errorf("release partition failed, reason: %s", resp.GetReason()) + return merr.Error(resp) } log.Info("release partitions done") @@ -127,7 +125,7 @@ func (b *ServerBroker) SyncNewCreatedPartition(ctx context.Context, collectionID } if resp.GetErrorCode() != commonpb.ErrorCode_Success { - return fmt.Errorf("sync new partition failed, reason: %s", resp.GetReason()) + return merr.Error(resp) } log.Info("sync new partition done") @@ -182,7 +180,7 @@ func (b *ServerBroker) WatchChannels(ctx context.Context, info *watchInfo) error } if resp.GetStatus().GetErrorCode() != commonpb.ErrorCode_Success { - return fmt.Errorf("failed to watch channels, code: %s, reason: %s", resp.GetStatus().GetErrorCode(), resp.GetStatus().GetReason()) + return merr.Error(resp.GetStatus()) } log.Ctx(ctx).Info("done to watch channels", zap.Uint64("ts", info.ts), zap.Int64("collection", info.collectionID), zap.Strings("vChannels", info.vChannels)) @@ -211,7 +209,7 @@ func (b *ServerBroker) DropCollectionIndex(ctx context.Context, collID UniqueID, return err } if rsp.ErrorCode != commonpb.ErrorCode_Success { - return fmt.Errorf("%s", rsp.Reason) + return merr.Error(rsp) } log.Ctx(ctx).Info("done to drop collection index", zap.Int64("collection", collID), zap.Int64s("partitions", partIDs)) @@ -268,7 +266,7 @@ func (b *ServerBroker) BroadcastAlteredCollection(ctx context.Context, collectio } if resp.ErrorCode != commonpb.ErrorCode_Success { - return errors.New(resp.Reason) + return merr.Error(resp) } log.Ctx(ctx).Info("done to broadcast request to alter collection", zap.String("collectionName", colMeta.Name), zap.Int64("collectionID", dcReq.GetCollectionID()), diff --git a/internal/rootcoord/create_collection_task.go b/internal/rootcoord/create_collection_task.go index 65ab912151..8304f55824 100644 --- a/internal/rootcoord/create_collection_task.go +++ b/internal/rootcoord/create_collection_task.go @@ -22,7 +22,6 @@ import ( "strconv" "time" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" @@ -68,7 +67,7 @@ func (t *createCollectionTask) releaseFileResources() { func (t *createCollectionTask) validate(ctx context.Context) error { if t.Req == nil { - return errors.New("empty requests") + return merr.WrapErrServiceInternalMsg("empty requests") } Params := paramtable.Get() @@ -81,12 +80,12 @@ func (t *createCollectionTask) validate(ctx context.Context) error { cfgMaxShardNum = Params.RootCoordCfg.DmlChannelNum.GetAsInt32() } if shardsNum > cfgMaxShardNum { - return fmt.Errorf("shard num (%d) exceeds max configuration (%d)", shardsNum, cfgMaxShardNum) + return merr.WrapErrParameterInvalidMsg("shard num (%d) exceeds max configuration (%d)", shardsNum, cfgMaxShardNum) } cfgShardLimit := Params.ProxyCfg.MaxShardNum.GetAsInt32() if shardsNum > cfgShardLimit { - return fmt.Errorf("shard num (%d) exceeds system limit (%d)", shardsNum, cfgShardLimit) + return merr.WrapErrParameterInvalidMsg("shard num (%d) exceeds system limit (%d)", shardsNum, cfgShardLimit) } // 2. check db-collection capacity @@ -145,7 +144,7 @@ func (t *createCollectionTask) checkMaxCollectionsPerDB(ctx context.Context, db2 if err != nil { log.Ctx(ctx).Warn("parse value of property fail", zap.String("key", common.DatabaseMaxCollectionsKey), zap.String("value", maxColNumPerDBStr), zap.Error(err)) - return fmt.Errorf("parse value of property fail, key:%s, value:%s", common.DatabaseMaxCollectionsKey, maxColNumPerDBStr) + return merr.WrapErrServiceInternalMsg("parse value of property fail, key:%s, value:%s", common.DatabaseMaxCollectionsKey, maxColNumPerDBStr) } return check(maxColNumPerDB) } @@ -308,7 +307,7 @@ func (t *createCollectionTask) assignFieldAndFunctionID(schema *schemapb.Collect for idx, name := range function.InputFieldNames { fieldId, ok := name2id[name] if !ok { - return fmt.Errorf("input field %s of function %s not found", name, function.GetName()) + return merr.WrapErrParameterInvalidMsg("input field %s of function %s not found", name, function.GetName()) } function.InputFieldIds[idx] = fieldId } @@ -317,7 +316,7 @@ func (t *createCollectionTask) assignFieldAndFunctionID(schema *schemapb.Collect for idx, name := range function.OutputFieldNames { fieldId, ok := name2id[name] if !ok { - return fmt.Errorf("output field %s of function %s not found", name, function.GetName()) + return merr.WrapErrParameterInvalidMsg("output field %s of function %s not found", name, function.GetName()) } function.OutputFieldIds[idx] = fieldId } @@ -444,7 +443,7 @@ func (t *createCollectionTask) prepareSchema(ctx context.Context) error { for _, field := range t.body.CollectionSchema.Fields { if field.Name != RowIDFieldName && field.GetFieldID() == 0 { log.Info("field id 0 is not allowed when preserve field ids", zap.String("field", field.Name)) - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("field id 0 is not allowed when preserve field ids, field: %s", field.Name)) + return merr.WrapErrParameterInvalidMsg("field id 0 is not allowed when preserve field ids, field: %s", field.Name) } if field.GetName() == MetaFieldName { @@ -525,12 +524,12 @@ func (t *createCollectionTask) assignPartitionIDs(ctx context.Context) error { partitionNums := t.Req.GetNumPartitions() // double check, default num of physical partitions should be greater than 0 if partitionNums <= 0 { - return errors.New("the specified partitions should be greater than 0 if partition key is used") + return merr.WrapErrParameterInvalidMsg("the specified partitions should be greater than 0 if partition key is used") } cfgMaxPartitionNum := Params.RootCoordCfg.MaxPartitionNum.GetAsInt64() if partitionNums > cfgMaxPartitionNum { - return fmt.Errorf("partition number (%d) exceeds max configuration (%d), collection: %s", + return merr.WrapErrParameterInvalidMsg("partition number (%d) exceeds max configuration (%d), collection: %s", partitionNums, cfgMaxPartitionNum, t.Req.CollectionName) } @@ -568,7 +567,8 @@ func (t *createCollectionTask) assignChannels(ctx context.Context) error { Num: int(t.Req.GetShardsNum()), }) if err != nil { - return err + return merr.Wrapf(err, "failed to allocate vchannels for collection %d (shards=%d)", + t.header.GetCollectionId(), t.Req.GetShardsNum()) } for _, vchannel := range vchannels { @@ -629,7 +629,7 @@ func (t *createCollectionTask) Prepare(ctx context.Context) error { func (t *createCollectionTask) validateIfCollectionExists(ctx context.Context) error { // Check if the collection name duplicates an alias. if _, err := t.meta.DescribeAlias(ctx, t.Req.GetDbName(), t.Req.GetCollectionName(), typeutil.MaxTimestamp); err == nil { - err2 := fmt.Errorf("collection name [%s] conflicts with an existing alias, please choose a unique name", t.Req.GetCollectionName()) + err2 := merr.WrapErrAsInputError(merr.WrapErrAliasCollectionNameConflict(t.Req.GetDbName(), t.Req.GetCollectionName(), "please choose a unique name")) log.Ctx(ctx).Warn("create collection failed", zap.String("database", t.Req.GetDbName()), zap.Error(err2)) return err2 } @@ -639,7 +639,7 @@ func (t *createCollectionTask) validateIfCollectionExists(ctx context.Context) e if err == nil { newCollInfo := newCollectionModel(t.header, t.body, 0) if equal := existedCollInfo.Equal(*newCollInfo); !equal { - return fmt.Errorf("create duplicate collection with different parameters, collection: %s", t.Req.GetCollectionName()) + return merr.WrapErrParameterInvalidMsg("create duplicate collection with different parameters, collection: %s", t.Req.GetCollectionName()) } return errIgnoredCreateCollection } @@ -658,12 +658,12 @@ func validateMultiAnalyzerParams(params string, coll *schemapb.CollectionSchema, mfield, ok := m["by_field"] if !ok { - return fmt.Errorf("multi analyzer params now must set by_field to specify with field decide analyzer") + return merr.WrapErrParameterInvalidMsg("multi analyzer params now must set by_field to specify with field decide analyzer") } err = json.Unmarshal(mfield, &mFileName) if err != nil { - return fmt.Errorf("multi analyzer params by_field must be string but now: %s", mfield) + return merr.WrapErrParameterInvalidMsg("multi analyzer params by_field must be string but now: %s", mfield) } // check field exist @@ -672,7 +672,7 @@ func validateMultiAnalyzerParams(params string, coll *schemapb.CollectionSchema, if field.GetName() == mFileName { // only support string field now if field.GetDataType() != schemapb.DataType_VarChar { - return fmt.Errorf("multi analyzer params now only support by string field, but field %s is not string", field.GetName()) + return merr.WrapErrParameterInvalidMsg("multi analyzer params now only support by string field, but field %s is not string", field.GetName()) } fieldExist = true break @@ -680,25 +680,25 @@ func validateMultiAnalyzerParams(params string, coll *schemapb.CollectionSchema, } if !fieldExist { - return fmt.Errorf("multi analyzer dependent field %s not exist in collection %s", string(mfield), coll.GetName()) + return merr.WrapErrParameterInvalidMsg("multi analyzer dependent field %s not exist in collection %s", string(mfield), coll.GetName()) } if value, ok := m["alias"]; ok { mapping := map[string]string{} err = json.Unmarshal(value, &mapping) if err != nil { - return fmt.Errorf("multi analyzer alias must be string map but now: %s", value) + return merr.WrapErrParameterInvalidMsg("multi analyzer alias must be string map but now: %s", value) } } analyzers, ok := m["analyzers"] if !ok { - return fmt.Errorf("multi analyzer params must set analyzers ") + return merr.WrapErrParameterInvalidMsg("multi analyzer params must set analyzers ") } err = json.Unmarshal(analyzers, &analyzerMap) if err != nil { - return fmt.Errorf("unmarshal analyzers failed: %s", err) + return merr.WrapErrParameterInvalidMsg("unmarshal analyzers failed: %s", err) } hasDefault := false @@ -714,7 +714,7 @@ func validateMultiAnalyzerParams(params string, coll *schemapb.CollectionSchema, } if !hasDefault { - return fmt.Errorf("multi analyzer must set default analyzer for all unknown value") + return merr.WrapErrParameterInvalidMsg("multi analyzer must set default analyzer for all unknown value") } return nil } @@ -726,15 +726,15 @@ func validateAnalyzer(collSchema *schemapb.CollectionSchema, fieldSchema *schema } if !h.EnableAnalyzer() { - return fmt.Errorf("field %s which has enable_match or is input of BM25 function must also enable_analyzer", fieldSchema.Name) + return merr.WrapErrParameterInvalidMsg("field %s which has enable_match or is input of BM25 function must also enable_analyzer", fieldSchema.Name) } if params, ok := h.GetMultiAnalyzerParams(); ok { if h.EnableMatch() { - return fmt.Errorf("multi analyzer now only support for bm25, but now field %s enable match", fieldSchema.Name) + return merr.WrapErrParameterInvalidMsg("multi analyzer now only support for bm25, but now field %s enable match", fieldSchema.Name) } if h.HasAnalyzerParams() { - return fmt.Errorf("field %s analyzer params should be none if has multi analyzer params", fieldSchema.Name) + return merr.WrapErrParameterInvalidMsg("field %s analyzer params should be none if has multi analyzer params", fieldSchema.Name) } return validateMultiAnalyzerParams(params, collSchema, fieldSchema, analyzerInfos) diff --git a/internal/rootcoord/ddl_callbacks.go b/internal/rootcoord/ddl_callbacks.go index 3daeb8bf35..1549bb035a 100644 --- a/internal/rootcoord/ddl_callbacks.go +++ b/internal/rootcoord/ddl_callbacks.go @@ -30,6 +30,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/messagespb" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message/ce" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -123,7 +124,7 @@ func (c *DDLCallback) ExpireCaches(ctx context.Context, expirations any) error { func (c *DDLCallback) expireCache(ctx context.Context, cacheExpiration *message.CacheExpiration) error { ts, err := c.tsoAllocator.GenerateTSO(1) if err != nil { - return errors.Wrap(err, "failed to generate timestamp") + return merr.Wrap(err, "failed to generate timestamp") } switch cacheExpiration.Cache.(type) { case *messagespb.CacheExpiration_LegacyProxyCollectionMetaCache: @@ -144,7 +145,7 @@ func (c *DDLCallback) expireCache(ctx context.Context, cacheExpiration *message. func startBroadcastWithRBACLock(ctx context.Context) (broadcaster.BroadcastAPI, error) { api, err := broadcast.StartBroadcastWithResourceKeys(ctx, message.NewExclusivePrivilegeResourceKey()) if err != nil { - return nil, errors.Wrap(err, "failed to start broadcast with rbac lock") + return nil, merr.Wrap(err, "failed to start broadcast with rbac lock") } return api, nil } @@ -153,7 +154,7 @@ func startBroadcastWithRBACLock(ctx context.Context) (broadcaster.BroadcastAPI, func startBroadcastWithDatabaseLock(ctx context.Context, dbName string) (broadcaster.BroadcastAPI, error) { broadcaster, err := broadcast.StartBroadcastWithResourceKeys(ctx, message.NewExclusiveDBNameResourceKey(dbName)) if err != nil { - return nil, errors.Wrap(err, "failed to start broadcast with database lock") + return nil, merr.Wrap(err, "failed to start broadcast with database lock") } return broadcaster, nil } @@ -167,7 +168,7 @@ func (*Core) startBroadcastWithCollectionLock(ctx context.Context, dbName string message.NewExclusiveCollectionNameResourceKey(dbName, collectionName), ) if err != nil { - return nil, errors.Wrap(err, "failed to start broadcast with collection lock") + return nil, merr.Wrap(err, "failed to start broadcast with collection lock") } return broadcaster, nil } @@ -189,7 +190,7 @@ func waitUntilSchemaDropReady(ctx context.Context) error { func (c *Core) startBroadcastWithAliasOrCollectionLock(ctx context.Context, dbName string, collectionNameOrAlias string) (broadcaster.BroadcastAPI, error) { coll, err := c.meta.GetCollectionByName(ctx, dbName, collectionNameOrAlias, typeutil.MaxTimestamp, true) if err != nil { - return nil, errors.Wrap(err, "failed to get collection by name") + return nil, merr.Wrap(err, "failed to get collection by name") } return c.startBroadcastWithCollectionLock(ctx, dbName, coll.Name) } diff --git a/internal/rootcoord/ddl_callbacks_alter_collection_add_field.go b/internal/rootcoord/ddl_callbacks_alter_collection_add_field.go index 595f4dfd3d..db2eda5686 100644 --- a/internal/rootcoord/ddl_callbacks_alter_collection_add_field.go +++ b/internal/rootcoord/ddl_callbacks_alter_collection_add_field.go @@ -3,7 +3,6 @@ package rootcoord import ( "context" - "github.com/cockroachdb/errors" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/fieldmaskpb" @@ -36,10 +35,10 @@ func (c *Core) broadcastAlterCollectionForAddField(ctx context.Context, req *mil // check if the field schema is illegal. fieldSchema := &schemapb.FieldSchema{} if err = proto.Unmarshal(req.Schema, fieldSchema); err != nil { - return errors.Wrap(err, "failed to unmarshal field schema") + return merr.Wrap(err, "failed to unmarshal field schema") } if err := checkFieldSchema([]*schemapb.FieldSchema{fieldSchema}); err != nil { - return errors.Wrap(err, "failed to check field schema") + return merr.Wrap(err, "failed to check field schema") } if fieldSchema.GetDataType() == schemapb.DataType_Timestamptz { timezone, exist := funcutil.TryGetAttrByKeyFromRepeatedKV(common.TimezoneKey, coll.Properties) @@ -47,7 +46,7 @@ func (c *Core) broadcastAlterCollectionForAddField(ctx context.Context, req *mil timezone = common.DefaultTimezone } if err := timestamptz.CheckAndRewriteTimestampTzDefaultValueForFieldSchema(fieldSchema, timezone); err != nil { - return merr.WrapErrParameterInvalidMsg("invalid default value of field, name: %s, err: %w", fieldSchema.Name, err) + return merr.WrapErrParameterInvalidErr(err, "invalid default value of field, name: %s", fieldSchema.Name) } } diff --git a/internal/rootcoord/ddl_callbacks_alter_collection_name.go b/internal/rootcoord/ddl_callbacks_alter_collection_name.go index 97d9c74607..411222b5a6 100644 --- a/internal/rootcoord/ddl_callbacks_alter_collection_name.go +++ b/internal/rootcoord/ddl_callbacks_alter_collection_name.go @@ -2,7 +2,6 @@ package rootcoord import ( "context" - "fmt" "google.golang.org/protobuf/types/known/fieldmaskpb" @@ -110,17 +109,17 @@ func (c *Core) validateEncryption(ctx context.Context, oldDBName string, newDBNa // old and new DB names are filled in Prepare, shouldn't be empty here originalDB, err := c.meta.GetDatabaseByName(ctx, oldDBName, typeutil.MaxTimestamp) if err != nil { - return fmt.Errorf("failed to get original database: %w", err) + return merr.Wrap(err, "failed to get original database") } targetDB, err := c.meta.GetDatabaseByName(ctx, newDBName, typeutil.MaxTimestamp) if err != nil { - return fmt.Errorf("target database %s not found: %w", newDBName, err) + return merr.Wrapf(err, "target database %s not found", newDBName) } // Check if either database has encryption enabled if hookutil.IsDBEncrypted(originalDB.Properties) || hookutil.IsDBEncrypted(targetDB.Properties) { - return fmt.Errorf("deny to change collection databases due to at least one database enabled encryption, original DB: %s, target DB: %s", oldDBName, newDBName) + return merr.WrapErrOperationNotSupportedMsg("deny to change collection databases due to at least one database enabled encryption, original DB: %s, target DB: %s", oldDBName, newDBName) } return nil diff --git a/internal/rootcoord/ddl_callbacks_alter_collection_properties.go b/internal/rootcoord/ddl_callbacks_alter_collection_properties.go index c5a9f13746..e2d88f62e9 100644 --- a/internal/rootcoord/ddl_callbacks_alter_collection_properties.go +++ b/internal/rootcoord/ddl_callbacks_alter_collection_properties.go @@ -411,7 +411,7 @@ func (c *DDLCallback) alterCollectionV2AckCallback(ctx context.Context, result m log.Ctx(ctx).Warn("alter a non-existent collection, ignore it", log.FieldMessage(result.Message)) return nil } - return errors.Wrap(err, "failed to alter collection") + return merr.Wrap(err, "failed to alter collection") } if body.Updates.AlterLoadConfig != nil { resp, err := c.mixCoord.UpdateLoadConfig(ctx, &querypb.UpdateLoadConfigRequest{ @@ -420,21 +420,21 @@ func (c *DDLCallback) alterCollectionV2AckCallback(ctx context.Context, result m ResourceGroups: body.Updates.AlterLoadConfig.ResourceGroups, }) if err != nil { - return errors.Wrap(err, "failed to update load config") + return merr.Wrap(err, "failed to update load config") } if err := merr.CheckRPCCall(resp, err); err != nil { if errors.Is(err, merr.ErrResourceGroupNotFound) { log.Ctx(ctx).Warn("failed to update load config due to missing resource group, stop retrying", zap.Error(err)) return nil } - return errors.Wrap(err, "failed to update load config") + return merr.Wrap(err, "failed to update load config") } } if err := c.cascadeDropFieldIndexesInline(ctx, result); err != nil { return err } if err := c.broker.BroadcastAlteredCollection(ctx, header.CollectionId); err != nil { - return errors.Wrap(err, "failed to broadcast altered collection") + return merr.Wrap(err, "failed to broadcast altered collection") } // If the collection was renamed or moved to a different DB, grants were migrated diff --git a/internal/rootcoord/ddl_callbacks_alter_collection_schema.go b/internal/rootcoord/ddl_callbacks_alter_collection_schema.go index 1ce14cce2d..d98d2ba483 100644 --- a/internal/rootcoord/ddl_callbacks_alter_collection_schema.go +++ b/internal/rootcoord/ddl_callbacks_alter_collection_schema.go @@ -19,7 +19,6 @@ package rootcoord import ( "context" - "github.com/cockroachdb/errors" "google.golang.org/protobuf/types/known/fieldmaskpb" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" @@ -105,7 +104,7 @@ func (c *Core) broadcastAlterCollectionSchemaAdd(ctx context.Context, broadcaste fieldSchemas = append(fieldSchemas, fieldSchema) } if err := checkFieldSchema(fieldSchemas); err != nil { - return errors.Wrap(err, "failed to check field schema") + return merr.Wrap(err, "failed to check field schema") } newFieldNames := make(map[string]struct{}, len(fieldSchemas)) for _, fieldSchema := range fieldSchemas { @@ -268,7 +267,7 @@ func (c *Core) broadcastAlterCollectionSchemaDrop(ctx context.Context, broadcast case *milvuspb.AlterCollectionSchemaRequest_DropRequest_FieldId: schema, properties, droppedFieldIds, err = buildSchemaForDropField(coll, "", id.FieldId) default: - return merr.WrapErrParameterInvalidMsg("drop request must specify field_name, field_id, or function_name") + return merr.WrapErrParameterMissingMsg("drop request must specify field_name, field_id, or function_name") } if err != nil { return err diff --git a/internal/rootcoord/ddl_callbacks_alter_database.go b/internal/rootcoord/ddl_callbacks_alter_database.go index 2fd344256a..127846232f 100644 --- a/internal/rootcoord/ddl_callbacks_alter_database.go +++ b/internal/rootcoord/ddl_callbacks_alter_database.go @@ -70,11 +70,11 @@ func (c *Core) broadcastAlterDatabase(ctx context.Context, req *rootcoordpb.Alte oldDB, err := c.meta.GetDatabaseByName(ctx, req.GetDbName(), typeutil.MaxTimestamp) if err != nil { - return errors.Wrap(err, "failed to get database by name") + return merr.Wrap(err, "failed to get database by name") } alterLoadConfig, err := c.getAlterLoadConfigOfAlterDatabase(ctx, req.GetDbName(), oldDB.Properties, req.GetProperties()) if err != nil { - return errors.Wrap(err, "failed to get alter load config of alter database") + return merr.Wrap(err, "failed to get alter load config of alter database") } // We only allow to alter or delete properties, not both. @@ -141,7 +141,7 @@ func (c *DDLCallback) alterDatabaseV1AckCallback(ctx context.Context, result mes db := model.NewDatabase(header.DbId, header.DbName, etcdpb.DatabaseState_DatabaseCreated, body.Properties) if err := c.meta.AlterDatabase(ctx, db, result.GetControlChannelResult().TimeTick); err != nil { - return errors.Wrap(err, "failed to alter database") + return merr.Wrap(err, "failed to alter database") } if body.AlterLoadConfig != nil { resp, err := c.mixCoord.UpdateLoadConfig(ctx, &querypb.UpdateLoadConfigRequest{ @@ -150,14 +150,14 @@ func (c *DDLCallback) alterDatabaseV1AckCallback(ctx context.Context, result mes ResourceGroups: body.AlterLoadConfig.ResourceGroups, }) if err != nil { - return errors.Wrap(err, "failed to update load config") + return merr.Wrap(err, "failed to update load config") } if err := merr.CheckRPCCall(resp, err); err != nil { if errors.Is(err, merr.ErrResourceGroupNotFound) { log.Ctx(ctx).Warn("failed to update load config due to missing resource group, stop retrying", zap.Error(err)) return nil } - return errors.Wrap(err, "failed to update load config") + return merr.Wrap(err, "failed to update load config") } } return c.ExpireCaches(ctx, ce.NewBuilder(). diff --git a/internal/rootcoord/ddl_callbacks_collection_function.go b/internal/rootcoord/ddl_callbacks_collection_function.go index 933321acc3..c4f4d642a3 100644 --- a/internal/rootcoord/ddl_callbacks_collection_function.go +++ b/internal/rootcoord/ddl_callbacks_collection_function.go @@ -18,7 +18,6 @@ package rootcoord import ( "context" - "fmt" "go.uber.org/zap" "google.golang.org/protobuf/types/known/fieldmaskpb" @@ -87,7 +86,7 @@ func callAlterCollection(ctx context.Context, c *Core, broadcaster broadcaster.B func alterFunctionGenNewCollection(ctx context.Context, fSchema *schemapb.FunctionSchema, collection *model.Collection) error { if fSchema == nil { - return fmt.Errorf("function schema is empty") + return merr.WrapErrParameterInvalidMsg("function schema is empty") } var oldFuncSchema *model.Function newFuncs := []*model.Function{} @@ -99,7 +98,7 @@ func alterFunctionGenNewCollection(ctx context.Context, fSchema *schemapb.Functi } } if oldFuncSchema == nil { - err := fmt.Errorf("function %s not exists", fSchema.Name) + err := merr.WrapErrParameterInvalidMsg("function %s not exists", fSchema.Name) log.Ctx(ctx).Error("Alter function failed:", zap.Error(err)) return err } @@ -115,7 +114,7 @@ func alterFunctionGenNewCollection(ctx context.Context, fSchema *schemapb.Functi for _, name := range oldFuncSchema.OutputFieldNames { field, exists := fieldMapping[name] if !exists { - return fmt.Errorf("old version function's output field %s not exists", name) + return merr.WrapErrParameterInvalidMsg("old version function's output field %s not exists", name) } field.IsFunctionOutput = false } @@ -123,7 +122,7 @@ func alterFunctionGenNewCollection(ctx context.Context, fSchema *schemapb.Functi for _, name := range fSchema.InputFieldNames { field, exists := fieldMapping[name] if !exists { - err := fmt.Errorf("function's input field %s not exists", name) + err := merr.WrapErrParameterInvalidMsg("function's input field %s not exists", name) log.Ctx(ctx).Error("Incorrect function configuration:", zap.Error(err)) return err } @@ -132,12 +131,12 @@ func alterFunctionGenNewCollection(ctx context.Context, fSchema *schemapb.Functi for _, name := range fSchema.OutputFieldNames { field, exists := fieldMapping[name] if !exists { - err := fmt.Errorf("function's output field %s not exists", name) + err := merr.WrapErrParameterInvalidMsg("function's output field %s not exists", name) log.Ctx(ctx).Error("Incorrect function configuration:", zap.Error(err)) return err } if field.IsFunctionOutput { - err := fmt.Errorf("function's output field %s is already of other functions", name) + err := merr.WrapErrParameterInvalidMsg("function's output field %s is already of other functions", name) log.Ctx(ctx).Error("Incorrect function configuration: ", zap.Error(err)) return err } @@ -216,7 +215,7 @@ func (c *Core) broadcastAlterCollectionForDropFunction(ctx context.Context, req for _, id := range needDelFunc.OutputFieldIDs { field, exists := fieldMapping[id] if !exists { - return fmt.Errorf("function's output field %d not exists", id) + return merr.WrapErrServiceInternalMsg("function's output field %d not exists", id) } field.IsFunctionOutput = false } @@ -238,7 +237,7 @@ func (c *Core) broadcastAlterCollectionForAddFunction(ctx context.Context, req * newColl := oldColl.Clone() fSchema := req.FunctionSchema if fSchema == nil { - return merr.WrapErrParameterInvalidMsg("Function schema is empty") + return merr.WrapErrParameterInvalidMsg("function schema is empty") } nextFunctionID := int64(StartOfUserFunctionID) @@ -257,7 +256,7 @@ func (c *Core) broadcastAlterCollectionForAddFunction(ctx context.Context, req * for _, name := range fSchema.InputFieldNames { field, exists := fieldMapping[name] if !exists { - err := fmt.Errorf("function's input field %s not exists", name) + err := merr.WrapErrParameterInvalidMsg("function's input field %s not exists", name) log.Ctx(ctx).Error("Incorrect function configuration:", zap.Error(err)) return err } @@ -266,12 +265,12 @@ func (c *Core) broadcastAlterCollectionForAddFunction(ctx context.Context, req * for _, name := range fSchema.OutputFieldNames { field, exists := fieldMapping[name] if !exists { - err := fmt.Errorf("function's output field %s not exists", name) + err := merr.WrapErrParameterInvalidMsg("function's output field %s not exists", name) log.Ctx(ctx).Error("Incorrect function configuration:", zap.Error(err)) return err } if field.IsFunctionOutput { - err := fmt.Errorf("function's output field %s is already of other functions", name) + err := merr.WrapErrParameterInvalidMsg("function's output field %s is already of other functions", name) log.Ctx(ctx).Error("Incorrect function configuration: ", zap.Error(err)) return err } diff --git a/internal/rootcoord/ddl_callbacks_create_collection.go b/internal/rootcoord/ddl_callbacks_create_collection.go index d94dd89819..922093a50a 100644 --- a/internal/rootcoord/ddl_callbacks_create_collection.go +++ b/internal/rootcoord/ddl_callbacks_create_collection.go @@ -18,9 +18,7 @@ package rootcoord import ( "context" - "fmt" - "github.com/cockroachdb/errors" "google.golang.org/protobuf/proto" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" @@ -112,13 +110,13 @@ func (c *DDLCallback) createCollectionV1AckCallback(ctx context.Context, result if !funcutil.IsControlChannel(vchannel) { // create shard info when virtual channel is created. if err := c.createCollectionShard(ctx, header, body, vchannel, result); err != nil { - return errors.Wrap(err, "failed to create collection shard") + return merr.Wrap(err, "failed to create collection shard") } } } newCollInfo := newCollectionModelWithMessage(header, body, result) if err := c.meta.AddCollection(ctx, newCollInfo); err != nil { - return errors.Wrap(err, "failed to add collection to meta table") + return merr.Wrap(err, "failed to add collection to meta table") } return c.ExpireCaches(ctx, ce.NewBuilder().WithLegacyProxyCollectionMetaCache( @@ -228,7 +226,7 @@ func newCollectionModel(header *message.CreateCollectionMessageHeader, body *mes func mustConsumeConsistencyLevel(properties []*commonpb.KeyValuePair) (commonpb.ConsistencyLevel, []*commonpb.KeyValuePair) { ok, consistencyLevel := getConsistencyLevel(properties...) if !ok { - panic(fmt.Errorf("consistency level not found in properties")) + panic(merr.WrapErrServiceInternalMsg("consistency level not found in properties")) } newProperties := make([]*commonpb.KeyValuePair, 0, len(properties)-1) for _, property := range properties { diff --git a/internal/rootcoord/ddl_callbacks_create_database.go b/internal/rootcoord/ddl_callbacks_create_database.go index c770e4ccaf..bcd0137502 100644 --- a/internal/rootcoord/ddl_callbacks_create_database.go +++ b/internal/rootcoord/ddl_callbacks_create_database.go @@ -20,8 +20,6 @@ import ( "context" "strings" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb" "github.com/milvus-io/milvus/internal/distributed/streaming" @@ -50,13 +48,13 @@ func (c *Core) broadcastCreateDatabase(ctx context.Context, req *milvuspb.Create dbID, err := c.idAllocator.AllocOne() if err != nil { - return errors.Wrap(err, "failed to allocate database id") + return merr.Wrap(err, "failed to allocate database id") } // Use dbID as ezID because the dbID is unqiue properties, err := hookutil.TidyDBCipherProperties(dbID, req.GetProperties()) if err != nil { - return errors.Wrap(err, "failed to tidy database cipher properties") + return merr.Wrap(err, "failed to tidy database cipher properties") } tz, exist := funcutil.TryGetAttrByKeyFromRepeatedKV(common.TimezoneKey, properties) @@ -65,7 +63,7 @@ func (c *Core) broadcastCreateDatabase(ctx context.Context, req *milvuspb.Create } if err := hookutil.CreateEZByDBProperties(properties); err != nil { - return errors.Wrap(err, "failed to create ez by db properties") + return merr.Wrap(err, "failed to create ez by db properties") } msg := message.NewCreateDatabaseMessageBuilderV2(). @@ -86,7 +84,7 @@ func (c *DDLCallback) createDatabaseV1AckCallback(ctx context.Context, result me header := result.Message.Header() db := model.NewDatabase(header.DbId, header.DbName, etcdpb.DatabaseState_DatabaseCreated, result.Message.MustBody().Properties) if err := c.meta.CreateDatabase(ctx, db, result.GetControlChannelResult().TimeTick); err != nil { - return errors.Wrap(err, "failed to create database") + return merr.Wrap(err, "failed to create database") } return c.ExpireCaches(ctx, ce.NewBuilder(). WithLegacyProxyCollectionMetaCache( diff --git a/internal/rootcoord/ddl_callbacks_create_partition.go b/internal/rootcoord/ddl_callbacks_create_partition.go index 52f8f3680a..b7c436e190 100644 --- a/internal/rootcoord/ddl_callbacks_create_partition.go +++ b/internal/rootcoord/ddl_callbacks_create_partition.go @@ -18,9 +18,6 @@ package rootcoord import ( "context" - "fmt" - - "github.com/cockroachdb/errors" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb" @@ -30,6 +27,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message/ce" "github.com/milvus-io/milvus/pkg/v3/util/commonpbutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -54,13 +52,13 @@ func (c *Core) broadcastCreatePartition(ctx context.Context, in *milvuspb.Create cfgMaxPartitionNum := Params.RootCoordCfg.MaxPartitionNum.GetAsInt() partitionNum := collMeta.GetPartitionNum(true) if partitionNum >= cfgMaxPartitionNum { - return 0, fmt.Errorf("partition number (%d) exceeds max configuration (%d), collection: %s", + return 0, merr.WrapErrParameterInvalidMsg("partition number (%d) exceeds max configuration (%d), collection: %s", partitionNum, cfgMaxPartitionNum, collMeta.Name) } partID, err := c.idAllocator.AllocOne() if err != nil { - return 0, errors.Wrap(err, "failed to allocate partition ID") + return 0, merr.Wrap(err, "failed to allocate partition ID") } channels := make([]string, 0, collMeta.ShardsNum+1) @@ -99,7 +97,7 @@ func (c *DDLCallback) createPartitionV1AckCallback(ctx context.Context, result m State: pb.PartitionState_PartitionCreated, } if err := c.meta.AddPartition(ctx, partition); err != nil { - return errors.Wrap(err, "failed to add partition meta") + return merr.Wrap(err, "failed to add partition meta") } return c.ExpireCaches(ctx, ce.NewBuilder(). WithLegacyProxyCollectionMetaCache( diff --git a/internal/rootcoord/ddl_callbacks_drop_alias.go b/internal/rootcoord/ddl_callbacks_drop_alias.go index 85e0171259..637b96e371 100644 --- a/internal/rootcoord/ddl_callbacks_drop_alias.go +++ b/internal/rootcoord/ddl_callbacks_drop_alias.go @@ -20,13 +20,12 @@ import ( "context" "strings" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb" "github.com/milvus-io/milvus/internal/distributed/streaming" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message/ce" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -61,7 +60,7 @@ func (c *Core) broadcastDropAlias(ctx context.Context, req *milvuspb.DropAliasRe func (c *DDLCallback) dropAliasV2AckCallback(ctx context.Context, result message.BroadcastResultDropAliasMessageV2) error { if err := c.meta.DropAlias(ctx, result); err != nil { - return errors.Wrap(err, "failed to drop alias") + return merr.Wrap(err, "failed to drop alias") } return c.ExpireCaches(ctx, ce.NewBuilder().WithLegacyProxyCollectionMetaCache( diff --git a/internal/rootcoord/ddl_callbacks_drop_collection.go b/internal/rootcoord/ddl_callbacks_drop_collection.go index 7730713221..410a66f48d 100644 --- a/internal/rootcoord/ddl_callbacks_drop_collection.go +++ b/internal/rootcoord/ddl_callbacks_drop_collection.go @@ -20,7 +20,6 @@ import ( "context" "fmt" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" @@ -92,7 +91,7 @@ func (c *DDLCallback) dropCollectionV1AckCallback(ctx context.Context, result me if err := registry.CallMessageAckCallback(ctx, dropLoadConfigMsg, map[string]*message.AppendResult{ streaming.WAL().ControlChannel(): result, }); err != nil { - return errors.Wrap(err, "failed to release collection") + return merr.Wrap(err, "failed to release collection") } // 2. drop the collection index. @@ -108,7 +107,7 @@ func (c *DDLCallback) dropCollectionV1AckCallback(ctx context.Context, result me if err := registry.CallMessageAckCallback(ctx, dropIndexMsg, map[string]*message.AppendResult{ streaming.WAL().ControlChannel(): result, }); err != nil { - return errors.Wrap(err, "failed to drop collection index") + return merr.Wrap(err, "failed to drop collection index") } // 2.5. best-effort drop all snapshots of this collection @@ -130,7 +129,7 @@ func (c *DDLCallback) dropCollectionV1AckCallback(ctx context.Context, result me // 3. drop the collection meta itself. if err := c.meta.DropCollection(ctx, collectionID, result.TimeTick); err != nil { - return errors.Wrap(err, "failed to drop collection") + return merr.Wrap(err, "failed to drop collection") } continue } @@ -142,7 +141,7 @@ func (c *DDLCallback) dropCollectionV1AckCallback(ctx context.Context, result me ChannelName: vchannel, }) if err := merr.CheckRPCCall(resp, err); err != nil { - return errors.Wrap(err, "failed to drop virtual channel") + return merr.Wrap(err, "failed to drop virtual channel") } } // add the collection tombstone to the sweeper. diff --git a/internal/rootcoord/ddl_callbacks_drop_database.go b/internal/rootcoord/ddl_callbacks_drop_database.go index ecb6535492..c2b511ebd9 100644 --- a/internal/rootcoord/ddl_callbacks_drop_database.go +++ b/internal/rootcoord/ddl_callbacks_drop_database.go @@ -20,14 +20,13 @@ import ( "context" "strings" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb" "github.com/milvus-io/milvus/internal/distributed/streaming" "github.com/milvus-io/milvus/internal/util/hookutil" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message/ce" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -45,12 +44,12 @@ func (c *Core) broadcastDropDatabase(ctx context.Context, req *milvuspb.DropData db, err := c.meta.GetDatabaseByName(ctx, req.GetDbName(), typeutil.MaxTimestamp) if err != nil { - return errors.Wrap(err, "failed to get database name") + return merr.Wrap(err, "failed to get database name") } // Call back cipher plugin when dropping database succeeded if err := hookutil.RemoveEZByDBProperties(db.Properties); err != nil { - return errors.Wrap(err, "failed to remove ez by db properties") + return merr.Wrap(err, "failed to remove ez by db properties") } msg := message.NewDropDatabaseMessageBuilderV2(). @@ -68,7 +67,7 @@ func (c *Core) broadcastDropDatabase(ctx context.Context, req *milvuspb.DropData func (c *DDLCallback) dropDatabaseV1AckCallback(ctx context.Context, result message.BroadcastResultDropDatabaseMessageV2) error { header := result.Message.Header() if err := c.meta.DropDatabase(ctx, header.DbName, result.GetControlChannelResult().TimeTick); err != nil { - return errors.Wrap(err, "failed to drop database") + return merr.Wrap(err, "failed to drop database") } return c.ExpireCaches(ctx, ce.NewBuilder(). WithLegacyProxyCollectionMetaCache( diff --git a/internal/rootcoord/ddl_callbacks_drop_partition.go b/internal/rootcoord/ddl_callbacks_drop_partition.go index 07cfa920ce..94bb2a62bf 100644 --- a/internal/rootcoord/ddl_callbacks_drop_partition.go +++ b/internal/rootcoord/ddl_callbacks_drop_partition.go @@ -20,8 +20,6 @@ import ( "context" "fmt" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb" "github.com/milvus-io/milvus/internal/distributed/streaming" @@ -29,12 +27,13 @@ import ( "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message/ce" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) func (c *Core) broadcastDropPartition(ctx context.Context, in *milvuspb.DropPartitionRequest) error { if in.GetPartitionName() == Params.CommonCfg.DefaultPartitionName.GetValue() { - return errors.New("default partition cannot be deleted") + return merr.WrapErrParameterInvalidMsg("default partition cannot be deleted") } broadcaster, err := c.startBroadcastWithAliasOrCollectionLock(ctx, in.GetDbName(), in.GetCollectionName()) diff --git a/internal/rootcoord/ddl_callbacks_rbac_credential.go b/internal/rootcoord/ddl_callbacks_rbac_credential.go index f90a24feb7..ad01ca11c8 100644 --- a/internal/rootcoord/ddl_callbacks_rbac_credential.go +++ b/internal/rootcoord/ddl_callbacks_rbac_credential.go @@ -20,13 +20,12 @@ import ( "context" "strings" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb" "github.com/milvus-io/milvus/internal/distributed/streaming" "github.com/milvus-io/milvus/pkg/v3/proto/internalpb" "github.com/milvus-io/milvus/pkg/v3/proto/proxypb" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -40,7 +39,7 @@ func (c *Core) broadcastAlterUserForCreateCredential(ctx context.Context, credIn defer broadcaster.Close() if err := c.meta.CheckIfAddCredential(ctx, credInfo); err != nil { - return errors.Wrap(err, "failed to check if add credential") + return merr.Wrap(err, "failed to check if add credential") } msg := message.NewAlterUserMessageBuilderV2(). @@ -66,7 +65,7 @@ func (c *Core) broadcastAlterUserForUpdateCredential(ctx context.Context, credIn defer broadcaster.Close() if err := c.meta.CheckIfUpdateCredential(ctx, credInfo); err != nil { - return errors.Wrap(err, "failed to check if update credential") + return merr.Wrap(err, "failed to check if update credential") } msg := message.NewAlterUserMessageBuilderV2(). @@ -86,11 +85,11 @@ func (c *Core) broadcastAlterUserForUpdateCredential(ctx context.Context, credIn func (c *DDLCallback) alterUserV2AckCallback(ctx context.Context, result message.BroadcastResultAlterUserMessageV2) error { // insert to db if err := c.meta.AlterCredential(ctx, result); err != nil { - return errors.Wrap(err, "failed to alter credential") + return merr.Wrap(err, "failed to alter credential") } // update proxy's local cache if err := c.UpdateCredCache(ctx, result.Message.MustBody().CredentialInfo); err != nil { - return errors.Wrap(err, "failed to update cred cache") + return merr.Wrap(err, "failed to update cred cache") } return nil } @@ -105,7 +104,7 @@ func (c *Core) broadcastDropUserForDeleteCredential(ctx context.Context, in *mil defer broadcaster.Close() if err := c.meta.CheckIfDeleteCredential(ctx, in); err != nil { - return errors.Wrap(err, "failed to check if delete credential") + return merr.Wrap(err, "failed to check if delete credential") } msg := message.NewDropUserMessageBuilderV2(). @@ -122,16 +121,16 @@ func (c *Core) broadcastDropUserForDeleteCredential(ctx context.Context, in *mil // dropUserV2AckCallback is the ack callback function for the DeleteCredential message func (c *DDLCallback) dropUserV2AckCallback(ctx context.Context, result message.BroadcastResultDropUserMessageV2) error { if err := c.meta.DeleteCredential(ctx, result); err != nil { - return errors.Wrap(err, "failed to delete credential") + return merr.Wrap(err, "failed to delete credential") } if err := c.ExpireCredCache(ctx, result.Message.Header().UserName); err != nil { - return errors.Wrap(err, "failed to expire cred cache") + return merr.Wrap(err, "failed to expire cred cache") } if err := c.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{ OpType: int32(typeutil.CacheDeleteUser), OpKey: result.Message.Header().UserName, }); err != nil { - return errors.Wrap(err, "failed to refresh policy info cache") + return merr.Wrap(err, "failed to refresh policy info cache") } return nil } diff --git a/internal/rootcoord/ddl_callbacks_rbac_privilege.go b/internal/rootcoord/ddl_callbacks_rbac_privilege.go index bffc914508..1536900f01 100644 --- a/internal/rootcoord/ddl_callbacks_rbac_privilege.go +++ b/internal/rootcoord/ddl_callbacks_rbac_privilege.go @@ -20,13 +20,12 @@ import ( "context" "strings" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb" "github.com/milvus-io/milvus/internal/distributed/streaming" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" "github.com/milvus-io/milvus/pkg/v3/util" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) func (c *Core) broadcastOperatePrivilege(ctx context.Context, in *milvuspb.OperatePrivilegeRequest) error { @@ -37,7 +36,7 @@ func (c *Core) broadcastOperatePrivilege(ctx context.Context, in *milvuspb.Opera defer broadcaster.Close() if err := c.operatePrivilegeCommonCheck(ctx, in); err != nil { - return errors.Wrap(err, "failed to operate privilege common check") + return merr.Wrap(err, "failed to operate privilege common check") } privName := in.Entity.Grantor.Privilege.Name switch in.Version { @@ -79,7 +78,7 @@ func (c *Core) broadcastOperatePrivilege(ctx context.Context, in *milvuspb.Opera WithBroadcast([]string{streaming.WAL().ControlChannel()}). MustBuildBroadcast() default: - return errors.New("invalid operate privilege type") + return merr.WrapErrParameterInvalidMsg("invalid operate privilege type") } _, err = broadcaster.Broadcast(ctx, msg) return err @@ -102,7 +101,7 @@ func (c *Core) broadcastCreatePrivilegeGroup(ctx context.Context, in *milvuspb.C defer broadcaster.Close() if err := c.meta.CheckIfPrivilegeGroupCreatable(ctx, in); err != nil { - return errors.Wrap(err, "failed to check if privilege group creatable") + return merr.Wrap(err, "failed to check if privilege group creatable") } msg := message.NewAlterPrivilegeGroupMessageBuilderV2(). @@ -126,7 +125,7 @@ func (c *Core) broadcastOperatePrivilegeGroup(ctx context.Context, in *milvuspb. defer broadcaster.Close() if err := c.meta.CheckIfPrivilegeGroupAlterable(ctx, in); err != nil { - return errors.Wrap(err, "failed to check if privilege group alterable") + return merr.Wrap(err, "failed to check if privilege group alterable") } var msg message.BroadcastMutableMessage @@ -154,7 +153,7 @@ func (c *Core) broadcastOperatePrivilegeGroup(ctx context.Context, in *milvuspb. WithBroadcast([]string{streaming.WAL().ControlChannel()}). MustBuildBroadcast() default: - return errors.New("invalid operate privilege group type") + return merr.WrapErrParameterInvalidMsg("invalid operate privilege group type") } _, err = broadcaster.Broadcast(ctx, msg) return err @@ -175,7 +174,7 @@ func (c *Core) broadcastDropPrivilegeGroup(ctx context.Context, in *milvuspb.Dro defer broadcaster.Close() if err := c.meta.CheckIfPrivilegeGroupDropable(ctx, in); err != nil { - return errors.Wrap(err, "failed to check if privilege group dropable") + return merr.Wrap(err, "failed to check if privilege group dropable") } msg := message.NewDropPrivilegeGroupMessageBuilderV2(). diff --git a/internal/rootcoord/ddl_callbacks_rbac_restore.go b/internal/rootcoord/ddl_callbacks_rbac_restore.go index bba8c672bd..e65f8814fb 100644 --- a/internal/rootcoord/ddl_callbacks_rbac_restore.go +++ b/internal/rootcoord/ddl_callbacks_rbac_restore.go @@ -19,13 +19,12 @@ package rootcoord import ( "context" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb" "github.com/milvus-io/milvus/internal/distributed/streaming" "github.com/milvus-io/milvus/pkg/v3/proto/proxypb" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" "github.com/milvus-io/milvus/pkg/v3/util" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -37,7 +36,7 @@ func (c *Core) broadcastRestoreRBACV2(ctx context.Context, req *milvuspb.Restore defer broadcaster.Close() if err := c.meta.CheckIfRBACRestorable(ctx, req); err != nil { - return errors.Wrap(err, "failed to check if rbac restorable") + return merr.Wrap(err, "failed to check if rbac restorable") } msg := message.NewRestoreRBACMessageBuilderV2(). @@ -54,12 +53,12 @@ func (c *Core) broadcastRestoreRBACV2(ctx context.Context, req *milvuspb.Restore func (c *DDLCallback) restoreRBACV2AckCallback(ctx context.Context, result message.BroadcastResultRestoreRBACMessageV2) error { meta := result.Message.MustBody().RbacMeta if err := c.meta.RestoreRBAC(ctx, util.DefaultTenant, meta); err != nil { - return errors.Wrap(err, "failed to restore rbac meta data") + return merr.Wrap(err, "failed to restore rbac meta data") } if err := c.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{ OpType: int32(typeutil.CacheRefresh), }); err != nil { - return errors.Wrap(err, "failed to refresh policy info cache") + return merr.Wrap(err, "failed to refresh policy info cache") } return nil } diff --git a/internal/rootcoord/ddl_callbacks_rbac_role.go b/internal/rootcoord/ddl_callbacks_rbac_role.go index ac47377f33..a7b4a07fb6 100644 --- a/internal/rootcoord/ddl_callbacks_rbac_role.go +++ b/internal/rootcoord/ddl_callbacks_rbac_role.go @@ -19,14 +19,13 @@ package rootcoord import ( "context" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb" "github.com/milvus-io/milvus/internal/distributed/streaming" "github.com/milvus-io/milvus/pkg/v3/proto/proxypb" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" "github.com/milvus-io/milvus/pkg/v3/util" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -38,7 +37,7 @@ func (c *Core) broadcastCreateRole(ctx context.Context, in *milvuspb.CreateRoleR defer broadcaster.Close() if err := c.meta.CheckIfCreateRole(ctx, in); err != nil { - return errors.Wrap(err, "failed to check if create role") + return merr.Wrap(err, "failed to check if create role") } msg := message.NewAlterRoleMessageBuilderV2(). @@ -65,7 +64,7 @@ func (c *Core) broadcastDropRole(ctx context.Context, in *milvuspb.DropRoleReque defer broadcaster.Close() if err := c.meta.CheckIfDropRole(ctx, in); err != nil { - return errors.Wrap(err, "failed to check if drop role") + return merr.Wrap(err, "failed to check if drop role") } msg := message.NewDropRoleMessageBuilderV2(). @@ -85,16 +84,16 @@ func (c *DDLCallback) dropRoleV2AckCallback(ctx context.Context, result message. msg := result.Message err := c.meta.DropRole(ctx, util.DefaultTenant, msg.Header().RoleName) if err != nil { - return errors.Wrap(err, "failed to drop role") + return merr.Wrap(err, "failed to drop role") } if err := c.meta.DropGrant(ctx, util.DefaultTenant, &milvuspb.RoleEntity{Name: msg.Header().RoleName}); err != nil { - return errors.Wrap(err, "failed to drop grant") + return merr.Wrap(err, "failed to drop grant") } if err := c.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{ OpType: int32(typeutil.CacheDropRole), OpKey: msg.Header().RoleName, }); err != nil { - return errors.Wrap(err, "failed to refresh policy info cache") + return merr.Wrap(err, "failed to refresh policy info cache") } return nil } @@ -107,7 +106,7 @@ func (c *Core) broadcastOperateUserRole(ctx context.Context, in *milvuspb.Operat defer broadcaster.Close() if err := c.meta.CheckIfOperateUserRole(ctx, in); err != nil { - return errors.Wrap(err, "failed to check if operate user role") + return merr.Wrap(err, "failed to check if operate user role") } var msg message.BroadcastMutableMessage @@ -135,7 +134,7 @@ func (c *Core) broadcastOperateUserRole(ctx context.Context, in *milvuspb.Operat WithBroadcast([]string{streaming.WAL().ControlChannel()}). MustBuildBroadcast() default: - return errors.New("invalid operate user role type") + return merr.WrapErrParameterInvalidMsg("invalid operate user role type") } _, err = broadcaster.Broadcast(ctx, msg) return err @@ -144,13 +143,13 @@ func (c *Core) broadcastOperateUserRole(ctx context.Context, in *milvuspb.Operat func (c *DDLCallback) alterUserRoleV2AckCallback(ctx context.Context, result message.BroadcastResultAlterUserRoleMessageV2) error { header := result.Message.Header() if err := c.meta.OperateUserRole(ctx, util.DefaultTenant, header.RoleBinding.UserEntity, header.RoleBinding.RoleEntity, milvuspb.OperateUserRoleType_AddUserToRole); err != nil { - return errors.Wrap(err, "failed to operate user role") + return merr.Wrap(err, "failed to operate user role") } if err := c.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{ OpType: int32(typeutil.CacheAddUserToRole), OpKey: funcutil.EncodeUserRoleCache(header.RoleBinding.UserEntity.Name, header.RoleBinding.RoleEntity.Name), }); err != nil { - return errors.Wrap(err, "failed to refresh policy info cache") + return merr.Wrap(err, "failed to refresh policy info cache") } return nil } @@ -158,13 +157,13 @@ func (c *DDLCallback) alterUserRoleV2AckCallback(ctx context.Context, result mes func (c *DDLCallback) dropUserRoleV2AckCallback(ctx context.Context, result message.BroadcastResultDropUserRoleMessageV2) error { header := result.Message.Header() if err := c.meta.OperateUserRole(ctx, util.DefaultTenant, header.RoleBinding.UserEntity, header.RoleBinding.RoleEntity, milvuspb.OperateUserRoleType_RemoveUserFromRole); err != nil { - return errors.Wrap(err, "failed to operate user role") + return merr.Wrap(err, "failed to operate user role") } if err := c.proxyClientManager.RefreshPolicyInfoCache(ctx, &proxypb.RefreshPolicyInfoCacheRequest{ OpType: int32(typeutil.CacheRemoveUserFromRole), OpKey: funcutil.EncodeUserRoleCache(header.RoleBinding.UserEntity.Name, header.RoleBinding.RoleEntity.Name), }); err != nil { - return errors.Wrap(err, "failed to refresh policy info cache") + return merr.Wrap(err, "failed to refresh policy info cache") } return nil } diff --git a/internal/rootcoord/ddl_callbacks_truncate_collection.go b/internal/rootcoord/ddl_callbacks_truncate_collection.go index 56e60fa6db..f28c27af80 100644 --- a/internal/rootcoord/ddl_callbacks_truncate_collection.go +++ b/internal/rootcoord/ddl_callbacks_truncate_collection.go @@ -27,6 +27,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/messagespb" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -79,12 +80,12 @@ func (c *DDLCallback) truncateCollectionV2AckCallback(ctx context.Context, resul // Drop segments that were updated before the flush timestamp if err := c.mixCoord.DropSegmentsByTime(ctx, header.CollectionId, flushTsList); err != nil { - return errors.Wrap(err, "when dropping segments by time") + return merr.Wrap(err, "when dropping segments by time") } // manually update current target to sync QueryCoord's view if err := c.mixCoord.ManualUpdateCurrentTarget(ctx, header.CollectionId); err != nil { - return errors.Wrap(err, "when manually updating current target") + return merr.Wrap(err, "when manually updating current target") } if err := c.meta.TruncateCollection(ctx, result); err != nil { @@ -92,12 +93,12 @@ func (c *DDLCallback) truncateCollectionV2AckCallback(ctx context.Context, resul log.Ctx(ctx).Warn("truncate a non-existent collection, ignore it", log.FieldMessage(result.Message)) return nil } - return errors.Wrap(err, "when truncating collection") + return merr.Wrap(err, "when truncating collection") } // notify datacoord to update their meta cache if err := c.broker.BroadcastAlteredCollection(ctx, header.CollectionId); err != nil { - return errors.Wrap(err, "when broadcasting altered collection") + return merr.Wrap(err, "when broadcasting altered collection") } return nil } @@ -117,12 +118,12 @@ func (c *DDLCallback) truncateCollectionV2AckOnceCallback(ctx context.Context, r log.Ctx(ctx).Warn("begin to truncate a non-existent collection, ignore it", log.FieldMessage(result.Message)) return nil } - return errors.Wrap(err, "when beginning truncate collection") + return merr.Wrap(err, "when beginning truncate collection") } // notify datacoord to update their meta cache if err := c.broker.BroadcastAlteredCollection(ctx, collectionID); err != nil { - return errors.Wrap(err, "when broadcasting altered collection") + return merr.Wrap(err, "when broadcasting altered collection") } return nil } diff --git a/internal/rootcoord/dml_channels.go b/internal/rootcoord/dml_channels.go index 671b4a99b1..5dbdd55833 100644 --- a/internal/rootcoord/dml_channels.go +++ b/internal/rootcoord/dml_channels.go @@ -24,7 +24,6 @@ import ( "strings" "sync" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus/internal/coordinator/snmanager" @@ -33,6 +32,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/metrics" "github.com/milvus-io/milvus/pkg/v3/mq/common" "github.com/milvus-io/milvus/pkg/v3/mq/msgstream" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -312,7 +312,7 @@ func (d *dmlChannels) getMsgStreamByName(chanName string) (*dmlMsgStream, error) dms, ok := d.pool.Get(chanName) if !ok { log.Ctx(d.ctx).Error("invalid channelName", zap.String("chanName", chanName)) - return nil, errors.Newf("invalid channel name: %s", chanName) + return nil, merr.WrapErrParameterInvalidMsg("invalid channel name: %s", chanName) } return dms, nil } @@ -361,7 +361,7 @@ func (d *dmlChannels) broadcastMark(chanNames []string, pack *msgstream.MsgPack) } } else { dms.mutex.RUnlock() - return nil, errors.Newf("channel not in use: %s", chanName) + return nil, merr.WrapErrServiceInternalMsg("channel not in use: %s", chanName) } dms.mutex.RUnlock() } diff --git a/internal/rootcoord/drop_collection_task.go b/internal/rootcoord/drop_collection_task.go index b2e81ef0b1..ee1ae142df 100644 --- a/internal/rootcoord/drop_collection_task.go +++ b/internal/rootcoord/drop_collection_task.go @@ -18,7 +18,6 @@ package rootcoord import ( "context" - "fmt" "github.com/cockroachdb/errors" "go.uber.org/zap" @@ -42,7 +41,7 @@ type dropCollectionTask struct { func (t *dropCollectionTask) validate(ctx context.Context) error { // Critical promise here, also see comment of startBroadcastWithCollectionLock. if t.meta.IsAlias(ctx, t.Req.GetDbName(), t.Req.GetCollectionName()) { - return fmt.Errorf("cannot drop the collection via alias = %s", t.Req.CollectionName) + return merr.WrapErrParameterInvalidMsg("cannot drop the collection via alias = %s", t.Req.CollectionName) } // use max ts to check if latest collection exists. @@ -62,7 +61,7 @@ func (t *dropCollectionTask) validate(ctx context.Context) error { // Check if all aliases have been dropped. if len(aliases) > 0 { - err = fmt.Errorf("unable to drop the collection [%s] because it has associated aliases %v, please remove all aliases before dropping the collection", t.Req.GetCollectionName(), aliases) + err = merr.WrapErrParameterInvalidMsg("unable to drop the collection [%s] because it has associated aliases %v, please remove all aliases before dropping the collection", t.Req.GetCollectionName(), aliases) log.Ctx(ctx).Warn("drop collection failed", zap.String("database", t.Req.GetDbName()), zap.Error(err)) return err } diff --git a/internal/rootcoord/key_manager.go b/internal/rootcoord/key_manager.go index c4494900a8..58ce01aaeb 100644 --- a/internal/rootcoord/key_manager.go +++ b/internal/rootcoord/key_manager.go @@ -18,7 +18,6 @@ package rootcoord import ( "context" - "fmt" "strconv" "github.com/samber/lo" @@ -29,6 +28,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/common" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/util" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type KeyManager struct { @@ -55,7 +55,7 @@ func NewKeyManager( func (km *KeyManager) GetRevokedDatabases() ([]int64, error) { currentStates, err := hookutil.GetEzStates() if err != nil { - return nil, fmt.Errorf("failed to get cipher states: %w", err) + return nil, merr.Wrap(err, "failed to get cipher states") } abnormalDB := make(map[int64]struct{}) @@ -88,7 +88,7 @@ func (km *KeyManager) getDatabaseByEzID(ezID int64) (*model.Database, error) { // verify the ezID matches the retrieved DB if db.GetProperty(common.EncryptionEzIDKey) != strconv.FormatInt(ezID, 10) { - return nil, fmt.Errorf("db for ezID %d not found", ezID) + return nil, merr.WrapErrServiceInternalMsg("db for ezID %d not found", ezID) } return db, nil diff --git a/internal/rootcoord/meta_rbac.go b/internal/rootcoord/meta_rbac.go index bb53e08ff3..61791d0a20 100644 --- a/internal/rootcoord/meta_rbac.go +++ b/internal/rootcoord/meta_rbac.go @@ -10,18 +10,14 @@ import ( ) var ( - errEmptyUsername = errors.New("username is empty") errUserNotFound = errors.New("user not found") errUserAlreadyExists = errors.New("user already exists") - errEmptyRoleName = errors.New("role name is empty") errRoleAlreadyExists = errors.New("role already exists") errRoleNotExists = errors.New("role not exists") errEmptyRBACMeta = errors.New("rbac meta is empty") errNotCustomPrivilegeGroup = errors.New("not a custom privilege group") - - errEmptyPrivilegeGroupName = errors.New("privilege group name is empty") ) type RBACChecker interface { diff --git a/internal/rootcoord/meta_table.go b/internal/rootcoord/meta_table.go index a97084bfef..b4974d1588 100644 --- a/internal/rootcoord/meta_table.go +++ b/internal/rootcoord/meta_table.go @@ -412,7 +412,7 @@ func (mt *MetaTable) CheckIfDatabaseCreatable(ctx context.Context, req *milvuspb if _, ok := mt.dbName2Meta[dbName]; ok || mt.aliases.exist(dbName) || mt.names.exist(dbName) { // TODO: idempotency check here. - return fmt.Errorf("database already exist: %s", dbName) + return merr.WrapErrParameterInvalidMsg("database already exist: %s", dbName) } cfgMaxDatabaseNum := Params.RootCoordCfg.MaxDatabaseNum.GetAsInt() @@ -466,7 +466,7 @@ func (mt *MetaTable) CheckIfDatabaseDroppable(ctx context.Context, req *milvuspb defer mt.ddLock.RUnlock() if dbName == util.DefaultDBName { - return errors.New("can not drop default database") + return merr.WrapErrParameterInvalidMsg("can not drop default database") } if _, err := mt.getDatabaseByNameInternal(ctx, dbName, typeutil.MaxTimestamp); err != nil { @@ -479,7 +479,7 @@ func (mt *MetaTable) CheckIfDatabaseDroppable(ctx context.Context, req *milvuspb return err } if len(colls) > 0 { - return fmt.Errorf("database:%s not empty, must drop all collections before drop database", dbName) + return merr.WrapErrParameterInvalidMsg("database:%s not empty, must drop all collections before drop database", dbName) } return nil } @@ -525,7 +525,7 @@ func (mt *MetaTable) getDatabaseByIDInternal(ctx context.Context, dbID int64, ts return db, nil } } - return nil, fmt.Errorf("database dbID:%d not found", dbID) + return nil, merr.WrapErrDatabaseNotFound(dbID) } func (mt *MetaTable) GetDatabaseByName(ctx context.Context, dbName string, ts Timestamp) (*model.Database, error) { @@ -557,7 +557,7 @@ func (mt *MetaTable) AddCollection(ctx context.Context, coll *model.Collection) // 1, idempotency check was already done outside; // 2, no need to check time travel logic, since ts should always be the latest; if coll.State != pb.CollectionState_CollectionCreated { - return fmt.Errorf("collection state should be created, collection name: %s, collection id: %d, state: %s", coll.Name, coll.CollectionID, coll.State) + return merr.WrapErrServiceInternalMsg("collection state should be created, collection name: %s, collection id: %d, state: %s", coll.Name, coll.CollectionID, coll.State) } // check if there's a collection meta with the same collection id. @@ -634,7 +634,7 @@ func (mt *MetaTable) DropCollection(ctx context.Context, collectionID UniqueID, db, err := mt.getDatabaseByIDInternal(ctx, coll.DBID, typeutil.MaxTimestamp) if err != nil { - return fmt.Errorf("dbID not found for collection:%d", collectionID) + return merr.Wrapf(err, "dbID not found for collection:%d", collectionID) } pn := coll.GetPartitionNum(true) @@ -716,7 +716,7 @@ func (mt *MetaTable) RemoveCollection(ctx context.Context, collectionID UniqueID return nil } if coll.State != pb.CollectionState_CollectionDropping { - return fmt.Errorf("remove collection which state is not dropping, collectionID: %d, state: %s", collectionID, coll.State.String()) + return merr.WrapErrServiceInternalMsg("remove collection which state is not dropping, collectionID: %d, state: %s", collectionID, coll.State.String()) } ctx1 := contextutil.WithTenantID(ctx, Params.CommonCfg.ClusterName.GetValue()) @@ -1186,27 +1186,27 @@ func (mt *MetaTable) CheckIfCollectionRenamable(ctx context.Context, dbName stri // get target db targetDB, ok := mt.dbName2Meta[newDBName] if !ok { - return fmt.Errorf("target database:%s not found", newDBName) + return merr.WrapErrDatabaseNotFound(newDBName) } // old collection should not be an alias _, ok = mt.aliases.get(dbName, oldName) if ok { log.Warn("unsupported use a alias to rename collection") - return fmt.Errorf("unsupported use an alias to rename collection, alias:%s", oldName) + return merr.WrapErrParameterInvalidMsg("unsupported use an alias to rename collection, alias:%s", oldName) } _, ok = mt.aliases.get(newDBName, newName) if ok { log.Warn("cannot rename collection to an existing alias") - return fmt.Errorf("cannot rename collection to an existing alias: %s", newName) + return merr.WrapErrAsInputError(merr.WrapErrAliasCollectionNameConflict(newDBName, newName)) } // check new collection already exists coll, err := mt.getCollectionByNameInternal(ctx, newDBName, newName, typeutil.MaxTimestamp, false) if coll != nil { log.Warn("duplicated new collection name, already taken by another collection or alias.") - return fmt.Errorf("duplicated new collection name %s:%s with other collection name or alias", newDBName, newName) + return merr.WrapErrParameterInvalidMsg("duplicated new collection name %s:%s with other collection name or alias", newDBName, newName) } if err != nil && !errors.Is(err, merr.ErrCollectionNotFound) { log.Warn("fail to check if new collection name is already taken", zap.Error(err)) @@ -1223,7 +1223,7 @@ func (mt *MetaTable) CheckIfCollectionRenamable(ctx context.Context, dbName stri // unsupported rename collection while the collection has aliases aliases := mt.listAliasesByID(oldColl.CollectionID) if len(aliases) > 0 && oldColl.DBID != targetDB.ID { - return errors.New("fail to rename db name, must drop all aliases of this collection before rename") + return merr.WrapErrParameterInvalidMsg("fail to rename db name, must drop all aliases of this collection before rename") } return nil } @@ -1279,11 +1279,11 @@ func (mt *MetaTable) AddPartition(ctx context.Context, partition *model.Partitio coll, ok := mt.collID2Meta[partition.CollectionID] if !ok || !coll.Available() { - return fmt.Errorf("collection not exists: %d", partition.CollectionID) + return merr.WrapErrServiceInternalMsg("collection not exists: %d", partition.CollectionID) } if partition.State != pb.PartitionState_PartitionCreated { - return fmt.Errorf("partition state is not created, collection: %d, partition: %d, state: %s", partition.CollectionID, partition.PartitionID, partition.State) + return merr.WrapErrServiceInternalMsg("partition state is not created, collection: %d, partition: %d, state: %s", partition.CollectionID, partition.PartitionID, partition.State) } // idempotency check here. @@ -1400,7 +1400,7 @@ func (mt *MetaTable) RemovePartition(ctx context.Context, collectionID UniqueID, } partition := coll.Partitions[loc] if partition.State != pb.PartitionState_PartitionDropping { - return fmt.Errorf("remove partition which state is not dropping, collection: %d, partition: %d, state: %s", collectionID, partitionID, partition.State.String()) + return merr.WrapErrServiceInternalMsg("remove partition which state is not dropping, collection: %d, partition: %d, state: %s", collectionID, partitionID, partition.State.String()) } ctx1 := contextutil.WithTenantID(ctx, Params.CommonCfg.ClusterName.GetValue()) @@ -1442,7 +1442,7 @@ func (mt *MetaTable) CheckIfAliasCreatable(ctx context.Context, dbName string, a if collID, ok := mt.names.get(dbName, alias); ok { coll, ok := mt.collID2Meta[collID] if !ok { - return errors.New("meta error, name mapped non-exist collection id") + return merr.WrapErrServiceInternalMsg("meta error, name mapped non-exist collection id") } // allow alias with dropping&dropped if coll.State != pb.CollectionState_CollectionDropping && coll.State != pb.CollectionState_CollectionDropped { @@ -1711,7 +1711,7 @@ func (mt *MetaTable) InitCredential(ctx context.Context) error { func (mt *MetaTable) CheckIfAddCredential(ctx context.Context, credInfo *internalpb.CredentialInfo) error { if funcutil.IsEmptyString(credInfo.GetUsername()) { - return errEmptyUsername + return merr.WrapErrParameterInvalidMsg("username is empty") } mt.permissionLock.RLock() defer mt.permissionLock.RUnlock() @@ -1732,14 +1732,14 @@ func (mt *MetaTable) CheckIfAddCredential(ctx context.Context, credInfo *interna if len(usernames) >= maxUserNum { errMsg := "unable to add user because the number of users has reached the limit" log.Ctx(ctx).Error(errMsg, zap.Int("maxUserNum", maxUserNum)) - return errors.New(errMsg) + return merr.WrapErrServiceQuotaExceeded(errMsg) } return nil } func (mt *MetaTable) CheckIfUpdateCredential(ctx context.Context, credInfo *internalpb.CredentialInfo) error { if funcutil.IsEmptyString(credInfo.GetUsername()) { - return errEmptyUsername + return merr.WrapErrParameterInvalidMsg("username is empty") } mt.permissionLock.RLock() defer mt.permissionLock.RUnlock() @@ -1793,7 +1793,7 @@ func (mt *MetaTable) GetCredential(ctx context.Context, username string) (*inter func (mt *MetaTable) CheckIfDeleteCredential(ctx context.Context, req *milvuspb.DeleteCredentialRequest) error { if funcutil.IsEmptyString(req.GetUsername()) { - return errEmptyUsername + return merr.WrapErrParameterInvalidMsg("username is empty") } mt.permissionLock.RLock() defer mt.permissionLock.RUnlock() @@ -1836,7 +1836,7 @@ func (mt *MetaTable) ListCredentialUsernames(ctx context.Context) (*milvuspb.Lis usernames, err := mt.catalog.ListCredentials(ctx) if err != nil { - return nil, fmt.Errorf("list credential usernames err:%w", err) + return nil, merr.Wrap(err, "failed to list credential usernames") } return &milvuspb.ListCredUsersResponse{Usernames: usernames}, nil } @@ -1844,7 +1844,7 @@ func (mt *MetaTable) ListCredentialUsernames(ctx context.Context) (*milvuspb.Lis // CheckIfCreateRole checks if the role can be created. func (mt *MetaTable) CheckIfCreateRole(ctx context.Context, in *milvuspb.CreateRoleRequest) error { if funcutil.IsEmptyString(in.GetEntity().GetName()) { - return errEmptyRoleName + return merr.WrapErrParameterInvalidMsg("role name is empty") } mt.permissionLock.RLock() defer mt.permissionLock.RUnlock() @@ -1863,7 +1863,7 @@ func (mt *MetaTable) CheckIfCreateRole(ctx context.Context, in *milvuspb.CreateR if len(results) >= Params.ProxyCfg.MaxRoleNum.GetAsInt() { errMsg := "unable to create role because the number of roles has reached the limit" log.Ctx(ctx).Warn(errMsg, zap.Int("max_role_num", Params.ProxyCfg.MaxRoleNum.GetAsInt())) - return errors.New(errMsg) + return merr.WrapErrServiceQuotaExceeded(errMsg) } return nil } @@ -1878,7 +1878,7 @@ func (mt *MetaTable) CreateRole(ctx context.Context, tenant string, entity *milv func (mt *MetaTable) CheckIfDropRole(ctx context.Context, in *milvuspb.DropRoleRequest) error { if funcutil.IsEmptyString(in.GetRoleName()) { - return errEmptyRoleName + return merr.WrapErrParameterInvalidMsg("role name is empty") } if util.IsBuiltinRole(in.GetRoleName()) { return merr.WrapErrPrivilegeNotPermitted("the role[%s] is a builtin role, which can't be dropped", in.GetRoleName()) @@ -1905,7 +1905,7 @@ func (mt *MetaTable) CheckIfDropRole(ctx context.Context, in *milvuspb.DropRoleR } if len(grantEntities) != 0 { errMsg := "fail to drop the role that it has privileges. Use REVOKE API to revoke privileges" - return errors.New(errMsg) + return merr.WrapErrParameterInvalidMsg(errMsg) } return nil } @@ -1920,10 +1920,10 @@ func (mt *MetaTable) DropRole(ctx context.Context, tenant string, roleName strin func (mt *MetaTable) CheckIfOperateUserRole(ctx context.Context, req *milvuspb.OperateUserRoleRequest) error { if funcutil.IsEmptyString(req.GetUsername()) { - return errors.New("username in the user entity is empty") + return merr.WrapErrParameterInvalidMsg("username in the user entity is empty") } if funcutil.IsEmptyString(req.GetRoleName()) { - return errors.New("role name in the role entity is empty") + return merr.WrapErrParameterInvalidMsg("role name in the role entity is empty") } mt.permissionLock.RLock() defer mt.permissionLock.RUnlock() @@ -1936,8 +1936,10 @@ func (mt *MetaTable) CheckIfOperateUserRole(ctx context.Context, req *milvuspb.O } if req.Type != milvuspb.OperateUserRoleType_RemoveUserFromRole { if _, err := mt.catalog.ListUser(ctx, util.DefaultTenant, &milvuspb.UserEntity{Name: req.Username}, false); err != nil { - errMsg := "not found the user, maybe the user isn't existed or internal system error" - return errors.New(errMsg) + if errors.Is(err, merr.ErrIoKeyNotFound) { + return merr.WrapErrParameterInvalidMsg("user %q not found", req.GetUsername()) + } + return merr.Wrap(err, "failed to check user existence") } } return nil @@ -1974,25 +1976,25 @@ func (mt *MetaTable) SelectUser(ctx context.Context, tenant string, entity *milv // OperatePrivilege grant or revoke privilege by setting the operateType param func (mt *MetaTable) OperatePrivilege(ctx context.Context, tenant string, entity *milvuspb.GrantEntity, operateType milvuspb.OperatePrivilegeType) error { if funcutil.IsEmptyString(entity.ObjectName) { - return errors.New("the object name in the grant entity is empty") + return merr.WrapErrParameterInvalidMsg("the object name in the grant entity is empty") } if entity.Object == nil || funcutil.IsEmptyString(entity.Object.Name) { - return errors.New("the object entity in the grant entity is invalid") + return merr.WrapErrParameterInvalidMsg("the object entity in the grant entity is invalid") } if entity.Role == nil || funcutil.IsEmptyString(entity.Role.Name) { - return errors.New("the role entity in the grant entity is invalid") + return merr.WrapErrParameterInvalidMsg("the role entity in the grant entity is invalid") } if entity.Grantor == nil { - return errors.New("the grantor in the grant entity is empty") + return merr.WrapErrParameterInvalidMsg("the grantor in the grant entity is empty") } if entity.Grantor.Privilege == nil || funcutil.IsEmptyString(entity.Grantor.Privilege.Name) { - return errors.New("the privilege name in the grant entity is empty") + return merr.WrapErrParameterInvalidMsg("the privilege name in the grant entity is empty") } if entity.Grantor.User == nil || funcutil.IsEmptyString(entity.Grantor.User.Name) { - return errors.New("the grantor name in the grant entity is empty") + return merr.WrapErrParameterInvalidMsg("the grantor name in the grant entity is empty") } if !funcutil.IsRevoke(operateType) && !funcutil.IsGrant(operateType) { - return errors.New("the operate type in the grant entity is invalid") + return merr.WrapErrParameterInvalidMsg("the operate type in the grant entity is invalid") } if entity.DbName == "" { entity.DbName = util.DefaultDBName @@ -2010,11 +2012,11 @@ func (mt *MetaTable) OperatePrivilege(ctx context.Context, tenant string, entity func (mt *MetaTable) SelectGrant(ctx context.Context, tenant string, entity *milvuspb.GrantEntity) ([]*milvuspb.GrantEntity, error) { var entities []*milvuspb.GrantEntity if entity == nil { - return entities, errors.New("the grant entity is nil") + return entities, merr.WrapErrParameterInvalidMsg("the grant entity is nil") } if entity.Role == nil || funcutil.IsEmptyString(entity.Role.Name) { - return entities, errors.New("the role entity in the grant entity is invalid") + return entities, merr.WrapErrParameterInvalidMsg("the role entity in the grant entity is invalid") } if entity.DbName == "" { entity.DbName = util.DefaultDBName @@ -2028,7 +2030,7 @@ func (mt *MetaTable) SelectGrant(ctx context.Context, tenant string, entity *mil func (mt *MetaTable) DropGrant(ctx context.Context, tenant string, role *milvuspb.RoleEntity) error { if role == nil || funcutil.IsEmptyString(role.Name) { - return errors.New("the role entity is invalid when dropping the grant") + return merr.WrapErrParameterInvalidMsg("the role entity is invalid when dropping the grant") } mt.permissionLock.Lock() defer mt.permissionLock.Unlock() @@ -2075,7 +2077,7 @@ func (mt *MetaTable) CheckIfRBACRestorable(ctx context.Context, req *milvuspb.Re existRoleAfterRestoreMap := lo.SliceToMap(existRoles, func(entity *milvuspb.RoleResult) (string, struct{}) { return entity.GetRole().GetName(), struct{}{} }) for _, role := range meta.GetRoles() { if _, ok := existRoleMap[role.GetName()]; ok { - return errors.Newf("role [%s] already exists", role.GetName()) + return merr.WrapErrParameterInvalidMsg("role [%s] already exists", role.GetName()) } existRoleAfterRestoreMap[role.GetName()] = struct{}{} } @@ -2089,7 +2091,7 @@ func (mt *MetaTable) CheckIfRBACRestorable(ctx context.Context, req *milvuspb.Re existPrivGroupAfterRestoreMap := lo.SliceToMap(existPrivGroups, func(entity *milvuspb.PrivilegeGroupInfo) (string, struct{}) { return entity.GetGroupName(), struct{}{} }) for _, group := range meta.GetPrivilegeGroups() { if _, ok := existPrivGroupMap[group.GetGroupName()]; ok { - return errors.Newf("privilege group [%s] already exists", group.GetGroupName()) + return merr.WrapErrParameterInvalidMsg("privilege group [%s] already exists", group.GetGroupName()) } existPrivGroupAfterRestoreMap[group.GetGroupName()] = struct{}{} } @@ -2101,7 +2103,7 @@ func (mt *MetaTable) CheckIfRBACRestorable(ctx context.Context, req *milvuspb.Re continue } if _, ok := existPrivGroupAfterRestoreMap[privName]; !ok && !util.IsPrivilegeNameDefined(privName) { - return errors.Newf("privilege [%s] does not exist", privName) + return merr.WrapErrParameterInvalidMsg("privilege [%s] does not exist", privName) } } @@ -2113,13 +2115,13 @@ func (mt *MetaTable) CheckIfRBACRestorable(ctx context.Context, req *milvuspb.Re existUserMap := lo.SliceToMap(existUser, func(entity *milvuspb.UserResult) (string, struct{}) { return entity.GetUser().GetName(), struct{}{} }) for _, user := range meta.GetUsers() { if _, ok := existUserMap[user.GetUser()]; ok { - return errors.Newf("user [%s] already exists", user.GetUser()) + return merr.WrapErrParameterInvalidMsg("user [%s] already exists", user.GetUser()) } // check if user-role can be restored for _, role := range user.GetRoles() { if _, ok := existRoleAfterRestoreMap[role.GetName()]; !ok { - return errors.Newf("role [%s] does not exist", role.GetName()) + return merr.WrapErrParameterInvalidMsg("role [%s] does not exist", role.GetName()) } } } @@ -2149,7 +2151,7 @@ func (mt *MetaTable) IsCustomPrivilegeGroup(ctx context.Context, groupName strin func (mt *MetaTable) CheckIfPrivilegeGroupCreatable(ctx context.Context, req *milvuspb.CreatePrivilegeGroupRequest) error { if funcutil.IsEmptyString(req.GetGroupName()) { - return errEmptyPrivilegeGroupName + return merr.WrapErrParameterInvalidMsg("privilege group name is empty") } mt.permissionLock.RLock() defer mt.permissionLock.RUnlock() @@ -2180,7 +2182,7 @@ func (mt *MetaTable) CreatePrivilegeGroup(ctx context.Context, groupName string) func (mt *MetaTable) CheckIfPrivilegeGroupDropable(ctx context.Context, req *milvuspb.DropPrivilegeGroupRequest) error { if funcutil.IsEmptyString(req.GetGroupName()) { - return errEmptyPrivilegeGroupName + return merr.WrapErrParameterInvalidMsg("privilege group name is empty") } mt.permissionLock.RLock() defer mt.permissionLock.RUnlock() @@ -2211,7 +2213,7 @@ func (mt *MetaTable) CheckIfPrivilegeGroupDropable(ctx context.Context, req *mil } for _, grant := range grants { if grant.Grantor.Privilege.Name == req.GetGroupName() { - return errors.Newf("privilege group [%s] is used by role [%s], Use REVOKE API to revoke it first", req.GetGroupName(), role.GetName()) + return merr.WrapErrParameterInvalidMsg("privilege group [%s] is used by role [%s], Use REVOKE API to revoke it first", req.GetGroupName(), role.GetName()) } } } @@ -2235,7 +2237,7 @@ func (mt *MetaTable) ListPrivilegeGroups(ctx context.Context) ([]*milvuspb.Privi // CheckIfPrivilegeGroupAlterable checks if the privilege group can be altered. func (mt *MetaTable) CheckIfPrivilegeGroupAlterable(ctx context.Context, req *milvuspb.OperatePrivilegeGroupRequest) error { if funcutil.IsEmptyString(req.GetGroupName()) { - return errEmptyPrivilegeGroupName + return merr.WrapErrParameterInvalidMsg("privilege group name is empty") } mt.permissionLock.RLock() defer mt.permissionLock.RUnlock() @@ -2301,7 +2303,7 @@ func (mt *MetaTable) OperatePrivilegeGroup(ctx context.Context, groupName string } default: log.Ctx(ctx).Warn("unsupported operate type", zap.Any("operate_type", operateType)) - return fmt.Errorf("unsupported operate type: %v", operateType) + return merr.WrapErrParameterInvalidMsg("unsupported operate type: %v", operateType) } mergedPrivs := lo.Map(lo.Keys(privSet), func(priv string, _ int) *milvuspb.PrivilegeEntity { @@ -2316,7 +2318,7 @@ func (mt *MetaTable) OperatePrivilegeGroup(ctx context.Context, groupName string func (mt *MetaTable) GetPrivilegeGroupRoles(ctx context.Context, groupName string) ([]*milvuspb.RoleEntity, error) { if funcutil.IsEmptyString(groupName) { - return nil, errors.New("the privilege group name is empty") + return nil, merr.WrapErrParameterInvalidMsg("the privilege group name is empty") } mt.permissionLock.RLock() defer mt.permissionLock.RUnlock() @@ -2356,7 +2358,7 @@ func (mt *MetaTable) AddFileResource(ctx context.Context, resource *internalpb.F if old.Path == resource.Path { return nil } - return errors.Newf("file resource %s already exists", resource.Name) + return merr.WrapErrParameterInvalidMsg("file resource %s already exists", resource.Name) } err := mt.catalog.SaveFileResource(ctx, resource, mt.fileResourceVersion+1) @@ -2376,7 +2378,7 @@ func (mt *MetaTable) RemoveFileResource(ctx context.Context, name string) (error if resource, ok := mt.fileResourceName2Meta[name]; ok { if mt.fileResourceRefCnt[resource.Id] > 0 { - return errors.Newf("file resource %s is still in use: %d", resource.Name, mt.fileResourceRefCnt[resource.Id]), false + return merr.WrapErrParameterInvalidMsg("file resource %s is still in use: %d", resource.Name, mt.fileResourceRefCnt[resource.Id]), false } err := mt.catalog.RemoveFileResource(ctx, resource.Id, mt.fileResourceVersion+1) diff --git a/internal/rootcoord/name_db.go b/internal/rootcoord/name_db.go index 30a1dd1dee..80ef97a9dc 100644 --- a/internal/rootcoord/name_db.go +++ b/internal/rootcoord/name_db.go @@ -17,11 +17,10 @@ package rootcoord import ( - "fmt" - "golang.org/x/exp/maps" "github.com/milvus-io/milvus/pkg/v3/util" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -86,7 +85,7 @@ func (n *nameDb) listCollections(dbName string) map[string]UniqueID { func (n *nameDb) listCollectionID(dbName string) ([]typeutil.UniqueID, error) { name2ID, ok := n.db2Name2ID[dbName] if !ok { - return nil, fmt.Errorf("database not exist: %s", dbName) + return nil, merr.WrapErrDatabaseNotFound(dbName) } return maps.Values(name2ID), nil } diff --git a/internal/rootcoord/quota_center.go b/internal/rootcoord/quota_center.go index aa5097b73e..2d025bdfdf 100644 --- a/internal/rootcoord/quota_center.go +++ b/internal/rootcoord/quota_center.go @@ -943,7 +943,7 @@ func (q *QuotaCenter) calculateWriteRates() error { } collectionLimiter := q.rateLimiter.GetCollectionLimiters(dbID, collection) if collectionLimiter == nil { - return fmt.Errorf("collection limiter not found: %d", collection) + return merr.WrapErrServiceInternalMsg("collection limiter not found: %d", collection) } limiter := collectionLimiter.GetLimiters() @@ -1439,7 +1439,7 @@ func (q *QuotaCenter) getCollectionMaxLimit(rt internalpb.RateType, collectionID case internalpb.RateType_DQLQuery: return Limit(getCollectionRateLimitConfig(collectionProps, common.CollectionQueryRateMaxKey)), nil default: - return 0, fmt.Errorf("unsupportd rate type:%s", rt.String()) + return 0, merr.WrapErrServiceInternalMsg("unsupportd rate type:%s", rt.String()) } } diff --git a/internal/rootcoord/rbac_task.go b/internal/rootcoord/rbac_task.go index 3e67cd8a04..170ce8f954 100644 --- a/internal/rootcoord/rbac_task.go +++ b/internal/rootcoord/rbac_task.go @@ -21,7 +21,6 @@ package rootcoord import ( "context" - "github.com/cockroachdb/errors" "github.com/samber/lo" "go.uber.org/zap" "google.golang.org/protobuf/proto" @@ -32,6 +31,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/proxypb" "github.com/milvus-io/milvus/pkg/v3/util" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -52,7 +52,7 @@ func executeOperatePrivilegeTaskSteps(ctx context.Context, core *Core, entity *m } return nil }(); err != nil { - return errors.Wrap(err, "failed to operate the privilege") + return merr.Wrap(err, "failed to operate the privilege") } if err := func() error { @@ -66,7 +66,7 @@ func executeOperatePrivilegeTaskSteps(ctx context.Context, core *Core, entity *m opType = int32(typeutil.CacheRevokePrivilege) default: log.Ctx(ctx).Warn("invalid operate type for the OperatePrivilege api", zap.Any("operate_type", operateType)) - return errors.New("invalid operate type for the OperatePrivilege api") + return merr.WrapErrParameterInvalidMsg("invalid operate type for the OperatePrivilege api") } grants := []*milvuspb.GrantEntity{entity} @@ -111,7 +111,7 @@ func executeOperatePrivilegeTaskSteps(ctx context.Context, core *Core, entity *m } return nil }(); err != nil { - return errors.Wrap(err, "failed to refresh policy info cache") + return merr.Wrap(err, "failed to refresh policy info cache") } return nil } @@ -148,7 +148,7 @@ func executeOperatePrivilegeGroupTaskSteps(ctx context.Context, core *Core, in * newPrivs, _ := lo.Difference(v, in.Privileges) newGroups[k] = newPrivs default: - return errors.New("invalid operate type") + return merr.WrapErrParameterInvalidMsg("invalid operate type") } } @@ -218,11 +218,11 @@ func executeOperatePrivilegeGroupTaskSteps(ctx context.Context, core *Core, in * } return nil }(); err != nil { - return errors.Wrap(err, "failed to refresh policy info cache") + return merr.Wrap(err, "failed to refresh policy info cache") } if err := core.meta.OperatePrivilegeGroup(ctx, in.GroupName, in.Privileges, operateType); err != nil && !common.IsIgnorableError(err) { log.Ctx(ctx).Warn("fail to operate privilege group", zap.Error(err)) - return errors.Wrap(err, "failed to operate privilege group") + return merr.Wrap(err, "failed to operate privilege group") } return nil } diff --git a/internal/rootcoord/root_coord.go b/internal/rootcoord/root_coord.go index b9abc740ad..acfa336ebf 100644 --- a/internal/rootcoord/root_coord.go +++ b/internal/rootcoord/root_coord.go @@ -88,7 +88,7 @@ func (c *Core) validateResourceGroups(ctx context.Context, properties []*commonp case "collection": rgs, err = common.CollectionLevelResourceGroups(properties) default: - return fmt.Errorf("invalid level %s for resource group validation", level) + return merr.WrapErrParameterInvalidMsg("invalid level %s for resource group validation", level) } if err != nil || len(rgs) == 0 { @@ -364,7 +364,7 @@ func (c *Core) SetTiKVClient(client *txnkv.Client) { func (c *Core) SetSession(session sessionutil.SessionInterface) error { c.session = session if c.session == nil { - return errors.New("session is nil, the etcd client connection may have failed") + return merr.WrapErrServiceNotReadyMsg("session is nil, the etcd client connection may have failed") } return nil } @@ -404,7 +404,7 @@ func (c *Core) initMetaTable(initCtx context.Context) error { kvmetastore.StartLegacyTombstoneGC(c.ctx, metaKV) catalog = kvmetastore.NewCatalog(metaKV) default: - return retry.Unrecoverable(fmt.Errorf("not supported meta store: %s", Params.MetaStoreCfg.MetaStoreType.GetValue())) + return retry.Unrecoverable(merr.WrapErrServiceInternalMsg("not supported meta store: %s", Params.MetaStoreCfg.MetaStoreType.GetValue())) } if c.meta, err = NewMetaTable(c.ctx, catalog, c.tsoAllocator); err != nil { @@ -584,7 +584,7 @@ func (c *Core) initRbac(initCtx context.Context) error { for _, role := range util.DefaultRoles { err = c.meta.CreateRole(initCtx, util.DefaultTenant, &milvuspb.RoleEntity{Name: role}) if err != nil && !common.IsIgnorableError(err) { - return errors.Wrap(err, "failed to create role") + return merr.Wrap(err, "failed to create role") } } @@ -624,7 +624,7 @@ func (c *Core) initPublicRolePrivilege(initCtx context.Context) error { }, }, milvuspb.OperatePrivilegeType_Grant) if err != nil && !common.IsIgnorableError(err) { - return errors.Wrap(err, "failed to grant global privilege") + return merr.Wrap(err, "failed to grant global privilege") } } for _, collectionPrivilege := range collectionPrivileges { @@ -639,7 +639,7 @@ func (c *Core) initPublicRolePrivilege(initCtx context.Context) error { }, }, milvuspb.OperatePrivilegeType_Grant) if err != nil && !common.IsIgnorableError(err) { - return errors.Wrap(err, "failed to grant collection privilege") + return merr.Wrap(err, "failed to grant collection privilege") } } return nil @@ -652,12 +652,12 @@ func (c *Core) initBuiltinRoles(ctx context.Context) error { err := c.meta.CreateRole(ctx, util.DefaultTenant, &milvuspb.RoleEntity{Name: role}) if err != nil && !common.IsIgnorableError(err) { log.Error("create a builtin role fail", zap.String("roleName", role), zap.Error(err)) - return errors.Wrapf(err, "failed to create a builtin role: %s", role) + return merr.Wrapf(err, "failed to create a builtin role: %s", role) } for _, privilege := range privilegesJSON[util.RoleConfigPrivileges] { privilegeName, err := c.getMetastorePrivilegeName(ctx, privilege[util.RoleConfigPrivilege]) if err != nil { - return errors.Wrapf(err, "failed to get metastore privilege name for: %s", privilege[util.RoleConfigPrivilege]) + return merr.Wrapf(err, "failed to get metastore privilege name for: %s", privilege[util.RoleConfigPrivilege]) } err = c.meta.OperatePrivilege(ctx, util.DefaultTenant, &milvuspb.GrantEntity{ @@ -672,7 +672,7 @@ func (c *Core) initBuiltinRoles(ctx context.Context) error { }, milvuspb.OperatePrivilegeType_Grant) if err != nil && !common.IsIgnorableError(err) { log.Error("grant privilege to builtin role fail", zap.String("roleName", role), zap.Any("privilege", privilege), zap.Error(err)) - return errors.Wrapf(err, "failed to grant privilege: <%s, %s, %s> of db: %s to role: %s", privilege[util.RoleConfigObjectType], privilege[util.RoleConfigObjectName], privilege[util.RoleConfigPrivilege], privilege[util.RoleConfigDBName], role) + return merr.Wrapf(err, "failed to grant privilege: <%s, %s, %s> of db: %s to role: %s", privilege[util.RoleConfigObjectType], privilege[util.RoleConfigObjectName], privilege[util.RoleConfigPrivilege], privilege[util.RoleConfigDBName], role) } } util.BuiltinRoles = append(util.BuiltinRoles, role) @@ -2204,7 +2204,7 @@ func (c *Core) CreateCredential(ctx context.Context, credInfo *internalpb.Creden ctxLog.Warn("CreateCredential failed", zap.Error(err)) metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.FailLabel).Inc() if errors.Is(err, errUserAlreadyExists) { - return merr.StatusWithErrorCode(errors.Errorf("user already exists: %s", credInfo.Username), commonpb.ErrorCode_CreateCredentialFailure), nil + return merr.StatusWithErrorCode(merr.WrapErrParameterInvalidMsg("user already exists: %s", credInfo.Username), commonpb.ErrorCode_CreateCredentialFailure), nil } return merr.StatusWithErrorCode(err, commonpb.ErrorCode_CreateCredentialFailure), nil } @@ -2347,7 +2347,7 @@ func (c *Core) CreateRole(ctx context.Context, in *milvuspb.CreateRoleRequest) ( ctxLog.Warn("fail to create role", zap.Error(err)) metrics.RootCoordDDLReqCounter.WithLabelValues(method, metrics.FailLabel).Inc() if errors.Is(err, errRoleAlreadyExists) { - return merr.StatusWithErrorCode(errors.Newf("role [%s] already exists", in.GetEntity()), commonpb.ErrorCode_CreateRoleFailure), nil + return merr.StatusWithErrorCode(merr.WrapErrParameterInvalidMsg("role [%s] already exists", in.GetEntity()), commonpb.ErrorCode_CreateRoleFailure), nil } return merr.StatusWithErrorCode(err, commonpb.ErrorCode_CreateRoleFailure), nil } @@ -2380,7 +2380,7 @@ func (c *Core) DropRole(ctx context.Context, in *milvuspb.DropRoleRequest) (*com if err := c.broadcastDropRole(ctx, in); err != nil { ctxLog.Warn("fail to drop role", zap.Error(err)) if errors.Is(err, errRoleNotExists) { - return merr.StatusWithErrorCode(errors.New("not found the role, maybe the role isn't existed or internal system error"), commonpb.ErrorCode_DropRoleFailure), nil + return merr.StatusWithErrorCode(merr.WrapErrParameterInvalidMsg("not found the role, maybe the role isn't existed or internal system error"), commonpb.ErrorCode_DropRoleFailure), nil } return merr.StatusWithErrorCode(err, commonpb.ErrorCode_DropRoleFailure), nil } @@ -2412,7 +2412,7 @@ func (c *Core) OperateUserRole(ctx context.Context, in *milvuspb.OperateUserRole if err := c.broadcastOperateUserRole(ctx, in); err != nil { ctxLog.Warn("fail to operate the user and role", zap.Error(err)) if errors.Is(err, errRoleNotExists) { - return merr.StatusWithErrorCode(errors.New("not found the role, maybe the role isn't existed or internal system error"), commonpb.ErrorCode_OperateUserRoleFailure), nil + return merr.StatusWithErrorCode(merr.WrapErrParameterInvalidMsg("not found the role, maybe the role isn't existed or internal system error"), commonpb.ErrorCode_OperateUserRoleFailure), nil } return merr.StatusWithErrorCode(err, commonpb.ErrorCode_OperateUserRoleFailure), nil } @@ -2448,7 +2448,7 @@ func (c *Core) SelectRole(ctx context.Context, in *milvuspb.SelectRoleRequest) ( errMsg := "fail to select the role to check the role name" ctxLog.Warn(errMsg, zap.Error(err)) return &milvuspb.SelectRoleResponse{ - Status: merr.StatusWithErrorCode(errors.New(errMsg), commonpb.ErrorCode_SelectRoleFailure), + Status: merr.StatusWithErrorCode(merr.Wrap(err, errMsg), commonpb.ErrorCode_SelectRoleFailure), }, nil } } @@ -2457,7 +2457,7 @@ func (c *Core) SelectRole(ctx context.Context, in *milvuspb.SelectRoleRequest) ( errMsg := "fail to select the role" ctxLog.Warn(errMsg, zap.Error(err)) return &milvuspb.SelectRoleResponse{ - Status: merr.StatusWithErrorCode(errors.New(errMsg), commonpb.ErrorCode_SelectRoleFailure), + Status: merr.StatusWithErrorCode(merr.Wrap(err, errMsg), commonpb.ErrorCode_SelectRoleFailure), }, nil } @@ -2495,7 +2495,7 @@ func (c *Core) SelectUser(ctx context.Context, in *milvuspb.SelectUserRequest) ( errMsg := "fail to select the user to check the username" ctxLog.Warn(errMsg, zap.Any("in", in), zap.Error(err)) return &milvuspb.SelectUserResponse{ - Status: merr.StatusWithErrorCode(errors.New(errMsg), commonpb.ErrorCode_SelectUserFailure), + Status: merr.StatusWithErrorCode(merr.Wrap(err, errMsg), commonpb.ErrorCode_SelectUserFailure), }, nil } } @@ -2504,7 +2504,7 @@ func (c *Core) SelectUser(ctx context.Context, in *milvuspb.SelectUserRequest) ( errMsg := "fail to select the user" ctxLog.Warn(errMsg, zap.Error(err)) return &milvuspb.SelectUserResponse{ - Status: merr.StatusWithErrorCode(errors.New(errMsg), commonpb.ErrorCode_SelectUserFailure), + Status: merr.StatusWithErrorCode(merr.Wrap(err, errMsg), commonpb.ErrorCode_SelectUserFailure), }, nil } @@ -2519,24 +2519,30 @@ func (c *Core) SelectUser(ctx context.Context, in *milvuspb.SelectUserRequest) ( func (c *Core) isValidRole(ctx context.Context, entity *milvuspb.RoleEntity) error { if entity == nil { - return errors.New("the role entity is nil") + return merr.WrapErrParameterInvalidMsg("the role entity is nil") } if entity.Name == "" { - return errors.New("the name in the role entity is empty") + return merr.WrapErrParameterInvalidMsg("the name in the role entity is empty") } if _, err := c.meta.SelectRole(ctx, util.DefaultTenant, &milvuspb.RoleEntity{Name: entity.Name}, false); err != nil { log.Warn("fail to select the role", zap.String("role_name", entity.Name), zap.Error(err)) - return errors.New("not found the role, maybe the role isn't existed or internal system error") + if errors.Is(err, merr.ErrIoKeyNotFound) { + // The caller named a non-existent role; translate the storage + // not-found into an input error instead of leaking the internal + // io-key-not-found code to the client. + return merr.WrapErrParameterInvalidMsg("the role %q does not exist", entity.Name) + } + return merr.Wrap(err, "fail to validate role") } return nil } func (c *Core) isValidObject(entity *milvuspb.ObjectEntity) error { if entity == nil { - return errors.New("the object entity is nil") + return merr.WrapErrParameterInvalidMsg("the object entity is nil") } if _, ok := commonpb.ObjectType_value[entity.Name]; !ok { - return fmt.Errorf("not found the object type[name: %s], supported the object types: %v", entity.Name, lo.Keys(commonpb.ObjectType_value)) + return merr.WrapErrParameterInvalidMsg("not found the object type[name: %s], supported the object types: %v", entity.Name, lo.Keys(commonpb.ObjectType_value)) } return nil } @@ -2550,16 +2556,16 @@ func (c *Core) isValidPrivilege(ctx context.Context, privilegeName string, objec return err } if customPrivGroup { - return fmt.Errorf("can not operate the custom privilege group [%s]", privilegeName) + return merr.WrapErrParameterInvalidMsg("can not operate the custom privilege group [%s]", privilegeName) } if lo.Contains(Params.RbacConfig.GetDefaultPrivilegeGroupNames(), privilegeName) { - return fmt.Errorf("can not operate the built-in privilege group [%s]", privilegeName) + return merr.WrapErrParameterInvalidMsg("can not operate the built-in privilege group [%s]", privilegeName) } // check object privileges for built-in privileges if util.IsPrivilegeNameDefined(privilegeName) { privileges, ok := util.ObjectPrivileges[object] if !ok { - return fmt.Errorf("not found the object type[name: %s], supported the object types: %v", object, lo.Keys(commonpb.ObjectType_value)) + return merr.WrapErrParameterInvalidMsg("not found the object type[name: %s], supported the object types: %v", object, lo.Keys(commonpb.ObjectType_value)) } for _, privilege := range privileges { if privilege == privilegeName { @@ -2567,7 +2573,7 @@ func (c *Core) isValidPrivilege(ctx context.Context, privilegeName string, objec } } } - return fmt.Errorf("not found the privilege name[%s] in object[%s]", privilegeName, object) + return merr.WrapErrParameterInvalidMsg("not found the privilege name[%s] in object[%s]", privilegeName, object) } func (c *Core) isValidPrivilegeV2(ctx context.Context, privilegeName string) error { @@ -2584,7 +2590,7 @@ func (c *Core) isValidPrivilegeV2(ctx context.Context, privilegeName string) err if util.IsPrivilegeNameDefined(privilegeName) { return nil } - return fmt.Errorf("not found the privilege name[%s]", privilegeName) + return merr.WrapErrParameterInvalidMsg("not found the privilege name[%s]", privilegeName) } // OperatePrivilege operate the privilege, including grant and revoke @@ -2620,31 +2626,36 @@ func (c *Core) OperatePrivilege(ctx context.Context, in *milvuspb.OperatePrivile func (c *Core) operatePrivilegeCommonCheck(ctx context.Context, in *milvuspb.OperatePrivilegeRequest) error { if in.Type != milvuspb.OperatePrivilegeType_Grant && in.Type != milvuspb.OperatePrivilegeType_Revoke { errMsg := fmt.Sprintf("invalid operate privilege type, current type: %s, valid value: [%s, %s]", in.Type, milvuspb.OperatePrivilegeType_Grant, milvuspb.OperatePrivilegeType_Revoke) - return errors.New(errMsg) + return merr.WrapErrParameterInvalidMsg(errMsg) } if in.Entity == nil { errMsg := "the grant entity in the request is nil" - return errors.New(errMsg) + return merr.WrapErrParameterInvalidMsg(errMsg) } if err := c.isValidObject(in.Entity.Object); err != nil { - return errors.New("the object entity in the request is nil or invalid") + return merr.WrapErrParameterInvalidMsg("the object entity in the request is nil or invalid") } if err := c.isValidRole(ctx, in.Entity.Role); err != nil { return err } entity := in.Entity.Grantor if entity == nil { - return errors.New("the grantor entity is nil") + return merr.WrapErrParameterInvalidMsg("the grantor entity is nil") } if entity.User == nil || entity.User.Name == "" { - return errors.New("the user entity in the grantor entity is nil or empty") + return merr.WrapErrParameterInvalidMsg("the user entity in the grantor entity is nil or empty") } if _, err := c.meta.SelectUser(ctx, util.DefaultTenant, &milvuspb.UserEntity{Name: entity.User.Name}, false); err != nil { log.Ctx(ctx).Warn("fail to select the user", zap.String("username", entity.User.Name), zap.Error(err)) - return errors.New("not found the user, maybe the user isn't existed or internal system error") + if errors.Is(err, merr.ErrIoKeyNotFound) { + // The grantor user does not exist; translate the storage not-found + // into an input error instead of leaking io-key-not-found. + return merr.WrapErrParameterInvalidMsg("the grantor user %q does not exist", entity.User.Name) + } + return merr.Wrap(err, "fail to validate grantor user") } if entity.Privilege == nil { - return errors.New("the privilege entity in the grantor entity is nil") + return merr.WrapErrParameterInvalidMsg("the privilege entity in the grantor entity is nil") } return nil } @@ -2681,7 +2692,7 @@ func (c *Core) validatePrivilegeGroupParams(ctx context.Context, entity string, } return nil default: - return errors.New("not found the privilege level") + return merr.WrapErrParameterInvalidMsg("not found the privilege level") } } @@ -2702,7 +2713,7 @@ func (c *Core) getMetastorePrivilegeName(ctx context.Context, privName string) ( if customGroup { return util.PrivilegeGroupNameForMetastore(privName), nil } - return "", errors.Newf("not found the privilege name [%s] from metastore", privName) + return "", merr.WrapErrParameterInvalidMsg("not found the privilege name [%s] from metastore", privName) } // SelectGrant select grant @@ -2726,7 +2737,7 @@ func (c *Core) SelectGrant(ctx context.Context, in *milvuspb.SelectGrantRequest) errMsg := "the grant entity in the request is nil" ctxLog.Warn(errMsg) return &milvuspb.SelectGrantResponse{ - Status: merr.StatusWithErrorCode(errors.New(errMsg), commonpb.ErrorCode_SelectGrantFailure), + Status: merr.StatusWithErrorCode(merr.WrapErrParameterInvalidMsg(errMsg), commonpb.ErrorCode_SelectGrantFailure), }, nil } if err := c.isValidRole(ctx, in.Entity.Role); err != nil { @@ -2755,7 +2766,7 @@ func (c *Core) SelectGrant(ctx context.Context, in *milvuspb.SelectGrantRequest) errMsg := "fail to select the grant" ctxLog.Warn(errMsg, zap.Error(err)) return &milvuspb.SelectGrantResponse{ - Status: merr.StatusWithErrorCode(errors.New(errMsg), commonpb.ErrorCode_SelectGrantFailure), + Status: merr.StatusWithErrorCode(merr.Wrap(err, errMsg), commonpb.ErrorCode_SelectGrantFailure), }, nil } @@ -2785,7 +2796,7 @@ func (c *Core) ListPolicy(ctx context.Context, in *internalpb.ListPolicyRequest) if err != nil { ctxLog.Error("fail to list policy", zap.Error(err)) return &internalpb.ListPolicyResponse{ - Status: merr.StatusWithErrorCode(fmt.Errorf("fail to list policy: %s", err.Error()), commonpb.ErrorCode_ListPolicyFailure), + Status: merr.StatusWithErrorCode(merr.Wrap(err, "fail to list policy"), commonpb.ErrorCode_ListPolicyFailure), }, nil } // expand privilege groups and turn to policies @@ -2793,7 +2804,7 @@ func (c *Core) ListPolicy(ctx context.Context, in *internalpb.ListPolicyRequest) if err != nil { ctxLog.Error("fail to get privilege groups", zap.Error(err)) return &internalpb.ListPolicyResponse{ - Status: merr.StatusWithErrorCode(fmt.Errorf("fail to get privilege groups: %s", err.Error()), commonpb.ErrorCode_ListPolicyFailure), + Status: merr.StatusWithErrorCode(merr.Wrap(err, "fail to get privilege groups"), commonpb.ErrorCode_ListPolicyFailure), }, nil } groups := lo.SliceToMap(allGroups, func(group *milvuspb.PrivilegeGroupInfo) (string, []*milvuspb.PrivilegeEntity) { @@ -2803,7 +2814,7 @@ func (c *Core) ListPolicy(ctx context.Context, in *internalpb.ListPolicyRequest) if err != nil { ctxLog.Error("fail to expand privilege groups", zap.Error(err)) return &internalpb.ListPolicyResponse{ - Status: merr.StatusWithErrorCode(fmt.Errorf("fail to expand privilege groups: %s", err.Error()), commonpb.ErrorCode_ListPolicyFailure), + Status: merr.StatusWithErrorCode(merr.Wrap(err, "fail to expand privilege groups"), commonpb.ErrorCode_ListPolicyFailure), }, nil } expandPolicies := lo.Map(expandGrants, func(r *milvuspb.GrantEntity, _ int) string { @@ -2814,7 +2825,7 @@ func (c *Core) ListPolicy(ctx context.Context, in *internalpb.ListPolicyRequest) if err != nil { ctxLog.Error("fail to list user-role", zap.Any("in", in), zap.Error(err)) return &internalpb.ListPolicyResponse{ - Status: merr.StatusWithErrorCode(fmt.Errorf("fail to list user-role: %s", err.Error()), commonpb.ErrorCode_ListPolicyFailure), + Status: merr.StatusWithErrorCode(merr.Wrap(err, "fail to list user-role"), commonpb.ErrorCode_ListPolicyFailure), }, nil } @@ -3157,7 +3168,7 @@ func (c *Core) AddFileResource(ctx context.Context, req *milvuspb.AddFileResourc if exist, err := c.storage.Exist(ctx, req.GetPath()); err != nil { return merr.Status(err), nil } else if !exist { - return merr.Status(merr.WrapErrAsInputError(errors.Errorf("file resource path not exist"))), nil + return merr.Status(merr.WrapErrParameterInvalidMsg("file resource path not exist")), nil } id, err := c.tsoAllocator.GenerateTSO(1) @@ -3178,7 +3189,7 @@ func (c *Core) AddFileResource(ctx context.Context, req *milvuspb.AddFileResourc err = c.fileResourceObserver.Sync() if err != nil { c.fileResourceObserver.Notify() - return merr.Status(errors.Wrap(err, "add file resource success but some node sync failed")), nil + return merr.Status(merr.Wrap(err, "add file resource success but some node sync failed")), nil } } @@ -3208,7 +3219,7 @@ func (c *Core) RemoveFileResource(ctx context.Context, req *milvuspb.RemoveFileR err = c.fileResourceObserver.Sync() if err != nil { c.fileResourceObserver.Notify() - return merr.Status(errors.Wrap(err, "remove file resource success but some node sync failed")), nil + return merr.Status(merr.Wrap(err, "remove file resource success but some node sync failed")), nil } } @@ -3510,7 +3521,7 @@ func (c *Core) ClientHeartbeat(ctx context.Context, req *milvuspb.ClientHeartbea if c.telemetryMgr == nil { return &milvuspb.ClientHeartbeatResponse{ - Status: merr.Status(errors.New("telemetry manager not initialized")), + Status: merr.Status(merr.WrapErrServiceInternalMsg("telemetry manager not initialized")), }, nil } @@ -3527,7 +3538,7 @@ func (c *Core) GetClientTelemetry(ctx context.Context, req *milvuspb.GetClientTe if c.telemetryMgr == nil { return &milvuspb.GetClientTelemetryResponse{ - Status: merr.Status(errors.New("telemetry manager not initialized")), + Status: merr.Status(merr.WrapErrServiceInternalMsg("telemetry manager not initialized")), }, nil } @@ -3544,7 +3555,7 @@ func (c *Core) PushClientCommand(ctx context.Context, req *milvuspb.PushClientCo if c.telemetryMgr == nil { return &milvuspb.PushClientCommandResponse{ - Status: merr.Status(errors.New("telemetry manager not initialized")), + Status: merr.Status(merr.WrapErrServiceInternalMsg("telemetry manager not initialized")), }, nil } @@ -3561,7 +3572,7 @@ func (c *Core) DeleteClientCommand(ctx context.Context, req *milvuspb.DeleteClie if c.telemetryMgr == nil { return &milvuspb.DeleteClientCommandResponse{ - Status: merr.Status(errors.New("telemetry manager not initialized")), + Status: merr.Status(merr.WrapErrServiceInternalMsg("telemetry manager not initialized")), }, nil } diff --git a/internal/rootcoord/root_coord_test.go b/internal/rootcoord/root_coord_test.go index 7f6416d60b..166b7ca815 100644 --- a/internal/rootcoord/root_coord_test.go +++ b/internal/rootcoord/root_coord_test.go @@ -413,7 +413,9 @@ func TestRootCoord_DescribeAlias(t *testing.T) { ctx := context.Background() resp, err := c.DescribeAlias(ctx, &milvuspb.DescribeAliasRequest{}) assert.NoError(t, err) - assert.Equal(t, commonpb.ErrorCode_UnexpectedError, resp.GetStatus().GetErrorCode()) + // ParameterMissing (1101) projects to the legacy IllegalArgument like + // every parameter-class error, so old SDKs don't see UnexpectedError. + assert.Equal(t, commonpb.ErrorCode_IllegalArgument, resp.GetStatus().GetErrorCode()) assert.Equal(t, int32(1101), resp.GetStatus().GetCode()) }) @@ -1780,7 +1782,7 @@ func TestCore_getMetastorePrivilegeName(t *testing.T) { meta.EXPECT().IsCustomPrivilegeGroup(mock.Anything, "unknown").Return(false, nil) _, err = c.getMetastorePrivilegeName(context.Background(), "unknown") - assert.Equal(t, err.Error(), "not found the privilege name [unknown] from metastore") + assert.ErrorContains(t, err, "not found the privilege name [unknown] from metastore") } func TestCore_expandPrivilegeGroup(t *testing.T) { diff --git a/internal/rootcoord/telemetry/command_store.go b/internal/rootcoord/telemetry/command_store.go index cb44534129..1976d4adac 100644 --- a/internal/rootcoord/telemetry/command_store.go +++ b/internal/rootcoord/telemetry/command_store.go @@ -21,7 +21,6 @@ import ( "crypto/sha256" "encoding/hex" "encoding/json" - "fmt" "sort" "sync" "time" @@ -411,7 +410,7 @@ func (s *CommandStore) DeleteCommand(ctx context.Context, commandID string) erro if configExists { if err := s.kv.Delete(ctx, s.configPath+commandID); err != nil { - return merr.WrapErrIoFailed(commandID, fmt.Errorf("delete failed: %v", err)) + return merr.WrapErrIoFailed(commandID, merr.WrapErrServiceInternalMsg("delete failed: %v", err)) } } diff --git a/internal/rootcoord/timeticksync.go b/internal/rootcoord/timeticksync.go index 5cf65fb2c5..bb63cffe67 100644 --- a/internal/rootcoord/timeticksync.go +++ b/internal/rootcoord/timeticksync.go @@ -23,7 +23,6 @@ import ( "time" "github.com/blang/semver/v4" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" @@ -36,6 +35,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/mq/msgstream" "github.com/milvus-io/milvus/pkg/v3/proto/internalpb" "github.com/milvus-io/milvus/pkg/v3/util/commonpbutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/timerecord" "github.com/milvus-io/milvus/pkg/v3/util/tsoutil" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" @@ -203,12 +203,12 @@ func (t *timetickSync) updateTimeTick(in *internalpb.ChannelTimeTickMsg, reason return nil } if len(in.Timestamps) != len(in.ChannelNames) { - return errors.New("invalid TimeTickMsg, timestamp and channelname size mismatch") + return merr.WrapErrServiceInternalMsg("invalid TimeTickMsg, timestamp and channelname size mismatch") } prev, ok := t.sess2ChanTsMap[in.Base.SourceID] if !ok { - return fmt.Errorf("skip ChannelTimeTickMsg from un-recognized session %d", in.Base.SourceID) + return merr.WrapErrServiceInternalMsg("skip ChannelTimeTickMsg from un-recognized session %d", in.Base.SourceID) } if in.Base.SourceID == t.sourceID { diff --git a/internal/rootcoord/util.go b/internal/rootcoord/util.go index ea303e9252..6d32c5043e 100644 --- a/internal/rootcoord/util.go +++ b/internal/rootcoord/util.go @@ -99,7 +99,7 @@ func Int64TupleMapToSlice(s map[int]common.Int64Tuple) []common.Int64Tuple { func CheckMsgType(got, expect commonpb.MsgType) error { if got != expect { - return fmt.Errorf("invalid msg type, expect %s, but got %s", expect, got) + return merr.WrapErrServiceInternalMsg("invalid msg type, expect %s, but got %s", expect, got) } return nil } @@ -332,7 +332,7 @@ func CheckTimeTickLagExceeded(ctx context.Context, mixcoord types.MixCoord, maxD errStr += fmt.Sprintf("data max timetick lag:%s on channel:%s", maxLag, maxLagChannel) } if errStr != "" { - return fmt.Errorf("max timetick lag execced threhold: %s", errStr) + return merr.WrapErrServiceInternalMsg("max timetick lag execced threhold: %s", errStr) } return nil @@ -438,7 +438,7 @@ func checkFieldSchema(fieldSchemas []*schemapb.FieldSchema) error { zap.ByteString("data", defVal), zap.Error(err), ) - return merr.WrapErrParameterInvalidMsg(err.Error()) + return merr.WrapErrParameterInvalidErr(err, "invalid default json value, milvus only supports json map") } default: panic("default value unsupport data type") @@ -512,7 +512,7 @@ func getStructSubFieldMaxCapacity(structName string, field *schemapb.FieldSchema } return maxCapacity, nil } - return 0, merr.WrapErrParameterInvalidMsg("type param(%s) should be specified for field %s in struct array field %s", + return 0, merr.WrapErrParameterMissingMsg("type param(%s) should be specified for field %s in struct array field %s", common.MaxCapacityKey, field.GetName(), structName) } @@ -564,11 +564,11 @@ func validateStructArrayFieldDataType(fieldSchemas []*schemapb.StructArrayFieldS } for _, subField := range field.GetFields() { if subField.GetDataType() != schemapb.DataType_Array && subField.GetDataType() != schemapb.DataType_ArrayOfVector { - return fmt.Errorf("fields in StructArrayField can only be array or array of vector, but field %s is %s", subField.Name, subField.DataType.String()) + return merr.WrapErrParameterInvalidMsg("fields in StructArrayField can only be array or array of vector, but field %s is %s", subField.Name, subField.DataType.String()) } if subField.GetElementType() == schemapb.DataType_ArrayOfStruct || subField.GetElementType() == schemapb.DataType_ArrayOfVector || subField.GetElementType() == schemapb.DataType_Array { - return fmt.Errorf("nested array is not supported %s", subField.Name) + return merr.WrapErrParameterInvalidMsg("nested array is not supported for field %s", subField.Name) } if _, ok := schemapb.DataType_name[int32(subField.GetElementType())]; !ok || subField.GetElementType() == schemapb.DataType_None { return merr.WrapErrParameterInvalid("Invalid field", fmt.Sprintf("field data type: %s is not supported", subField.GetElementType())) diff --git a/internal/storage/arrow_util.go b/internal/storage/arrow_util.go index 5f7f13c74c..afbc2d42a9 100644 --- a/internal/storage/arrow_util.go +++ b/internal/storage/arrow_util.go @@ -17,7 +17,6 @@ package storage import ( - "fmt" "strconv" "github.com/apache/arrow/go/v17/arrow" @@ -50,7 +49,7 @@ func appendValueAt(builder array.Builder, a arrow.Array, idx int, defaultValue * case *array.BooleanBuilder: ba, ok := a.(*array.Boolean) if !ok { - return 0, fmt.Errorf("invalid value type %T, expect %T", a.DataType(), builder.Type()) + return 0, merr.WrapErrServiceInternalMsg("invalid value type %T, expect %T", a.DataType(), builder.Type()) } if ba.IsNull(idx) { if defaultValue != nil { @@ -66,7 +65,7 @@ func appendValueAt(builder array.Builder, a arrow.Array, idx int, defaultValue * case *array.Int8Builder: ia, ok := a.(*array.Int8) if !ok { - return 0, fmt.Errorf("invalid value type %T, expect %T", a.DataType(), builder.Type()) + return 0, merr.WrapErrServiceInternalMsg("invalid value type %T, expect %T", a.DataType(), builder.Type()) } if ia.IsNull(idx) { if defaultValue != nil { @@ -82,7 +81,7 @@ func appendValueAt(builder array.Builder, a arrow.Array, idx int, defaultValue * case *array.Int16Builder: ia, ok := a.(*array.Int16) if !ok { - return 0, fmt.Errorf("invalid value type %T, expect %T", a.DataType(), builder.Type()) + return 0, merr.WrapErrServiceInternalMsg("invalid value type %T, expect %T", a.DataType(), builder.Type()) } if ia.IsNull(idx) { if defaultValue != nil { @@ -98,7 +97,7 @@ func appendValueAt(builder array.Builder, a arrow.Array, idx int, defaultValue * case *array.Int32Builder: ia, ok := a.(*array.Int32) if !ok { - return 0, fmt.Errorf("invalid value type %T, expect %T", a.DataType(), builder.Type()) + return 0, merr.WrapErrServiceInternalMsg("invalid value type %T, expect %T", a.DataType(), builder.Type()) } if ia.IsNull(idx) { if defaultValue != nil { @@ -114,7 +113,7 @@ func appendValueAt(builder array.Builder, a arrow.Array, idx int, defaultValue * case *array.Int64Builder: ia, ok := a.(*array.Int64) if !ok { - return 0, fmt.Errorf("invalid value type %T, expect %T", a.DataType(), builder.Type()) + return 0, merr.WrapErrServiceInternalMsg("invalid value type %T, expect %T", a.DataType(), builder.Type()) } if ia.IsNull(idx) { if defaultValue != nil { @@ -130,7 +129,7 @@ func appendValueAt(builder array.Builder, a arrow.Array, idx int, defaultValue * case *array.Float32Builder: fa, ok := a.(*array.Float32) if !ok { - return 0, fmt.Errorf("invalid value type %T, expect %T", a.DataType(), builder.Type()) + return 0, merr.WrapErrServiceInternalMsg("invalid value type %T, expect %T", a.DataType(), builder.Type()) } if fa.IsNull(idx) { if defaultValue != nil { @@ -155,7 +154,7 @@ func appendValueAt(builder array.Builder, a arrow.Array, idx int, defaultValue * } fa, ok := a.(*array.Float64) if !ok { - return 0, fmt.Errorf("invalid value type %T, expect %T", a.DataType(), builder.Type()) + return 0, merr.WrapErrServiceInternalMsg("invalid value type %T, expect %T", a.DataType(), builder.Type()) } if fa.IsNull(idx) { b.AppendNull() @@ -167,7 +166,7 @@ func appendValueAt(builder array.Builder, a arrow.Array, idx int, defaultValue * case *array.StringBuilder: sa, ok := a.(*array.String) if !ok { - return 0, fmt.Errorf("invalid value type %T, expect %T", a.DataType(), builder.Type()) + return 0, merr.WrapErrServiceInternalMsg("invalid value type %T, expect %T", a.DataType(), builder.Type()) } if sa.IsNull(idx) { if defaultValue != nil { @@ -185,7 +184,7 @@ func appendValueAt(builder array.Builder, a arrow.Array, idx int, defaultValue * case *array.BinaryBuilder: ba, ok := a.(*array.Binary) if !ok { - return 0, fmt.Errorf("invalid value type %T, expect %T", a.DataType(), builder.Type()) + return 0, merr.WrapErrServiceInternalMsg("invalid value type %T, expect %T", a.DataType(), builder.Type()) } if ba.IsNull(idx) { // could be internal $meta json @@ -204,7 +203,7 @@ func appendValueAt(builder array.Builder, a arrow.Array, idx int, defaultValue * case *array.FixedSizeBinaryBuilder: ba, ok := a.(*array.FixedSizeBinary) if !ok { - return 0, fmt.Errorf("invalid value type %T, expect %T", a.DataType(), builder.Type()) + return 0, merr.WrapErrServiceInternalMsg("invalid value type %T, expect %T", a.DataType(), builder.Type()) } if ba.IsNull(idx) { b.AppendNull() @@ -218,7 +217,7 @@ func appendValueAt(builder array.Builder, a arrow.Array, idx int, defaultValue * // Handle ListBuilder for ArrayOfVector type la, ok := a.(*array.List) if !ok { - return 0, fmt.Errorf("invalid value type %T, expect %T", a.DataType(), builder.Type()) + return 0, merr.WrapErrServiceInternalMsg("invalid value type %T, expect %T", a.DataType(), builder.Type()) } if la.IsNull(idx) { b.AppendNull() @@ -235,7 +234,7 @@ func appendValueAt(builder array.Builder, a arrow.Array, idx int, defaultValue * case *array.FixedSizeBinaryBuilder: fixedArray, ok := valuesArray.(*array.FixedSizeBinary) if !ok { - return 0, fmt.Errorf("invalid value type %T, expect %T", valuesArray.DataType(), vb.Type()) + return 0, merr.WrapErrServiceInternalMsg("invalid value type %T, expect %T", valuesArray.DataType(), vb.Type()) } for i := start; i < end; i++ { val := fixedArray.Value(int(i)) @@ -243,12 +242,12 @@ func appendValueAt(builder array.Builder, a arrow.Array, idx int, defaultValue * totalSize += uint64(len(val)) } default: - return 0, fmt.Errorf("unsupported value builder type in ListBuilder: %T", valueBuilder) + return 0, merr.WrapErrServiceInternalMsg("unsupported value builder type in ListBuilder: %T", valueBuilder) } return totalSize, nil default: - return 0, fmt.Errorf("unsupported builder type: %T", builder) + return 0, merr.WrapErrServiceInternalMsg("unsupported builder type: %T", builder) } } @@ -259,7 +258,7 @@ func appendValueAt(builder array.Builder, a arrow.Array, idx int, defaultValue * func GenerateEmptyArrayFromSchema(schema *schemapb.FieldSchema, numRows int) (arrow.Array, error) { // if not nullable, return error if !schema.GetNullable() { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("missing field data %s", schema.Name)) + return nil, merr.WrapErrServiceInternalMsg("missing field data %s", schema.Name) } dim, _ := typeutil.GetDim(schema) @@ -327,7 +326,7 @@ func GenerateEmptyArrayFromSchema(schema *schemapb.FieldSchema, numRows int) (ar lo.RepeatBy(numRows, func(_ int) []byte { return schema.GetDefaultValue().GetBytesData() }), nil) default: - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("Unexpected default value type: %s", schema.GetDataType().String())) + return nil, merr.WrapErrServiceInternalMsg("Unexpected default value type: %s", schema.GetDataType().String()) } } else { builder.AppendNulls(numRows) @@ -356,7 +355,7 @@ func (b *RecordBuilder) Append(rec Record, start, end int) error { col := rec.Column(f.FieldID) size, err := appendValueAt(builder, col, offset, f.GetDefaultValue()) if err != nil { - return fmt.Errorf("failed to append value at offset %d for field %s: %w", offset, f.GetName(), err) + return merr.Wrapf(err, "failed to append value at offset %d for field %s", offset, f.GetName()) } b.size += size } diff --git a/internal/storage/binlog_reader.go b/internal/storage/binlog_reader.go index 237b9590ec..cc62ce03b3 100644 --- a/internal/storage/binlog_reader.go +++ b/internal/storage/binlog_reader.go @@ -19,15 +19,14 @@ package storage import ( "bytes" "encoding/binary" - "fmt" "io" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus/internal/util/hookutil" "github.com/milvus-io/milvus/pkg/v3/common" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // BinlogReader is an object to read binlog file. Binlog file's format can be @@ -43,7 +42,7 @@ type BinlogReader struct { // NextEventReader iters all events reader to read the binlog file. func (reader *BinlogReader) NextEventReader() (*EventReader, error) { if reader.isClose { - return nil, errors.New("bin log reader is closed") + return nil, merr.WrapErrStorageMsg("bin log reader is closed") } if reader.buffer.Len() <= 0 { return nil, nil @@ -69,7 +68,7 @@ func readMagicNumber(buffer io.Reader) (int32, error) { return -1, err } if magicNumber != MagicNumber { - return -1, fmt.Errorf("parse magic number failed, expected: %d, actual: %d", MagicNumber, magicNumber) + return -1, merr.WrapErrServiceInternalMsg("parse magic number failed, expected: %d, actual: %d", MagicNumber, magicNumber) } return magicNumber, nil diff --git a/internal/storage/binlog_record_writer.go b/internal/storage/binlog_record_writer.go index aeb1acaf57..8c386ab749 100644 --- a/internal/storage/binlog_record_writer.go +++ b/internal/storage/binlog_record_writer.go @@ -263,7 +263,7 @@ func (pw *PackedBinlogRecordWriter) Write(r Record) error { err := pw.writer.Write(r) if err != nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("write record batch error: %s", err.Error())) + return merr.WrapErrStorage(err, "write record batch error") } pw.writtenUncompressed = pw.writer.GetWrittenUncompressed() return nil @@ -287,7 +287,7 @@ func (pw *PackedBinlogRecordWriter) initWriters(r Record) error { } pw.writer, err = NewPackedRecordWriter(pw.storageConfig.GetBucketName(), paths, pw.schema, pw.bufferSize, pw.multiPartUploadSize, pw.columnGroups, pw.storageConfig, pw.storagePluginContext) if err != nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("can not new packed record writer %s", err.Error())) + return merr.WrapErrStorage(err, "can not new packed record writer") } } return nil @@ -342,7 +342,7 @@ func newPackedBinlogRecordWriter(collectionID, partitionID, segmentID UniqueID, ) (*PackedBinlogRecordWriter, error) { arrowSchema, err := ConvertToArrowSchema(schema, true) if err != nil { - return nil, merr.WrapErrParameterInvalid("convert collection schema [%s] to arrow schema error: %s", schema.Name, err.Error()) + return nil, merr.WrapErrSerializationFailed(err, "convert collection schema [%s] to arrow schema", schema.Name) } writer := &PackedBinlogRecordWriter{ @@ -420,7 +420,7 @@ func (pw *PackedManifestRecordWriter) Write(r Record) error { err := pw.writer.Write(r) if err != nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("write record batch error: %s", err.Error())) + return merr.WrapErrStorage(err, "write record batch error") } pw.writtenUncompressed = pw.writer.GetWrittenUncompressed() return nil @@ -439,7 +439,7 @@ func (pw *PackedManifestRecordWriter) initWriters(r Record) error { pw.basePath = path.Join(pw.storageConfig.GetRootPath(), common.SegmentInsertLogPath, k) pw.writer, err = NewPackedRecordBatchWriter(pw.basePath, pw.schema, pw.bufferSize, pw.multiPartUploadSize, pw.columnGroups, pw.storageConfig, pw.storagePluginContext, writerFormat, schemaBasedFormats) if err != nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("can not new packed record writer %s", err.Error())) + return merr.WrapErrStorage(err, "can not new packed record writer") } } return nil @@ -500,7 +500,7 @@ func (pw *PackedManifestRecordWriter) Close() error { } newManifest, err := packed.CommitManifestUpdates(pw.basePath, packed.ManifestEarliest, pw.storageConfig, updates) if err != nil { - return fmt.Errorf("PackedManifestRecordWriter.Close commit: %w", err) + return merr.Wrap(err, "PackedManifestRecordWriter.Close commit") } pw.manifest = newManifest return nil @@ -522,7 +522,7 @@ func (pw *PackedManifestRecordWriter) appendV3Stats(updates *packed.ManifestUpda } fullPath := path.Join(pw.basePath, fmt.Sprintf("_stats/bloom_filter.%d/%d", pkFieldID, id)) if err := packed.WriteFile(pw.storageConfig, fullPath, statsBlob.Value); err != nil { - return fmt.Errorf("appendV3Stats: failed to write bloom filter stats: %w", err) + return merr.Wrap(err, "appendV3Stats: failed to write bloom filter stats") } updates.Stats = append(updates.Stats, packed.StatEntry{ Key: fmt.Sprintf("bloom_filter.%d", pkFieldID), @@ -542,7 +542,7 @@ func (pw *PackedManifestRecordWriter) appendV3Stats(updates *packed.ManifestUpda } fullPath := path.Join(pw.basePath, fmt.Sprintf("_stats/bm25.%d/%d", fieldID, id)) if err := packed.WriteFile(pw.storageConfig, fullPath, blob.Value); err != nil { - return fmt.Errorf("appendV3Stats: failed to write bm25 stats: %w", err) + return merr.Wrap(err, "appendV3Stats: failed to write bm25 stats") } updates.Stats = append(updates.Stats, packed.StatEntry{ Key: fmt.Sprintf("bm25.%d", fieldID), @@ -560,7 +560,7 @@ func newPackedManifestRecordWriter(collectionID, partitionID, segmentID UniqueID ) (*PackedManifestRecordWriter, error) { arrowSchema, err := ConvertToArrowSchema(schema, true) if err != nil { - return nil, merr.WrapErrParameterInvalid("convert collection schema [%s] to arrow schema error: %s", schema.Name, err.Error()) + return nil, merr.WrapErrSerializationFailed(err, "convert collection schema [%s] to arrow schema", schema.Name) } writer := &PackedManifestRecordWriter{ @@ -640,7 +640,7 @@ func (pw *PackedTextManifestRecordWriter) Write(r Record) error { err := pw.writer.Write(r) if err != nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("write record batch error: %s", err.Error())) + return merr.WrapErrStorage(err, "write record batch error") } pw.writtenUncompressed = pw.writer.GetWrittenUncompressed() return nil @@ -659,7 +659,7 @@ func (pw *PackedTextManifestRecordWriter) initWriters(r Record) error { pw.basePath = path.Join(pw.storageConfig.GetRootPath(), common.SegmentInsertLogPath, k) pw.writer, err = NewPackedTextBatchWriter(pw.storageConfig.GetBucketName(), pw.basePath, pw.schema, pw.bufferSize, pw.multiPartUploadSize, pw.columnGroups, pw.storageConfig, pw.textColumnConfigs, writerFormat, schemaBasedFormats) if err != nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("can not new packed text writer %s", err.Error())) + return merr.WrapErrStorage(err, "can not new packed text writer") } } return nil @@ -715,7 +715,7 @@ func (pw *PackedTextManifestRecordWriter) Close() error { newManifest, err := packed.CommitManifestUpdates(pw.basePath, packed.ManifestEarliest, pw.storageConfig, &packed.ManifestUpdates{NewFiles: out}) if err != nil { - return fmt.Errorf("PackedTextManifestRecordWriter.Close commit: %w", err) + return merr.Wrap(err, "PackedTextManifestRecordWriter.Close commit") } pw.manifest = newManifest return pw.writeStats() @@ -736,7 +736,7 @@ func NewPackedTextManifestRecordWriter( ) (*PackedTextManifestRecordWriter, error) { arrowSchema, err := ConvertToArrowSchema(schema, true) if err != nil { - return nil, merr.WrapErrParameterInvalid("convert collection schema [%s] to arrow schema error: %s", schema.Name, err.Error()) + return nil, merr.WrapErrSerializationFailed(err, "convert collection schema [%s] to arrow schema", schema.Name) } writer := &PackedTextManifestRecordWriter{ diff --git a/internal/storage/binlog_util.go b/internal/storage/binlog_util.go index a8e30b2691..36a48fefe4 100644 --- a/internal/storage/binlog_util.go +++ b/internal/storage/binlog_util.go @@ -1,11 +1,11 @@ package storage import ( - "fmt" "strconv" "strings" "github.com/milvus-io/milvus/pkg/v3/common" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // ParseSegmentIDByBinlog parse segment id from binlog paths @@ -13,7 +13,7 @@ import ( func ParseSegmentIDByBinlog(rootPath, path string) (UniqueID, error) { // check path contains rootPath as prefix if !strings.HasPrefix(path, rootPath) { - return 0, fmt.Errorf("path \"%s\" does not contains rootPath \"%s\"", path, rootPath) + return 0, merr.WrapErrServiceInternalMsg("path \"%s\" does not contains rootPath \"%s\"", path, rootPath) } p := path[len(rootPath):] @@ -30,12 +30,12 @@ func ParseSegmentIDByBinlog(rootPath, path string) (UniqueID, error) { if len(keyStr) == 5 { return strconv.ParseInt(keyStr[3], 10, 64) } - return 0, fmt.Errorf("%s is not a valid delta log path", path) + return 0, merr.WrapErrServiceInternalMsg("%s is not a valid delta log path", path) } // log type are binlog or statslog if len(keyStr) == 6 { return strconv.ParseInt(keyStr[len(keyStr)-3], 10, 64) } - return 0, fmt.Errorf("%s is not a valid binlog path", path) + return 0, merr.WrapErrServiceInternalMsg("%s is not a valid binlog path", path) } diff --git a/internal/storage/binlog_writer.go b/internal/storage/binlog_writer.go index 689f44afef..dbdce3083b 100644 --- a/internal/storage/binlog_writer.go +++ b/internal/storage/binlog_writer.go @@ -20,13 +20,13 @@ import ( "bytes" "encoding/binary" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus-proto/go-api/v3/hook" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/pkg/v3/common" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // BinlogType is to distinguish different files saving different data. @@ -114,7 +114,7 @@ func (writer *baseBinlogWriter) GetBinlogType() BinlogType { // GetBuffer gets binlog buffer. Return nil if binlog is not finished yet. func (writer *baseBinlogWriter) GetBuffer() ([]byte, error) { if writer.buffer == nil { - return nil, errors.New("please close binlog before get buffer") + return nil, merr.WrapErrServiceInternalMsg("please close binlog before get buffer") } return writer.buffer.Bytes(), nil } @@ -125,7 +125,7 @@ func (writer *baseBinlogWriter) Finish() error { return nil } if writer.StartTimestamp == 0 || writer.EndTimestamp == 0 { - return errors.New("invalid start/end timestamp") + return merr.WrapErrServiceInternalMsg("invalid start/end timestamp") } var offset int32 @@ -194,7 +194,7 @@ type InsertBinlogWriter struct { // NextInsertEventWriter returns an event writer to write insert data to an event. func (writer *InsertBinlogWriter) NextInsertEventWriter(opts ...PayloadWriterOptions) (*insertEventWriter, error) { if writer.isClosed() { - return nil, errors.New("binlog has closed") + return nil, merr.WrapErrServiceInternalMsg("binlog has closed") } event, err := newInsertEventWriter(writer.PayloadDataType, opts...) @@ -214,7 +214,7 @@ type DeleteBinlogWriter struct { // NextDeleteEventWriter returns an event writer to write delete data to an event. func (writer *DeleteBinlogWriter) NextDeleteEventWriter(opts ...PayloadWriterOptions) (*deleteEventWriter, error) { if writer.isClosed() { - return nil, errors.New("binlog has closed") + return nil, merr.WrapErrServiceInternalMsg("binlog has closed") } event, err := newDeleteEventWriter(writer.PayloadDataType, opts...) if err != nil { @@ -232,7 +232,7 @@ type IndexFileBinlogWriter struct { // NextIndexFileEventWriter return next available EventWriter func (writer *IndexFileBinlogWriter) NextIndexFileEventWriter() (*indexFileEventWriter, error) { if writer.isClosed() { - return nil, errors.New("binlog has closed") + return nil, merr.WrapErrServiceInternalMsg("binlog has closed") } event, err := newIndexFileEventWriter(writer.PayloadDataType) if err != nil { diff --git a/internal/storage/data_codec.go b/internal/storage/data_codec.go index 167c00c7c5..e30c9550ad 100644 --- a/internal/storage/data_codec.go +++ b/internal/storage/data_codec.go @@ -22,7 +22,6 @@ import ( "math" "sort" - "github.com/cockroachdb/errors" "github.com/samber/lo" "go.uber.org/zap" @@ -146,7 +145,7 @@ func NewInsertCodecWithSchema(schema *etcdpb.CollectionMeta) *InsertCodec { // Serialize Pk stats log func (insertCodec *InsertCodec) SerializePkStats(stats *PrimaryKeyStats, rowNum int64) (*Blob, error) { if stats == nil || stats.BF == nil { - return nil, errors.New("sericalize empty pk stats") + return nil, merr.WrapErrServiceInternalMsg("sericalize empty pk stats") } // Serialize by pk stats @@ -191,10 +190,10 @@ func (insertCodec *InsertCodec) SerializePkStatsList(stats []*PrimaryKeyStats, r func (insertCodec *InsertCodec) SerializePkStatsByData(data *InsertData) (*Blob, error) { timeFieldData, ok := data.Data[common.TimeStampField] if !ok { - return nil, errors.New("data doesn't contains timestamp field") + return nil, merr.WrapErrServiceInternalMsg("data doesn't contains timestamp field") } if timeFieldData.RowNum() <= 0 { - return nil, errors.New("there's no data in InsertData") + return nil, merr.WrapErrServiceInternalMsg("there's no data in InsertData") } rowNum := int64(timeFieldData.RowNum()) @@ -217,7 +216,7 @@ func (insertCodec *InsertCodec) SerializePkStatsByData(data *InsertData) (*Blob, RowNum: rowNum, }, nil } - return nil, errors.New("there is no pk field") + return nil, merr.WrapErrServiceInternalMsg("there is no pk field") } // Serialize transforms insert data to blob. It will sort insert data by timestamp. @@ -228,7 +227,7 @@ func (insertCodec *InsertCodec) Serialize(partitionID UniqueID, segmentID Unique blobs := make([]*Blob, 0) var writer *InsertBinlogWriter if insertCodec.Schema == nil { - return nil, errors.New("schema is not set") + return nil, merr.WrapErrServiceInternalMsg("schema is not set") } var rowNum int64 @@ -238,7 +237,7 @@ func (insertCodec *InsertCodec) Serialize(partitionID UniqueID, segmentID Unique for _, block := range data { timeFieldData, ok := block.Data[common.TimeStampField] if !ok { - return nil, errors.New("data doesn't contains timestamp field") + return nil, merr.WrapErrServiceInternalMsg("data doesn't contains timestamp field") } rowNum += int64(timeFieldData.RowNum()) @@ -281,11 +280,11 @@ func (insertCodec *InsertCodec) Serialize(partitionID UniqueID, segmentID Unique // found missing block if !allExists { if !field.GetNullable() { - return errors.Newf("field %d(%s) missing and field not nullable", field.GetFieldID(), field.GetName()) + return merr.WrapErrStorageMsg("field %d(%s) missing and field not nullable", field.GetFieldID(), field.GetName()) } // segment must be in same schema if !allMissing { - return errors.Newf("segment must not be heterogeneous, all blocks must contain all fields or none, abnormal field %d(%s)", field.GetFieldID(), field.GetName()) + return merr.WrapErrStorageMsg("segment must not be heterogeneous, all blocks must contain all fields or none, abnormal field %d(%s)", field.GetFieldID(), field.GetName()) } log.Info("Skip field nullable missing field, could be schema change", zap.Int64("fieldId", field.GetFieldID()), zap.String("fieldName", field.GetName())) return nil @@ -474,13 +473,13 @@ func AddFieldDataToPayload(eventWriter *insertEventWriter, dataType schemapb.Dat case schemapb.DataType_ArrayOfVector: vectorArrayData := singleData.(*VectorArrayFieldData) if vectorArrayData.Nullable { - return fmt.Errorf("nullable ArrayOfVector is not supported in V1 storage format") + return merr.WrapErrStorageMsg("nullable ArrayOfVector is not supported in V1 storage format") } if err = eventWriter.AddVectorArrayFieldDataToPayload(vectorArrayData); err != nil { return err } default: - return fmt.Errorf("undefined data type %d", dataType) + return merr.WrapErrServiceInternalMsg("undefined data type %d", dataType) } return nil } @@ -493,7 +492,7 @@ func (insertCodec *InsertCodec) DeserializeAll(blobs []*Blob) ( err error, ) { if len(blobs) == 0 { - return InvalidUniqueID, InvalidUniqueID, InvalidUniqueID, nil, errors.New("blobs is empty") + return InvalidUniqueID, InvalidUniqueID, InvalidUniqueID, nil, merr.WrapErrServiceInternalMsg("blobs is empty") } var blobList BlobList = blobs @@ -897,14 +896,14 @@ func AddInsertData(dataType schemapb.DataType, data interface{}, insertData *Ins vectorArrayFieldData := fieldData.(*VectorArrayFieldData) if len(validData) > 0 { - return 0, fmt.Errorf("nullable ArrayOfVector is not supported in V1 storage format") + return 0, merr.WrapErrStorageMsg("nullable ArrayOfVector is not supported in V1 storage format") } vectorArrayFieldData.Data = append(vectorArrayFieldData.Data, singleData...) insertData.Data[fieldID] = vectorArrayFieldData return len(singleData), nil default: - return 0, fmt.Errorf("undefined data type %d", dataType) + return 0, merr.WrapErrServiceInternalMsg("undefined data type %d", dataType) } } @@ -940,7 +939,7 @@ func (deleteCodec *DeleteCodec) Serialize(collectionID UniqueID, partitionID Uni defer eventWriter.Close() length := len(data.Pks) if length != len(data.Tss) { - return nil, errors.New("the length of pks, and TimeStamps is not equal") + return nil, merr.WrapErrServiceInternalMsg("the length of pks, and TimeStamps is not equal") } sizeTotal := 0 @@ -993,7 +992,7 @@ func (deleteCodec *DeleteCodec) Serialize(collectionID UniqueID, partitionID Uni // Deserialize deserializes the deltalog blobs into DeleteData func (deleteCodec *DeleteCodec) Deserialize(blobs []*Blob) (partitionID UniqueID, segmentID UniqueID, data *DeltaData, err error) { if len(blobs) == 0 { - return InvalidUniqueID, InvalidUniqueID, nil, errors.New("blobs is empty") + return InvalidUniqueID, InvalidUniqueID, nil, merr.WrapErrServiceInternalMsg("blobs is empty") } rowNum := lo.SumBy(blobs, func(blob *Blob) int64 { diff --git a/internal/storage/delta_data.go b/internal/storage/delta_data.go index 4877c15fdd..c8af4131f1 100644 --- a/internal/storage/delta_data.go +++ b/internal/storage/delta_data.go @@ -17,7 +17,6 @@ package storage import ( - "fmt" "math" "strconv" "strings" @@ -26,7 +25,6 @@ import ( "github.com/apache/arrow/go/v17/arrow" "github.com/apache/arrow/go/v17/arrow/array" "github.com/apache/arrow/go/v17/arrow/memory" - "github.com/cockroachdb/errors" "github.com/samber/lo" "github.com/tidwall/gjson" @@ -126,7 +124,7 @@ func NewDeltaDataWithPkType(cap int64, pkType schemapb.DataType) (*DeltaData, er func NewDeltaDataWithData(pks PrimaryKeys, tss []uint64) (*DeltaData, error) { if pks.Len() != len(tss) { - return nil, merr.WrapErrParameterInvalidMsg("length of pks and tss not equal") + return nil, merr.WrapErrStorageMsg("length of pks and tss not equal: pks=%d tss=%d", pks.Len(), len(tss)) } dd := &DeltaData{ deletePks: pks, @@ -167,23 +165,23 @@ func (dl *DeleteLog) Parse(val string) error { pkRes := result.Get("pk") pkTypeRes := result.Get("pkType") if !tsRes.Exists() || !pkRes.Exists() || !pkTypeRes.Exists() { - return fmt.Errorf("invalid delete log json: missing required fields in %s", val) + return merr.WrapErrDataIntegrityMsg("invalid delete log json: missing required fields in %s", val) } dl.Ts = tsRes.Uint() dl.PkType = pkTypeRes.Int() switch dl.PkType { case int64(schemapb.DataType_Int64): if pkRes.Type != gjson.Number { - return fmt.Errorf("invalid delete log: pkType is Int64 but pk is not a number in %s", val) + return merr.WrapErrDataIntegrityMsg("invalid delete log: pkType is Int64 but pk is not a number in %s", val) } dl.Pk = &Int64PrimaryKey{Value: pkRes.Int()} case int64(schemapb.DataType_VarChar): if pkRes.Type != gjson.String { - return fmt.Errorf("invalid delete log: pkType is VarChar but pk is not a string in %s", val) + return merr.WrapErrDataIntegrityMsg("invalid delete log: pkType is VarChar but pk is not a string in %s", val) } dl.Pk = &VarCharPrimaryKey{Value: pkRes.String()} default: - return fmt.Errorf("invalid delete log: unsupported pkType %d in %s", dl.PkType, val) + return merr.WrapErrDataIntegrityMsg("invalid delete log: unsupported pkType %d in %s", dl.PkType, val) } return nil } @@ -192,7 +190,7 @@ func (dl *DeleteLog) Parse(val string) error { // compatible with fmt.Sprintf("%d,%d", pk, ts) splits := strings.Split(val, ",") if len(splits) != 2 { - return fmt.Errorf("the format of delta log is incorrect, %v can not be split", val) + return merr.WrapErrDataIntegrityMsg("the format of delta log is incorrect, %v can not be split", val) } pk, err := strconv.ParseInt(splits[0], 10, 64) if err != nil { @@ -226,7 +224,7 @@ func (dl *DeleteLog) UnmarshalJSON(data []byte) error { case schemapb.DataType_VarChar: dl.Pk = &VarCharPrimaryKey{} default: - return fmt.Errorf("unsupported primary key type: %v", schemapb.DataType(dl.PkType)) + return merr.WrapErrDataIntegrityMsg("unsupported primary key type: %v", schemapb.DataType(dl.PkType)) } if err = json.Unmarshal(*messageMap["pk"], dl.Pk); err != nil { @@ -296,10 +294,10 @@ func BuildDeleteRecord(pks []PrimaryKey, tss []Timestamp) (r Record, tsFrom uint tsTo = 0 if len(pks) == 0 { - return nil, 0, 0, errors.New("empty primary keys") + return nil, 0, 0, merr.WrapErrServiceInternalMsg("empty primary keys") } if len(pks) != len(tss) { - return nil, 0, 0, errors.New("length of pks and tss must be equal") + return nil, 0, 0, merr.WrapErrServiceInternalMsg("length of pks and tss must be equal") } allocator := memory.DefaultAllocator @@ -322,7 +320,7 @@ func BuildDeleteRecord(pks []PrimaryKey, tss []Timestamp) (r Record, tsFrom uint } pkArray = builder.NewArray() default: - return nil, 0, 0, errors.Newf("unsupported primary key type %T", pks[0]) + return nil, 0, 0, merr.WrapErrStorageMsg("unsupported primary key type %T", pks[0]) } // Build timestamp array diff --git a/internal/storage/event_data.go b/internal/storage/event_data.go index aaddbdb63a..76ff57784c 100644 --- a/internal/storage/event_data.go +++ b/internal/storage/event_data.go @@ -18,12 +18,9 @@ package storage import ( "encoding/binary" - "fmt" "io" "strconv" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/json" "github.com/milvus-io/milvus/pkg/v3/common" @@ -81,7 +78,7 @@ func (data *descriptorEventData) GetNullable() (bool, error) { nullable, ok := nullableStore.(bool) // will not happen, has checked bool format when FinishExtra if !ok { - return false, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("value of %v must in bool format", nullableKey)) + return false, merr.WrapErrDataIntegrityMsg("value of %v must in bool format", nullableKey) } return nullable, nil } @@ -128,24 +125,24 @@ func (data *descriptorEventData) FinishExtra() error { // keep all binlog file records the original size sizeStored, ok := data.Extras[originalSizeKey] if !ok { - return fmt.Errorf("%v not in extra", originalSizeKey) + return merr.WrapErrDataIntegrityMsg("%v not in extra", originalSizeKey) } // if we store a large int directly, golang will use scientific notation, we then will get a float value. // so it's better to store the original size in string format. sizeStr, ok := sizeStored.(string) if !ok { - return fmt.Errorf("value of %v must in string format", originalSizeKey) + return merr.WrapErrDataIntegrityMsg("value of %v must in string format", originalSizeKey) } _, err = strconv.Atoi(sizeStr) if err != nil { - return fmt.Errorf("value of %v must be able to be converted into int format", originalSizeKey) + return merr.WrapErrDataIntegrityMsg("value of %v must be able to be converted into int format", originalSizeKey) } nullableStore, existed := data.Extras[nullableKey] if existed { _, ok := nullableStore.(bool) if !ok { - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("value of %v must in bool format", nullableKey)) + return merr.WrapErrDataIntegrityMsg("value of %v must in bool format", nullableKey) } } @@ -153,14 +150,14 @@ func (data *descriptorEventData) FinishExtra() error { if exist { _, ok := edekStored.(string) if !ok { - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("value of %v must in string format", edekKey)) + return merr.WrapErrDataIntegrityMsg("value of %v must in string format", edekKey) } } ezIDStored, exist := data.Extras[ezIDKey] if exist { _, ok := ezIDStored.(int64) if !ok { - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("value of %v must in int64 format", ezIDKey)) + return merr.WrapErrDataIntegrityMsg("value of %v must in int64 format", ezIDKey) } } @@ -207,7 +204,9 @@ func readDescriptorEventData(buffer io.Reader) (*descriptorEventData, error) { return nil, err } if err := json.Unmarshal(event.ExtraBytes, &event.Extras); err != nil { - return nil, err + // Malformed extras read back from a stored descriptor event is corrupt + // stored data, consistent with the other ErrDataIntegrity checks here. + return nil, merr.WrapErrDataIntegrity(err, "failed to unmarshal descriptor event extras") } return event, nil @@ -236,10 +235,10 @@ func (data *insertEventData) GetEventDataFixPartSize() int32 { func (data *insertEventData) WriteEventData(buffer io.Writer) error { if data.StartTimestamp == 0 { - return errors.New("hasn't set start time stamp") + return merr.WrapErrServiceInternalMsg("hasn't set start time stamp") } if data.EndTimestamp == 0 { - return errors.New("hasn't set end time stamp") + return merr.WrapErrServiceInternalMsg("hasn't set end time stamp") } return binary.Write(buffer, common.Endian, data) } @@ -260,10 +259,10 @@ func (data *deleteEventData) GetEventDataFixPartSize() int32 { func (data *deleteEventData) WriteEventData(buffer io.Writer) error { if data.StartTimestamp == 0 { - return errors.New("hasn't set start time stamp") + return merr.WrapErrServiceInternalMsg("hasn't set start time stamp") } if data.EndTimestamp == 0 { - return errors.New("hasn't set end time stamp") + return merr.WrapErrServiceInternalMsg("hasn't set end time stamp") } return binary.Write(buffer, common.Endian, data) } @@ -284,10 +283,10 @@ func (data *createCollectionEventData) GetEventDataFixPartSize() int32 { func (data *createCollectionEventData) WriteEventData(buffer io.Writer) error { if data.StartTimestamp == 0 { - return errors.New("hasn't set start time stamp") + return merr.WrapErrServiceInternalMsg("hasn't set start time stamp") } if data.EndTimestamp == 0 { - return errors.New("hasn't set end time stamp") + return merr.WrapErrServiceInternalMsg("hasn't set end time stamp") } return binary.Write(buffer, common.Endian, data) } @@ -308,10 +307,10 @@ func (data *dropCollectionEventData) GetEventDataFixPartSize() int32 { func (data *dropCollectionEventData) WriteEventData(buffer io.Writer) error { if data.StartTimestamp == 0 { - return errors.New("hasn't set start time stamp") + return merr.WrapErrServiceInternalMsg("hasn't set start time stamp") } if data.EndTimestamp == 0 { - return errors.New("hasn't set end time stamp") + return merr.WrapErrServiceInternalMsg("hasn't set end time stamp") } return binary.Write(buffer, common.Endian, data) } @@ -332,10 +331,10 @@ func (data *createPartitionEventData) GetEventDataFixPartSize() int32 { func (data *createPartitionEventData) WriteEventData(buffer io.Writer) error { if data.StartTimestamp == 0 { - return errors.New("hasn't set start time stamp") + return merr.WrapErrServiceInternalMsg("hasn't set start time stamp") } if data.EndTimestamp == 0 { - return errors.New("hasn't set end time stamp") + return merr.WrapErrServiceInternalMsg("hasn't set end time stamp") } return binary.Write(buffer, common.Endian, data) } @@ -356,10 +355,10 @@ func (data *dropPartitionEventData) GetEventDataFixPartSize() int32 { func (data *dropPartitionEventData) WriteEventData(buffer io.Writer) error { if data.StartTimestamp == 0 { - return errors.New("hasn't set start time stamp") + return merr.WrapErrServiceInternalMsg("hasn't set start time stamp") } if data.EndTimestamp == 0 { - return errors.New("hasn't set end time stamp") + return merr.WrapErrServiceInternalMsg("hasn't set end time stamp") } return binary.Write(buffer, common.Endian, data) } @@ -380,10 +379,10 @@ func (data *indexFileEventData) GetEventDataFixPartSize() int32 { func (data *indexFileEventData) WriteEventData(buffer io.Writer) error { if data.StartTimestamp == 0 { - return errors.New("hasn't set start time stamp") + return merr.WrapErrServiceInternalMsg("hasn't set start time stamp") } if data.EndTimestamp == 0 { - return errors.New("hasn't set end time stamp") + return merr.WrapErrServiceInternalMsg("hasn't set end time stamp") } return binary.Write(buffer, common.Endian, data) } diff --git a/internal/storage/event_reader.go b/internal/storage/event_reader.go index 3cc5329e7e..39b4eb3d1e 100644 --- a/internal/storage/event_reader.go +++ b/internal/storage/event_reader.go @@ -18,11 +18,9 @@ package storage import ( "bytes" - "fmt" - - "github.com/cockroachdb/errors" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // EventReader is used to parse the events contained in the Binlog file. @@ -36,7 +34,7 @@ type EventReader struct { func (reader *EventReader) readHeader() error { if reader.isClosed { - return errors.New("event reader is closed") + return merr.WrapErrServiceInternalMsg("event reader is closed") } header, err := readEventHeader(reader.buffer) if err != nil { @@ -48,7 +46,7 @@ func (reader *EventReader) readHeader() error { func (reader *EventReader) readData() error { if reader.isClosed { - return errors.New("event reader is closed") + return merr.WrapErrServiceInternalMsg("event reader is closed") } var data eventData var err error @@ -68,7 +66,7 @@ func (reader *EventReader) readData() error { case IndexFileEventType: data, err = readIndexFileEventDataFixPart(reader.buffer) default: - return fmt.Errorf("unknown header type code: %d", reader.TypeCode) + return merr.WrapErrServiceInternalMsg("unknown header type code: %d", reader.TypeCode) } if err != nil { return err diff --git a/internal/storage/event_writer.go b/internal/storage/event_writer.go index c5b02a892b..26c00164b8 100644 --- a/internal/storage/event_writer.go +++ b/internal/storage/event_writer.go @@ -21,10 +21,9 @@ import ( "encoding/binary" "io" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/pkg/v3/common" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // EventTypeCode represents event type by code @@ -266,7 +265,7 @@ func newDeleteEventWriter(dataType schemapb.DataType, opts ...PayloadWriterOptio func newCreateCollectionEventWriter(dataType schemapb.DataType) (*createCollectionEventWriter, error) { if dataType != schemapb.DataType_String && dataType != schemapb.DataType_Int64 { - return nil, errors.New("incorrect data type") + return nil, merr.WrapErrServiceInternalMsg("incorrect data type") } payloadWriter, err := NewPayloadWriter(dataType) @@ -292,7 +291,7 @@ func newCreateCollectionEventWriter(dataType schemapb.DataType) (*createCollecti func newDropCollectionEventWriter(dataType schemapb.DataType) (*dropCollectionEventWriter, error) { if dataType != schemapb.DataType_String && dataType != schemapb.DataType_Int64 { - return nil, errors.New("incorrect data type") + return nil, merr.WrapErrServiceInternalMsg("incorrect data type") } payloadWriter, err := NewPayloadWriter(dataType) @@ -318,7 +317,7 @@ func newDropCollectionEventWriter(dataType schemapb.DataType) (*dropCollectionEv func newCreatePartitionEventWriter(dataType schemapb.DataType) (*createPartitionEventWriter, error) { if dataType != schemapb.DataType_String && dataType != schemapb.DataType_Int64 { - return nil, errors.New("incorrect data type") + return nil, merr.WrapErrServiceInternalMsg("incorrect data type") } payloadWriter, err := NewPayloadWriter(dataType) @@ -344,7 +343,7 @@ func newCreatePartitionEventWriter(dataType schemapb.DataType) (*createPartition func newDropPartitionEventWriter(dataType schemapb.DataType) (*dropPartitionEventWriter, error) { if dataType != schemapb.DataType_String && dataType != schemapb.DataType_Int64 { - return nil, errors.New("incorrect data type") + return nil, merr.WrapErrServiceInternalMsg("incorrect data type") } payloadWriter, err := NewPayloadWriter(dataType) diff --git a/internal/storage/factory.go b/internal/storage/factory.go index f174409939..da6e102636 100644 --- a/internal/storage/factory.go +++ b/internal/storage/factory.go @@ -3,9 +3,8 @@ package storage import ( "context" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus/pkg/v3/objectstorage" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -55,7 +54,7 @@ func (f *ChunkManagerFactory) newChunkManager(ctx context.Context, engine string case "remote", "minio", "opendal": return NewRemoteChunkManager(ctx, f.config) default: - return nil, errors.New("no chunk manager implemented with engine: " + engine) + return nil, merr.WrapErrServiceInternalMsg("no chunk manager implemented with engine: " + engine) } } diff --git a/internal/storage/field_stats.go b/internal/storage/field_stats.go index e26da9dd9f..2919fe7619 100644 --- a/internal/storage/field_stats.go +++ b/internal/storage/field_stats.go @@ -17,7 +17,6 @@ package storage import ( - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" @@ -67,7 +66,7 @@ func (stats *FieldStats) UnmarshalJSON(data []byte) error { return err } } else { - return errors.New("invalid fieldStats, no fieldID") + return merr.WrapErrServiceInternalMsg("invalid fieldStats, no fieldID") } stats.Type = schemapb.DataType_Int64 @@ -471,7 +470,7 @@ func (sr *FieldStatsReader) GetFieldStatsList() ([]*FieldStats, error) { stats := &FieldStats{} errNew := json.Unmarshal(sr.buffer, &stats) if errNew != nil { - return nil, merr.WrapErrParameterInvalid("valid JSON", string(sr.buffer), err.Error()) + return nil, merr.WrapErrDataIntegrity(err, "FieldStats list unmarshal failed") } return []*FieldStats{stats}, nil } diff --git a/internal/storage/field_stats_test.go b/internal/storage/field_stats_test.go index cc9be76a8d..f228be9990 100644 --- a/internal/storage/field_stats_test.go +++ b/internal/storage/field_stats_test.go @@ -419,7 +419,7 @@ func TestDeserializeFieldStatsFailed(t *testing.T) { } _, err := DeserializeFieldStats(blob) - assert.ErrorIs(t, err, merr.ErrParameterInvalid) + assert.ErrorIs(t, err, merr.ErrDataIntegrity) }) t.Run("valid field stats", func(t *testing.T) { diff --git a/internal/storage/field_value.go b/internal/storage/field_value.go index 9e1f206a66..4bdc44cf5d 100644 --- a/internal/storage/field_value.go +++ b/internal/storage/field_value.go @@ -21,8 +21,6 @@ import ( "math" "strings" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/json" "github.com/milvus-io/milvus/pkg/v3/log" @@ -159,7 +157,7 @@ func (ifv *Int8FieldValue) SetValue(data interface{}) error { value, ok := data.(int8) if !ok { log.Warn("wrong type value when setValue for Int64FieldValue") - return errors.New("wrong type value when setValue for Int64FieldValue") + return merr.WrapErrServiceInternalMsg("wrong type value when setValue for Int64FieldValue") } ifv.Value = value @@ -279,7 +277,7 @@ func (ifv *Int16FieldValue) SetValue(data interface{}) error { value, ok := data.(int16) if !ok { log.Warn("wrong type value when setValue for Int64FieldValue") - return errors.New("wrong type value when setValue for Int64FieldValue") + return merr.WrapErrServiceInternalMsg("wrong type value when setValue for Int64FieldValue") } ifv.Value = value @@ -399,7 +397,7 @@ func (ifv *Int32FieldValue) SetValue(data interface{}) error { value, ok := data.(int32) if !ok { log.Warn("wrong type value when setValue for Int64FieldValue") - return errors.New("wrong type value when setValue for Int64FieldValue") + return merr.WrapErrServiceInternalMsg("wrong type value when setValue for Int64FieldValue") } ifv.Value = value @@ -519,7 +517,7 @@ func (ifv *Int64FieldValue) SetValue(data interface{}) error { value, ok := data.(int64) if !ok { log.Warn("wrong type value when setValue for Int64FieldValue") - return errors.New("wrong type value when setValue for Int64FieldValue") + return merr.WrapErrServiceInternalMsg("wrong type value when setValue for Int64FieldValue") } ifv.Value = value @@ -640,7 +638,7 @@ func (ifv *FloatFieldValue) SetValue(data interface{}) error { value, ok := data.(float32) if !ok { log.Warn("wrong type value when setValue for FloatFieldValue") - return errors.New("wrong type value when setValue for FloatFieldValue") + return merr.WrapErrServiceInternalMsg("wrong type value when setValue for FloatFieldValue") } ifv.Value = value @@ -760,7 +758,7 @@ func (ifv *DoubleFieldValue) SetValue(data interface{}) error { value, ok := data.(float64) if !ok { log.Warn("wrong type value when setValue for DoubleFieldValue") - return errors.New("wrong type value when setValue for DoubleFieldValue") + return merr.WrapErrServiceInternalMsg("wrong type value when setValue for DoubleFieldValue") } ifv.Value = value @@ -856,7 +854,7 @@ func (sfv *StringFieldValue) UnmarshalJSON(data []byte) error { func (sfv *StringFieldValue) SetValue(data interface{}) error { value, ok := data.(string) if !ok { - return errors.New("wrong type value when setValue for StringFieldValue") + return merr.WrapErrServiceInternalMsg("wrong type value when setValue for StringFieldValue") } sfv.Value = value @@ -934,7 +932,7 @@ func (vcfv *VarCharFieldValue) EQ(obj ScalarFieldValue) bool { func (vcfv *VarCharFieldValue) SetValue(data interface{}) error { value, ok := data.(string) if !ok { - return errors.New("wrong type value when setValue for StringFieldValue") + return merr.WrapErrServiceInternalMsg("wrong type value when setValue for StringFieldValue") } vcfv.Value = value @@ -1014,7 +1012,7 @@ func (ifv *FloatVectorFieldValue) SetValue(data interface{}) error { value, ok := data.([]float32) if !ok { log.Warn("wrong type value when setValue for FloatVectorFieldValue") - return errors.New("wrong type value when setValue for FloatVectorFieldValue") + return merr.WrapErrServiceInternalMsg("wrong type value when setValue for FloatVectorFieldValue") } ifv.Value = value diff --git a/internal/storage/index_data_codec.go b/internal/storage/index_data_codec.go index 4760d8291c..741e55139d 100644 --- a/internal/storage/index_data_codec.go +++ b/internal/storage/index_data_codec.go @@ -21,12 +21,12 @@ import ( "strconv" "time" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/json" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -164,7 +164,7 @@ func (codec *IndexFileBinlogCodec) DeserializeImpl(blobs []*Blob) ( err error, ) { if len(blobs) == 0 { - return 0, 0, 0, 0, 0, 0, nil, "", 0, nil, errors.New("blobs is empty") + return 0, 0, 0, 0, 0, 0, nil, "", 0, nil, merr.WrapErrDataIntegrityMsg("blobs is empty") } indexParams = make(map[string]string) datas = make([]*Blob, 0) @@ -250,7 +250,7 @@ func (codec *IndexFileBinlogCodec) DeserializeImpl(blobs []*Blob) ( // make sure there is one string if len(content) != 1 { - err := fmt.Errorf("failed to parse index event because content length is not one %d", len(content)) + err := merr.WrapErrDataIntegrityMsg("failed to parse index event because content length is not one %d", len(content)) eventReader.Close() binlogReader.Close() return 0, 0, 0, 0, 0, 0, nil, "", 0, nil, err @@ -321,7 +321,7 @@ func (indexCodec *IndexCodec) Deserialize(blobs []*Blob) ([]*Blob, map[string]st break } if file == nil { - return nil, nil, "", InvalidUniqueID, errors.New("can not find params blob") + return nil, nil, "", InvalidUniqueID, merr.WrapErrDataIntegrityMsg("can not find params blob") } info := struct { Params map[string]string @@ -329,7 +329,7 @@ func (indexCodec *IndexCodec) Deserialize(blobs []*Blob) ([]*Blob, map[string]st IndexID UniqueID }{} if err := json.Unmarshal(file.Value, &info); err != nil { - return nil, nil, "", InvalidUniqueID, fmt.Errorf("json unmarshal error: %s", err.Error()) + return nil, nil, "", InvalidUniqueID, merr.WrapErrSerializationFailed(err, "json unmarshal error") } return blobs, info.Params, info.IndexName, info.IndexID, nil diff --git a/internal/storage/insert_data.go b/internal/storage/insert_data.go index 6c94b483ab..39eab2486a 100644 --- a/internal/storage/insert_data.go +++ b/internal/storage/insert_data.go @@ -18,7 +18,6 @@ package storage import ( "encoding/binary" - "fmt" "google.golang.org/protobuf/proto" @@ -60,7 +59,11 @@ func NewInsertDataWithFunctionOutputField(schema *schemapb.CollectionSchema) (*I func NewInsertDataWithCap(schema *schemapb.CollectionSchema, cap int, withFunctionOutput bool) (*InsertData, error) { if schema == nil { - return nil, merr.WrapErrParameterMissing("collection schema") + // A nil schema here is an internal invariant violation (callers within + // Milvus always pass a schema), not a client-supplied missing parameter. + // ErrParameterMissing defaults to InputError, so mark this one site as a + // system error to keep it out of the client-fault (fail_input) bucket. + return nil, merr.WrapErrAsSysError(merr.WrapErrParameterMissing("collection schema")) } idata := &InsertData{ @@ -69,17 +72,17 @@ func NewInsertDataWithCap(schema *schemapb.CollectionSchema, cap int, withFuncti appendField := func(field *schemapb.FieldSchema) error { if field.IsPrimaryKey && field.GetNullable() { - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("primary key field should not be nullable (field: %s)", field.Name)) + return merr.WrapErrParameterInvalidMsg("primary key field should not be nullable (field: %s)", field.Name) } if field.IsPartitionKey && field.GetNullable() { - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("partition key field should not be nullable (field: %s)", field.Name)) + return merr.WrapErrParameterInvalidMsg("partition key field should not be nullable (field: %s)", field.Name) } if field.IsFunctionOutput { if field.IsPrimaryKey || field.IsPartitionKey { - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("function output field should not be primary key or partition key (field: %s)", field.Name)) + return merr.WrapErrParameterInvalidMsg("function output field should not be primary key or partition key (field: %s)", field.Name) } if field.GetNullable() { - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("function output field should not be nullable (field: %s)", field.Name)) + return merr.WrapErrParameterInvalidMsg("function output field should not be nullable (field: %s)", field.Name) } if !withFunctionOutput { return nil @@ -149,11 +152,11 @@ func (i *InsertData) Append(row map[FieldID]interface{}) error { for fID, v := range row { field, ok := i.Data[fID] if !ok { - return fmt.Errorf("missing field when appending row, got %d", fID) + return merr.WrapErrServiceInternalMsg("missing field when appending row, got %d", fID) } if err := field.AppendRow(v); err != nil { - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("append data for field %d failed, err=%s", fID, err.Error())) + return merr.WrapErrParameterInvalidMsg("append data for field %d failed, err=%s", fID, err.Error()) } } @@ -405,7 +408,7 @@ func NewFieldData(dataType schemapb.DataType, fieldSchema *schemapb.FieldSchema, } return data, nil default: - return nil, fmt.Errorf("unexpected schema data type: %d", dataType) + return nil, merr.WrapErrServiceInternalMsg("unexpected schema data type: %d", dataType) } } diff --git a/internal/storage/local_chunk_manager.go b/internal/storage/local_chunk_manager.go index f35419758c..2f2faf1d85 100644 --- a/internal/storage/local_chunk_manager.go +++ b/internal/storage/local_chunk_manager.go @@ -119,7 +119,7 @@ func (lcm *LocalChunkManager) MultiWrite(ctx context.Context, contents map[strin for filePath, content := range contents { err := lcm.Write(ctx, filePath, content) if err != nil { - el = merr.Combine(el, errors.Wrapf(err, "write %s failed", filePath)) + el = merr.Combine(el, merr.Wrapf(err, "write %s failed", filePath)) } } return el @@ -149,7 +149,7 @@ func (lcm *LocalChunkManager) MultiRead(ctx context.Context, filePaths []string) for i, filePath := range filePaths { content, err := lcm.Read(ctx, filePath) if err != nil { - el = merr.Combine(el, errors.Wrapf(err, "failed to read %s", filePath)) + el = merr.Combine(el, merr.Wrapf(err, "failed to read %s", filePath)) } results[i] = content } @@ -271,7 +271,7 @@ func (lcm *LocalChunkManager) RemoveWithPrefix(ctx context.Context, prefix strin if len(prefix) == 0 { errMsg := "empty prefix is not allowed for ChunkManager remove operation" log.Warn(errMsg) - return merr.WrapErrParameterInvalidMsg(errMsg) + return merr.WrapErrStorageMsg("%s", errMsg) } var removeErr error if err := lcm.WalkWithPrefix(ctx, prefix, true, func(chunkInfo *ChunkObjectInfo) bool { diff --git a/internal/storage/minio_object_storage_test.go b/internal/storage/minio_object_storage_test.go index 386898c977..0ee71571e6 100644 --- a/internal/storage/minio_object_storage_test.go +++ b/internal/storage/minio_object_storage_test.go @@ -201,31 +201,51 @@ func TestMinioObjectStorage(t *testing.T) { }) t.Run("test useIAM", func(t *testing.T) { + // newMinioObjectStorageWithConfig validates IAM credentials by calling + // BucketExists against the configured endpoint. With invalid IAM + // credentials on a host where the endpoint is unreachable, the dial + // blocks long enough that retry.Do (CheckBucketRetryAttempts=20) drags + // the whole package out to the 10min testing.M timeout. Bound each + // call with a short context so it fails fast regardless of + // environment; the test only asserts an error is returned. Mirrors + // the Azure fix in PR #49814. var err error config.UseIAM = true - _, err = newMinioObjectStorageWithConfig(ctx, &config) + cctx, cancel := context.WithTimeout(ctx, 5*time.Second) + _, err = newMinioObjectStorageWithConfig(cctx, &config) + cancel() assert.Error(t, err) config.UseIAM = false }) t.Run("test ssl", func(t *testing.T) { + // Same endpoint-unreachable hang as the "test useIAM" subtest above: + // UseSSL=true with a dummy CA cert against a non-TLS minio endpoint + // keeps retry.Do dialing until the testing.M timeout. Bound it. var err error config.UseSSL = true config.SslCACert = "/tmp/dummy.crt" - _, err = newMinioObjectStorageWithConfig(ctx, &config) + cctx, cancel := context.WithTimeout(ctx, 5*time.Second) + _, err = newMinioObjectStorageWithConfig(cctx, &config) + cancel() assert.Error(t, err) config.UseSSL = false }) t.Run("test cloud provider", func(t *testing.T) { + // Same endpoint-unreachable hang as the "test useIAM" subtest above. var err error cloudProvider := config.CloudProvider config.CloudProvider = "aliyun" config.UseIAM = true - _, err = newMinioObjectStorageWithConfig(ctx, &config) + cctx, cancel := context.WithTimeout(ctx, 5*time.Second) + _, err = newMinioObjectStorageWithConfig(cctx, &config) + cancel() assert.Error(t, err) config.UseIAM = false - _, err = newMinioObjectStorageWithConfig(ctx, &config) + cctx, cancel = context.WithTimeout(ctx, 5*time.Second) + _, err = newMinioObjectStorageWithConfig(cctx, &config) + cancel() assert.Error(t, err) config.CloudProvider = "gcp" _, err = newMinioObjectStorageWithConfig(ctx, &config) diff --git a/internal/storage/payload_reader.go b/internal/storage/payload_reader.go index 9463e445b4..384ffb6907 100644 --- a/internal/storage/payload_reader.go +++ b/internal/storage/payload_reader.go @@ -3,7 +3,6 @@ package storage import ( "bytes" "context" - "fmt" "strconv" "time" @@ -13,7 +12,6 @@ import ( "github.com/apache/arrow/go/v17/parquet" "github.com/apache/arrow/go/v17/parquet/file" "github.com/apache/arrow/go/v17/parquet/pqarrow" - "github.com/cockroachdb/errors" "go.uber.org/zap" "google.golang.org/protobuf/proto" @@ -39,7 +37,7 @@ var _ PayloadReaderInterface = (*PayloadReader)(nil) func NewPayloadReader(colType schemapb.DataType, buf []byte, nullable bool) (*PayloadReader, error) { if len(buf) == 0 { - return nil, errors.New("create Payload reader failed, buffer is empty") + return nil, merr.WrapErrDataIntegrityMsg("create Payload reader failed, buffer is empty") } parquetReader, err := file.NewParquetReader(bytes.NewReader(buf)) if err != nil { @@ -56,32 +54,32 @@ func NewPayloadReader(colType schemapb.DataType, buf []byte, nullable bool) (*Pa if colType == schemapb.DataType_ArrayOfVector { arrowReader, err := pqarrow.NewFileReader(parquetReader, pqarrow.ArrowReadProperties{BatchSize: 1024}, memory.DefaultAllocator) if err != nil { - return nil, fmt.Errorf("failed to create arrow reader for VectorArray: %w", err) + return nil, merr.WrapErrStorage(err, "failed to create arrow reader for VectorArray") } arrowSchema, err := arrowReader.Schema() if err != nil { - return nil, fmt.Errorf("failed to get arrow schema for VectorArray: %w", err) + return nil, merr.WrapErrStorage(err, "failed to get arrow schema for VectorArray") } if arrowSchema.NumFields() != 1 { - return nil, fmt.Errorf("VectorArray should have exactly 1 field, got %d", arrowSchema.NumFields()) + return nil, merr.WrapErrDataIntegrityMsg("VectorArray should have exactly 1 field, got %d", arrowSchema.NumFields()) } field := arrowSchema.Field(0) if !field.HasMetadata() { - return nil, errors.New("VectorArray field is missing metadata") + return nil, merr.WrapErrDataIntegrityMsg("VectorArray field is missing metadata") } metadata := field.Metadata elementTypeStr, ok := metadata.GetValue("elementType") if !ok { - return nil, errors.New("VectorArray metadata missing required 'elementType' field") + return nil, merr.WrapErrDataIntegrityMsg("VectorArray metadata missing required 'elementType' field") } elementTypeInt, err := strconv.ParseInt(elementTypeStr, 10, 32) if err != nil { - return nil, fmt.Errorf("invalid elementType in VectorArray metadata: %s", elementTypeStr) + return nil, merr.WrapErrDataIntegrityMsg("invalid elementType in VectorArray metadata: %s", elementTypeStr) } elementType := schemapb.DataType(elementTypeInt) @@ -94,19 +92,19 @@ func NewPayloadReader(colType schemapb.DataType, buf []byte, nullable bool) (*Pa schemapb.DataType_SparseFloatVector: reader.elementType = elementType default: - return nil, fmt.Errorf("invalid vector type for VectorArray: %s", elementType.String()) + return nil, merr.WrapErrDataIntegrityMsg("invalid vector type for VectorArray: %s", elementType.String()) } dimStr, ok := metadata.GetValue("dim") if !ok { - return nil, errors.New("VectorArray metadata missing required 'dim' field") + return nil, merr.WrapErrDataIntegrityMsg("VectorArray metadata missing required 'dim' field") } dimVal, err := strconv.ParseInt(dimStr, 10, 64) if err != nil { - return nil, fmt.Errorf("invalid dim in VectorArray metadata: %s", dimStr) + return nil, merr.WrapErrDataIntegrityMsg("invalid dim in VectorArray metadata: %s", dimStr) } if dimVal <= 0 { - return nil, fmt.Errorf("VectorArray dim must be positive, got %d", dimVal) + return nil, merr.WrapErrDataIntegrityMsg("VectorArray dim must be positive, got %d", dimVal) } reader.dim = dimVal } @@ -182,7 +180,7 @@ func (r *PayloadReader) GetDataFromPayload() (interface{}, []bool, int, error) { val, validData, err := r.GetGeometryFromPayload() return val, validData, 0, err default: - return nil, nil, 0, merr.WrapErrParameterInvalidMsg("unknown type") + return nil, nil, 0, merr.WrapErrDataIntegrityMsg("unknown type") } } @@ -194,7 +192,7 @@ func (r *PayloadReader) ReleasePayloadReader() error { // GetBoolFromPayload returns bool slice from payload. func (r *PayloadReader) GetBoolFromPayload() ([]bool, []bool, error) { if r.colType != schemapb.DataType_Bool { - return nil, nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("failed to get bool from datatype %v", r.colType.String())) + return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get bool from datatype %v", r.colType.String()) } values := make([]bool, r.numRows) @@ -206,7 +204,7 @@ func (r *PayloadReader) GetBoolFromPayload() ([]bool, []bool, error) { return nil, nil, err } if valuesRead != r.numRows { - return nil, nil, merr.WrapErrParameterInvalid(r.numRows, valuesRead, "valuesRead is not equal to rows") + return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead) } return values, validData, nil } @@ -215,7 +213,7 @@ func (r *PayloadReader) GetBoolFromPayload() ([]bool, []bool, error) { return nil, nil, err } if valuesRead != r.numRows { - return nil, nil, merr.WrapErrParameterInvalid(r.numRows, valuesRead, "valuesRead is not equal to rows") + return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead) } return values, nil, nil } @@ -223,7 +221,7 @@ func (r *PayloadReader) GetBoolFromPayload() ([]bool, []bool, error) { // GetByteFromPayload returns byte slice from payload func (r *PayloadReader) GetByteFromPayload() ([]byte, []bool, error) { if r.colType != schemapb.DataType_Int8 { - return nil, nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("failed to get byte from datatype %v", r.colType.String())) + return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get byte from datatype %v", r.colType.String()) } if r.nullable { @@ -234,7 +232,7 @@ func (r *PayloadReader) GetByteFromPayload() ([]byte, []bool, error) { return nil, nil, err } if valuesRead != r.numRows { - return nil, nil, merr.WrapErrParameterInvalid(r.numRows, valuesRead, "valuesRead is not equal to rows") + return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead) } ret := make([]byte, r.numRows) for i := int64(0); i < r.numRows; i++ { @@ -249,7 +247,7 @@ func (r *PayloadReader) GetByteFromPayload() ([]byte, []bool, error) { } if valuesRead != r.numRows { - return nil, nil, merr.WrapErrParameterInvalid(r.numRows, valuesRead, "valuesRead is not equal to rows") + return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead) } ret := make([]byte, r.numRows) @@ -261,7 +259,7 @@ func (r *PayloadReader) GetByteFromPayload() ([]byte, []bool, error) { func (r *PayloadReader) GetInt8FromPayload() ([]int8, []bool, error) { if r.colType != schemapb.DataType_Int8 { - return nil, nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("failed to get int8 from datatype %v", r.colType.String())) + return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get int8 from datatype %v", r.colType.String()) } if r.nullable { @@ -273,7 +271,7 @@ func (r *PayloadReader) GetInt8FromPayload() ([]int8, []bool, error) { } if valuesRead != r.numRows { - return nil, nil, merr.WrapErrParameterInvalid(r.numRows, valuesRead, "valuesRead is not equal to rows") + return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead) } return values, validData, nil @@ -285,7 +283,7 @@ func (r *PayloadReader) GetInt8FromPayload() ([]int8, []bool, error) { } if valuesRead != r.numRows { - return nil, nil, merr.WrapErrParameterInvalid(r.numRows, valuesRead, "valuesRead is not equal to rows") + return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead) } ret := make([]int8, r.numRows) @@ -297,7 +295,7 @@ func (r *PayloadReader) GetInt8FromPayload() ([]int8, []bool, error) { func (r *PayloadReader) GetInt16FromPayload() ([]int16, []bool, error) { if r.colType != schemapb.DataType_Int16 { - return nil, nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("failed to get int16 from datatype %v", r.colType.String())) + return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get int16 from datatype %v", r.colType.String()) } if r.nullable { @@ -309,7 +307,7 @@ func (r *PayloadReader) GetInt16FromPayload() ([]int16, []bool, error) { } if valuesRead != r.numRows { - return nil, nil, merr.WrapErrParameterInvalid(r.numRows, valuesRead, "valuesRead is not equal to rows") + return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead) } return values, validData, nil } @@ -320,7 +318,7 @@ func (r *PayloadReader) GetInt16FromPayload() ([]int16, []bool, error) { } if valuesRead != r.numRows { - return nil, nil, merr.WrapErrParameterInvalid(r.numRows, valuesRead, "valuesRead is not equal to rows") + return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead) } ret := make([]int16, r.numRows) @@ -332,7 +330,7 @@ func (r *PayloadReader) GetInt16FromPayload() ([]int16, []bool, error) { func (r *PayloadReader) GetInt32FromPayload() ([]int32, []bool, error) { if r.colType != schemapb.DataType_Int32 { - return nil, nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("failed to get int32 from datatype %v", r.colType.String())) + return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get int32 from datatype %v", r.colType.String()) } values := make([]int32, r.numRows) @@ -344,7 +342,7 @@ func (r *PayloadReader) GetInt32FromPayload() ([]int32, []bool, error) { } if valuesRead != r.numRows { - return nil, nil, merr.WrapErrParameterInvalid(r.numRows, valuesRead, "valuesRead is not equal to rows") + return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead) } return values, validData, nil } @@ -354,14 +352,14 @@ func (r *PayloadReader) GetInt32FromPayload() ([]int32, []bool, error) { } if valuesRead != r.numRows { - return nil, nil, merr.WrapErrParameterInvalid(r.numRows, valuesRead, "valuesRead is not equal to rows") + return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead) } return values, nil, nil } func (r *PayloadReader) GetInt64FromPayload() ([]int64, []bool, error) { if r.colType != schemapb.DataType_Int64 { - return nil, nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("failed to get int64 from datatype %v", r.colType.String())) + return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get int64 from datatype %v", r.colType.String()) } values := make([]int64, r.numRows) @@ -373,7 +371,7 @@ func (r *PayloadReader) GetInt64FromPayload() ([]int64, []bool, error) { } if valuesRead != r.numRows { - return nil, nil, merr.WrapErrParameterInvalid(r.numRows, valuesRead, "valuesRead is not equal to rows") + return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead) } return values, validData, nil @@ -384,7 +382,7 @@ func (r *PayloadReader) GetInt64FromPayload() ([]int64, []bool, error) { } if valuesRead != r.numRows { - return nil, nil, merr.WrapErrParameterInvalid(r.numRows, valuesRead, "valuesRead is not equal to rows") + return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead) } return values, nil, nil @@ -392,7 +390,7 @@ func (r *PayloadReader) GetInt64FromPayload() ([]int64, []bool, error) { func (r *PayloadReader) GetFloatFromPayload() ([]float32, []bool, error) { if r.colType != schemapb.DataType_Float { - return nil, nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("failed to get float32 from datatype %v", r.colType.String())) + return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get float32 from datatype %v", r.colType.String()) } values := make([]float32, r.numRows) @@ -403,7 +401,7 @@ func (r *PayloadReader) GetFloatFromPayload() ([]float32, []bool, error) { return nil, nil, err } if valuesRead != r.numRows { - return nil, nil, merr.WrapErrParameterInvalid(r.numRows, valuesRead, "valuesRead is not equal to rows") + return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead) } return values, validData, nil } @@ -412,14 +410,14 @@ func (r *PayloadReader) GetFloatFromPayload() ([]float32, []bool, error) { return nil, nil, err } if valuesRead != r.numRows { - return nil, nil, merr.WrapErrParameterInvalid(r.numRows, valuesRead, "valuesRead is not equal to rows") + return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead) } return values, nil, nil } func (r *PayloadReader) GetDoubleFromPayload() ([]float64, []bool, error) { if r.colType != schemapb.DataType_Double { - return nil, nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("failed to get double from datatype %v", r.colType.String())) + return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get double from datatype %v", r.colType.String()) } values := make([]float64, r.numRows) @@ -431,7 +429,7 @@ func (r *PayloadReader) GetDoubleFromPayload() ([]float64, []bool, error) { } if valuesRead != r.numRows { - return nil, nil, merr.WrapErrParameterInvalid(r.numRows, valuesRead, "valuesRead is not equal to rows") + return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead) } return values, validData, nil } @@ -441,14 +439,14 @@ func (r *PayloadReader) GetDoubleFromPayload() ([]float64, []bool, error) { } if valuesRead != r.numRows { - return nil, nil, merr.WrapErrParameterInvalid(r.numRows, valuesRead, "valuesRead is not equal to rows") + return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead) } return values, nil, nil } func (r *PayloadReader) GetTimestamptzFromPayload() ([]int64, []bool, error) { if r.colType != schemapb.DataType_Timestamptz { - return nil, nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("failed to get timestamptz from datatype %v", r.colType.String())) + return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get timestamptz from datatype %v", r.colType.String()) } values := make([]int64, r.numRows) @@ -460,7 +458,7 @@ func (r *PayloadReader) GetTimestamptzFromPayload() ([]int64, []bool, error) { } if valuesRead != r.numRows { - return nil, nil, merr.WrapErrParameterInvalid(r.numRows, valuesRead, "valuesRead is not equal to rows") + return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead) } return values, validData, nil @@ -471,7 +469,7 @@ func (r *PayloadReader) GetTimestamptzFromPayload() ([]int64, []bool, error) { } if valuesRead != r.numRows { - return nil, nil, merr.WrapErrParameterInvalid(r.numRows, valuesRead, "valuesRead is not equal to rows") + return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead) } return values, nil, nil @@ -479,7 +477,7 @@ func (r *PayloadReader) GetTimestamptzFromPayload() ([]int64, []bool, error) { func (r *PayloadReader) GetStringFromPayload() ([]string, []bool, error) { if r.colType != schemapb.DataType_String && r.colType != schemapb.DataType_VarChar { - return nil, nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("failed to get string from datatype %v", r.colType.String())) + return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get string from datatype %v", r.colType.String()) } if r.nullable { @@ -491,7 +489,7 @@ func (r *PayloadReader) GetStringFromPayload() ([]string, []bool, error) { } if valuesRead != r.numRows { - return nil, nil, merr.WrapErrParameterInvalid(r.numRows, valuesRead, "valuesRead is not equal to rows") + return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead) } return values, validData, nil } @@ -506,7 +504,7 @@ func (r *PayloadReader) GetStringFromPayload() ([]string, []bool, error) { func (r *PayloadReader) GetArrayFromPayload() ([]*schemapb.ScalarField, []bool, error) { if r.colType != schemapb.DataType_Array { - return nil, nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("failed to get array from datatype %v", r.colType.String())) + return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get array from datatype %v", r.colType.String()) } if r.nullable { @@ -529,7 +527,7 @@ func (r *PayloadReader) GetArrayFromPayload() ([]*schemapb.ScalarField, []bool, func (r *PayloadReader) GetVectorArrayFromPayload() ([]*schemapb.VectorField, error) { if r.colType != schemapb.DataType_ArrayOfVector { - return nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("failed to get vector from datatype %v", r.colType.String())) + return nil, merr.WrapErrDataIntegrityMsg("failed to get vector from datatype %v", r.colType.String()) } return readVectorArrayFromListArray(r) @@ -537,7 +535,7 @@ func (r *PayloadReader) GetVectorArrayFromPayload() ([]*schemapb.VectorField, er func (r *PayloadReader) GetJSONFromPayload() ([][]byte, []bool, error) { if r.colType != schemapb.DataType_JSON { - return nil, nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("failed to get json from datatype %v", r.colType.String())) + return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get json from datatype %v", r.colType.String()) } if r.nullable { @@ -556,7 +554,7 @@ func (r *PayloadReader) GetJSONFromPayload() ([][]byte, []bool, error) { func (r *PayloadReader) GetGeometryFromPayload() ([][]byte, []bool, error) { if r.colType != schemapb.DataType_Geometry { - return nil, nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("failed to get Geometry from datatype %v", r.colType.String())) + return nil, nil, merr.WrapErrDataIntegrityMsg("failed to get Geometry from datatype %v", r.colType.String()) } if r.nullable { @@ -575,7 +573,7 @@ func (r *PayloadReader) GetGeometryFromPayload() ([][]byte, []bool, error) { func (r *PayloadReader) GetByteArrayDataSet() (*DataSet[parquet.ByteArray, *file.ByteArrayColumnChunkReader], error) { if r.colType != schemapb.DataType_String && r.colType != schemapb.DataType_VarChar { - return nil, fmt.Errorf("failed to get string from datatype %v", r.colType.String()) + return nil, merr.WrapErrServiceInternalMsg("failed to get string from datatype %v", r.colType.String()) } return NewDataSet[parquet.ByteArray, *file.ByteArrayColumnChunkReader](r.reader, 0, r.numRows), nil @@ -610,7 +608,7 @@ func readVectorArrayFromListArray(r *PayloadReader) ([]*schemapb.VectorField, er defer table.Release() if table.NumCols() != 1 { - return nil, fmt.Errorf("expected 1 column, got %d", table.NumCols()) + return nil, merr.WrapErrDataIntegrityMsg("expected 1 column, got %d", table.NumCols()) } column := table.Column(0) @@ -625,17 +623,17 @@ func readVectorArrayFromListArray(r *PayloadReader) ([]*schemapb.VectorField, er for _, chunk := range column.Data().Chunks() { listArray, ok := chunk.(*array.List) if !ok { - return nil, fmt.Errorf("expected ListArray, got %T", chunk) + return nil, merr.WrapErrDataIntegrityMsg("expected ListArray, got %T", chunk) } for i := 0; i < listArray.Len(); i++ { value, err := deserializeArrayOfVector(listArray, i, elementType, dim, true) if err != nil { - return nil, fmt.Errorf("failed to deserialize VectorArray at row %d: %w", len(result), err) + return nil, merr.Wrapf(err, "failed to deserialize VectorArray at row %d", len(result)) } vectorField, _ := value.(*schemapb.VectorField) if vectorField == nil { - return nil, fmt.Errorf("null value in VectorArray") + return nil, merr.WrapErrDataIntegrityMsg("null value in VectorArray") } result = append(result, vectorField) } @@ -653,7 +651,7 @@ func readNullableByteAndConvert[T any](r *PayloadReader, convert func([]byte) T) } if valuesRead != r.numRows { - return nil, nil, merr.WrapErrParameterInvalid(r.numRows, valuesRead, "valuesRead is not equal to rows") + return nil, nil, merr.WrapErrDataIntegrityMsg("valuesRead is not equal to rows: expected=%d actual=%d", r.numRows, valuesRead) } ret := make([]T, r.numRows) @@ -671,7 +669,7 @@ func readByteAndConvert[T any](r *PayloadReader, convert func(parquet.ByteArray) } if valuesRead != r.numRows { - return nil, fmt.Errorf("expect %d rows, but got valuesRead = %d", r.numRows, valuesRead) + return nil, merr.WrapErrDataIntegrityMsg("expect %d rows, but got valuesRead = %d", r.numRows, valuesRead) } ret := make([]T, r.numRows) @@ -684,7 +682,7 @@ func readByteAndConvert[T any](r *PayloadReader, convert func(parquet.ByteArray) // GetBinaryVectorFromPayload returns vector, dimension, validData, numRows, error func (r *PayloadReader) GetBinaryVectorFromPayload() ([]byte, int, []bool, int, error) { if r.colType != schemapb.DataType_BinaryVector { - return nil, -1, nil, 0, fmt.Errorf("failed to get binary vector from datatype %v", r.colType.String()) + return nil, -1, nil, 0, merr.WrapErrServiceInternalMsg("failed to get binary vector from datatype %v", r.colType.String()) } if r.nullable { @@ -699,7 +697,7 @@ func (r *PayloadReader) GetBinaryVectorFromPayload() ([]byte, int, []bool, int, } if arrowSchema.NumFields() != 1 { - return nil, -1, nil, 0, fmt.Errorf("expected 1 field, got %d", arrowSchema.NumFields()) + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expected 1 field, got %d", arrowSchema.NumFields()) } field := arrowSchema.Field(0) @@ -707,17 +705,17 @@ func (r *PayloadReader) GetBinaryVectorFromPayload() ([]byte, int, []bool, int, if field.Type.ID() == arrow.BINARY { if !field.HasMetadata() { - return nil, -1, nil, 0, fmt.Errorf("nullable binary vector field is missing metadata") + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("nullable binary vector field is missing metadata") } metadata := field.Metadata dimStr, ok := metadata.GetValue("dim") if !ok { - return nil, -1, nil, 0, fmt.Errorf("nullable binary vector metadata missing required 'dim' field") + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("nullable binary vector metadata missing required 'dim' field") } var err error dim, err = strconv.Atoi(dimStr) if err != nil { - return nil, -1, nil, 0, fmt.Errorf("invalid dim value in metadata: %v", err) + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("invalid dim value in metadata: %v", err) } dim = dim / 8 } else { @@ -735,7 +733,7 @@ func (r *PayloadReader) GetBinaryVectorFromPayload() ([]byte, int, []bool, int, defer table.Release() if table.NumCols() != 1 { - return nil, -1, nil, 0, fmt.Errorf("expected 1 column, got %d", table.NumCols()) + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expected 1 column, got %d", table.NumCols()) } column := table.Column(0) @@ -760,7 +758,7 @@ func (r *PayloadReader) GetBinaryVectorFromPayload() ([]byte, int, []bool, int, } if valuesRead != r.numRows { - return nil, -1, nil, 0, fmt.Errorf("expect %d rows, but got valuesRead = %d", r.numRows, valuesRead) + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expect %d rows, but got valuesRead = %d", r.numRows, valuesRead) } ret := make([]byte, int64(dim)*r.numRows) @@ -773,7 +771,7 @@ func (r *PayloadReader) GetBinaryVectorFromPayload() ([]byte, int, []bool, int, // GetFloat16VectorFromPayload returns vector, dimension, validData, numRows, error func (r *PayloadReader) GetFloat16VectorFromPayload() ([]byte, int, []bool, int, error) { if r.colType != schemapb.DataType_Float16Vector { - return nil, -1, nil, 0, fmt.Errorf("failed to get float16 vector from datatype %v", r.colType.String()) + return nil, -1, nil, 0, merr.WrapErrServiceInternalMsg("failed to get float16 vector from datatype %v", r.colType.String()) } if r.nullable { fileReader, err := pqarrow.NewFileReader(r.reader, pqarrow.ArrowReadProperties{}, memory.DefaultAllocator) @@ -787,7 +785,7 @@ func (r *PayloadReader) GetFloat16VectorFromPayload() ([]byte, int, []bool, int, } if arrowSchema.NumFields() != 1 { - return nil, -1, nil, 0, fmt.Errorf("expected 1 field, got %d", arrowSchema.NumFields()) + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expected 1 field, got %d", arrowSchema.NumFields()) } field := arrowSchema.Field(0) @@ -795,17 +793,17 @@ func (r *PayloadReader) GetFloat16VectorFromPayload() ([]byte, int, []bool, int, if field.Type.ID() == arrow.BINARY { if !field.HasMetadata() { - return nil, -1, nil, 0, fmt.Errorf("nullable float16 vector field is missing metadata") + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("nullable float16 vector field is missing metadata") } metadata := field.Metadata dimStr, ok := metadata.GetValue("dim") if !ok { - return nil, -1, nil, 0, fmt.Errorf("nullable float16 vector metadata missing required 'dim' field") + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("nullable float16 vector metadata missing required 'dim' field") } var err error dim, err = strconv.Atoi(dimStr) if err != nil { - return nil, -1, nil, 0, fmt.Errorf("invalid dim value in metadata: %v", err) + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("invalid dim value in metadata: %v", err) } } else { col, err := r.reader.RowGroup(0).Column(0) @@ -822,7 +820,7 @@ func (r *PayloadReader) GetFloat16VectorFromPayload() ([]byte, int, []bool, int, defer table.Release() if table.NumCols() != 1 { - return nil, -1, nil, 0, fmt.Errorf("expected 1 column, got %d", table.NumCols()) + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expected 1 column, got %d", table.NumCols()) } column := table.Column(0) @@ -848,7 +846,7 @@ func (r *PayloadReader) GetFloat16VectorFromPayload() ([]byte, int, []bool, int, } if valuesRead != r.numRows { - return nil, -1, nil, 0, fmt.Errorf("expect %d rows, but got valuesRead = %d", r.numRows, valuesRead) + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expect %d rows, but got valuesRead = %d", r.numRows, valuesRead) } ret := make([]byte, int64(dim*2)*r.numRows) @@ -861,7 +859,7 @@ func (r *PayloadReader) GetFloat16VectorFromPayload() ([]byte, int, []bool, int, // GetBFloat16VectorFromPayload returns vector, dimension, validData, numRows, error func (r *PayloadReader) GetBFloat16VectorFromPayload() ([]byte, int, []bool, int, error) { if r.colType != schemapb.DataType_BFloat16Vector { - return nil, -1, nil, 0, fmt.Errorf("failed to get bfloat16 vector from datatype %v", r.colType.String()) + return nil, -1, nil, 0, merr.WrapErrServiceInternalMsg("failed to get bfloat16 vector from datatype %v", r.colType.String()) } if r.nullable { fileReader, err := pqarrow.NewFileReader(r.reader, pqarrow.ArrowReadProperties{}, memory.DefaultAllocator) @@ -875,7 +873,7 @@ func (r *PayloadReader) GetBFloat16VectorFromPayload() ([]byte, int, []bool, int } if arrowSchema.NumFields() != 1 { - return nil, -1, nil, 0, fmt.Errorf("expected 1 field, got %d", arrowSchema.NumFields()) + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expected 1 field, got %d", arrowSchema.NumFields()) } field := arrowSchema.Field(0) @@ -883,17 +881,17 @@ func (r *PayloadReader) GetBFloat16VectorFromPayload() ([]byte, int, []bool, int if field.Type.ID() == arrow.BINARY { if !field.HasMetadata() { - return nil, -1, nil, 0, fmt.Errorf("nullable bfloat16 vector field is missing metadata") + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("nullable bfloat16 vector field is missing metadata") } metadata := field.Metadata dimStr, ok := metadata.GetValue("dim") if !ok { - return nil, -1, nil, 0, fmt.Errorf("nullable bfloat16 vector metadata missing required 'dim' field") + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("nullable bfloat16 vector metadata missing required 'dim' field") } var err error dim, err = strconv.Atoi(dimStr) if err != nil { - return nil, -1, nil, 0, fmt.Errorf("invalid dim value in metadata: %v", err) + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("invalid dim value in metadata: %v", err) } } else { col, err := r.reader.RowGroup(0).Column(0) @@ -910,7 +908,7 @@ func (r *PayloadReader) GetBFloat16VectorFromPayload() ([]byte, int, []bool, int defer table.Release() if table.NumCols() != 1 { - return nil, -1, nil, 0, fmt.Errorf("expected 1 column, got %d", table.NumCols()) + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expected 1 column, got %d", table.NumCols()) } column := table.Column(0) @@ -936,7 +934,7 @@ func (r *PayloadReader) GetBFloat16VectorFromPayload() ([]byte, int, []bool, int } if valuesRead != r.numRows { - return nil, -1, nil, 0, fmt.Errorf("expect %d rows, but got valuesRead = %d", r.numRows, valuesRead) + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expect %d rows, but got valuesRead = %d", r.numRows, valuesRead) } ret := make([]byte, int64(dim*2)*r.numRows) @@ -949,7 +947,7 @@ func (r *PayloadReader) GetBFloat16VectorFromPayload() ([]byte, int, []bool, int // GetFloatVectorFromPayload returns vector, dimension, validData, numRows, error func (r *PayloadReader) GetFloatVectorFromPayload() ([]float32, int, []bool, int, error) { if r.colType != schemapb.DataType_FloatVector { - return nil, -1, nil, 0, fmt.Errorf("failed to get float vector from datatype %v", r.colType.String()) + return nil, -1, nil, 0, merr.WrapErrServiceInternalMsg("failed to get float vector from datatype %v", r.colType.String()) } if r.nullable { fileReader, err := pqarrow.NewFileReader(r.reader, pqarrow.ArrowReadProperties{}, memory.DefaultAllocator) @@ -963,7 +961,7 @@ func (r *PayloadReader) GetFloatVectorFromPayload() ([]float32, int, []bool, int } if arrowSchema.NumFields() != 1 { - return nil, -1, nil, 0, fmt.Errorf("expected 1 field, got %d", arrowSchema.NumFields()) + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expected 1 field, got %d", arrowSchema.NumFields()) } field := arrowSchema.Field(0) @@ -971,17 +969,17 @@ func (r *PayloadReader) GetFloatVectorFromPayload() ([]float32, int, []bool, int if field.Type.ID() == arrow.BINARY { if !field.HasMetadata() { - return nil, -1, nil, 0, fmt.Errorf("nullable float vector field is missing metadata") + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("nullable float vector field is missing metadata") } metadata := field.Metadata dimStr, ok := metadata.GetValue("dim") if !ok { - return nil, -1, nil, 0, fmt.Errorf("nullable float vector metadata missing required 'dim' field") + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("nullable float vector metadata missing required 'dim' field") } var err error dim, err = strconv.Atoi(dimStr) if err != nil { - return nil, -1, nil, 0, fmt.Errorf("invalid dim value in metadata: %v", err) + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("invalid dim value in metadata: %v", err) } } else { col, err := r.reader.RowGroup(0).Column(0) @@ -998,7 +996,7 @@ func (r *PayloadReader) GetFloatVectorFromPayload() ([]float32, int, []bool, int defer table.Release() if table.NumCols() != 1 { - return nil, -1, nil, 0, fmt.Errorf("expected 1 column, got %d", table.NumCols()) + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expected 1 column, got %d", table.NumCols()) } column := table.Column(0) @@ -1024,7 +1022,7 @@ func (r *PayloadReader) GetFloatVectorFromPayload() ([]float32, int, []bool, int } if valuesRead != r.numRows { - return nil, -1, nil, 0, fmt.Errorf("expect %d rows, but got valuesRead = %d", r.numRows, valuesRead) + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expect %d rows, but got valuesRead = %d", r.numRows, valuesRead) } ret := make([]float32, int64(dim)*r.numRows) @@ -1037,7 +1035,7 @@ func (r *PayloadReader) GetFloatVectorFromPayload() ([]float32, int, []bool, int // GetSparseFloatVectorFromPayload returns fieldData, dimension, validData, error func (r *PayloadReader) GetSparseFloatVectorFromPayload() (*SparseFloatVectorFieldData, int, []bool, error) { if !typeutil.IsSparseFloatVectorType(r.colType) { - return nil, -1, nil, fmt.Errorf("failed to get sparse float vector from datatype %v", r.colType.String()) + return nil, -1, nil, merr.WrapErrServiceInternalMsg("failed to get sparse float vector from datatype %v", r.colType.String()) } if r.nullable { @@ -1056,7 +1054,7 @@ func (r *PayloadReader) GetSparseFloatVectorFromPayload() (*SparseFloatVectorFie defer table.Release() if table.NumCols() != 1 { - return nil, -1, nil, fmt.Errorf("expected 1 column, got %d", table.NumCols()) + return nil, -1, nil, merr.WrapErrDataIntegrityMsg("expected 1 column, got %d", table.NumCols()) } column := table.Column(0) @@ -1064,7 +1062,7 @@ func (r *PayloadReader) GetSparseFloatVectorFromPayload() (*SparseFloatVectorFie for _, chunk := range column.Data().Chunks() { binaryArray, ok := chunk.(*array.Binary) if !ok { - return nil, -1, nil, fmt.Errorf("expected Binary array, got %T", chunk) + return nil, -1, nil, merr.WrapErrDataIntegrityMsg("expected Binary array, got %T", chunk) } for i := 0; i < binaryArray.Len(); i++ { @@ -1072,7 +1070,7 @@ func (r *PayloadReader) GetSparseFloatVectorFromPayload() (*SparseFloatVectorFie if validData[offset+i] { value := binaryArray.Value(i) if len(value)%8 != 0 { - return nil, -1, nil, errors.New("invalid bytesData length") + return nil, -1, nil, merr.WrapErrDataIntegrityMsg("invalid bytesData length") } fieldData.Contents = append(fieldData.Contents, append([]byte{}, value...)) rowDim := typeutil.SparseFloatRowDim(value) @@ -1093,14 +1091,14 @@ func (r *PayloadReader) GetSparseFloatVectorFromPayload() (*SparseFloatVectorFie return nil, -1, nil, err } if valuesRead != r.numRows { - return nil, -1, nil, fmt.Errorf("expect %d binary, but got = %d", r.numRows, valuesRead) + return nil, -1, nil, merr.WrapErrDataIntegrityMsg("expect %d binary, but got = %d", r.numRows, valuesRead) } fieldData := &SparseFloatVectorFieldData{} for _, value := range values { if len(value)%8 != 0 { - return nil, -1, nil, errors.New("invalid bytesData length") + return nil, -1, nil, merr.WrapErrDataIntegrityMsg("invalid bytesData length") } fieldData.Contents = append(fieldData.Contents, value) @@ -1116,7 +1114,7 @@ func (r *PayloadReader) GetSparseFloatVectorFromPayload() (*SparseFloatVectorFie // GetInt8VectorFromPayload returns vector, dimension, validData, numRows, error func (r *PayloadReader) GetInt8VectorFromPayload() ([]int8, int, []bool, int, error) { if r.colType != schemapb.DataType_Int8Vector { - return nil, -1, nil, 0, fmt.Errorf("failed to get int8 vector from datatype %v", r.colType.String()) + return nil, -1, nil, 0, merr.WrapErrServiceInternalMsg("failed to get int8 vector from datatype %v", r.colType.String()) } if r.nullable { fileReader, err := pqarrow.NewFileReader(r.reader, pqarrow.ArrowReadProperties{}, memory.DefaultAllocator) @@ -1130,7 +1128,7 @@ func (r *PayloadReader) GetInt8VectorFromPayload() ([]int8, int, []bool, int, er } if arrowSchema.NumFields() != 1 { - return nil, -1, nil, 0, fmt.Errorf("expected 1 field, got %d", arrowSchema.NumFields()) + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expected 1 field, got %d", arrowSchema.NumFields()) } field := arrowSchema.Field(0) @@ -1138,17 +1136,17 @@ func (r *PayloadReader) GetInt8VectorFromPayload() ([]int8, int, []bool, int, er if field.Type.ID() == arrow.BINARY { if !field.HasMetadata() { - return nil, -1, nil, 0, fmt.Errorf("nullable int8 vector field is missing metadata") + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("nullable int8 vector field is missing metadata") } metadata := field.Metadata dimStr, ok := metadata.GetValue("dim") if !ok { - return nil, -1, nil, 0, fmt.Errorf("nullable int8 vector metadata missing required 'dim' field") + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("nullable int8 vector metadata missing required 'dim' field") } var err error dim, err = strconv.Atoi(dimStr) if err != nil { - return nil, -1, nil, 0, fmt.Errorf("invalid dim value in metadata: %v", err) + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("invalid dim value in metadata: %v", err) } } else { col, err := r.reader.RowGroup(0).Column(0) @@ -1165,7 +1163,7 @@ func (r *PayloadReader) GetInt8VectorFromPayload() ([]int8, int, []bool, int, er defer table.Release() if table.NumCols() != 1 { - return nil, -1, nil, 0, fmt.Errorf("expected 1 column, got %d", table.NumCols()) + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expected 1 column, got %d", table.NumCols()) } column := table.Column(0) @@ -1191,7 +1189,7 @@ func (r *PayloadReader) GetInt8VectorFromPayload() ([]int8, int, []bool, int, er } if valuesRead != r.numRows { - return nil, -1, nil, 0, fmt.Errorf("expect %d rows, but got valuesRead = %d", r.numRows, valuesRead) + return nil, -1, nil, 0, merr.WrapErrDataIntegrityMsg("expect %d rows, but got valuesRead = %d", r.numRows, valuesRead) } ret := make([]int8, int64(dim)*r.numRows) @@ -1220,7 +1218,7 @@ func ReadDataFromAllRowGroups[T any, E interface { for i := 0; i < reader.NumRowGroups(); i++ { if columnIdx >= reader.RowGroup(i).NumColumns() { - return -1, fmt.Errorf("try to fetch %d-th column of reader but row group has only %d column(s)", columnIdx, reader.RowGroup(i).NumColumns()) + return -1, merr.WrapErrDataIntegrityMsg("try to fetch %d-th column of reader but row group has only %d column(s)", columnIdx, reader.RowGroup(i).NumColumns()) } column, err := reader.RowGroup(i).Column(columnIdx) if err != nil { @@ -1229,7 +1227,7 @@ func ReadDataFromAllRowGroups[T any, E interface { cReader, ok := column.(E) if !ok { - return -1, fmt.Errorf("expect type %T, but got %T", *new(E), column) + return -1, merr.WrapErrDataIntegrityMsg("expect type %T, but got %T", *new(E), column) } _, valuesRead, err := cReader.ReadBatch(numRows, values[offset:], nil, nil) @@ -1272,7 +1270,7 @@ func (s *DataSet[T, E]) nextGroup() error { cReader, ok := column.(E) if !ok { - return fmt.Errorf("expect type %T, but got %T", *new(E), column) + return merr.WrapErrDataIntegrityMsg("expect type %T, but got %T", *new(E), column) } s.groupID++ s.cReader = cReader @@ -1288,7 +1286,7 @@ func (s *DataSet[T, E]) HasNext() bool { func (s *DataSet[T, E]) NextBatch(batch int64) ([]T, error) { if s.groupID > s.reader.NumRowGroups() || (s.groupID == s.reader.NumRowGroups() && s.cnt >= s.numRows) || s.numRows == 0 { - return nil, errors.New("has no more data") + return nil, merr.WrapErrServiceInternalMsg("has no more data") } if s.groupID == 0 || s.cnt >= s.numRows { @@ -1343,7 +1341,7 @@ func ReadData[T any, E interface { reader, ok := chunk.(E) if !ok { log.Warn("the column data in parquet is not equal to field", zap.String("fieldName", field.Name), zap.String("actual type", chunk.DataType().Name())) - return -1, merr.WrapErrImportFailed(fmt.Sprintf("the column data in parquet is not equal to field: %s, but: %s", field.Name, chunk.DataType().Name())) + return -1, merr.WrapErrImportFailedMsg("the column data in parquet is not equal to field: %s, but: %s", field.Name, chunk.DataType().Name()) } nullBitset := bytesToBoolArray(dataNums, reader.NullBitmapBytes()) for i := 0; i < dataNums; i++ { @@ -1400,7 +1398,7 @@ func readNullableVectorData(column *arrow.Column, numRows int64, bytesPerVector for _, chunk := range chunks { binaryArray, ok := chunk.(*array.Binary) if !ok { - return nil, nil, merr.WrapErrParameterInvalidMsg("expected Binary array for nullable vector, got %T", chunk) + return nil, nil, merr.WrapErrDataIntegrityMsg("expected Binary array for nullable vector, got %T", chunk) } chunkLen := binaryArray.Len() @@ -1410,7 +1408,7 @@ func readNullableVectorData(column *arrow.Column, numRows int64, bytesPerVector valueBytes := binaryArray.ValueBytes() expected := chunkValidCount * bytesPerVector if len(valueBytes) != expected { - return nil, nil, merr.WrapErrParameterInvalidMsg( + return nil, nil, merr.WrapErrDataIntegrityMsg( "unexpected valueBytes length for nullable vector: got %d, expected %d", len(valueBytes), expected) } copy(ret[dataOffset:dataOffset+len(valueBytes)], valueBytes) diff --git a/internal/storage/payload_writer.go b/internal/storage/payload_writer.go index 47453c4f76..0880d6f58a 100644 --- a/internal/storage/payload_writer.go +++ b/internal/storage/payload_writer.go @@ -29,7 +29,6 @@ import ( "github.com/apache/arrow/go/v17/parquet" "github.com/apache/arrow/go/v17/parquet/compress" "github.com/apache/arrow/go/v17/parquet/pqarrow" - "github.com/cockroachdb/errors" "google.golang.org/protobuf/proto" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" @@ -303,17 +302,17 @@ func (w *NativePayloadWriter) AddDataToPayloadForUT(data interface{}, validData } return w.AddVectorArrayFieldDataToPayload(val) default: - return errors.New("unsupported datatype") + return merr.WrapErrServiceInternalMsg("unsupported datatype") } } func (w *NativePayloadWriter) AddBoolToPayload(data []bool, validData []bool) error { if w.finished { - return errors.New("can't append data to finished bool payload") + return merr.WrapErrServiceInternalMsg("can't append data to finished bool payload") } if len(data) == 0 { - return errors.New("can't add empty msgs into bool payload") + return merr.WrapErrServiceInternalMsg("can't add empty msgs into bool payload") } if !w.nullable && len(validData) != 0 { @@ -328,7 +327,7 @@ func (w *NativePayloadWriter) AddBoolToPayload(data []bool, validData []bool) er builder, ok := w.builder.(*array.BooleanBuilder) if !ok { - return errors.New("failed to cast ArrayBuilder") + return merr.WrapErrServiceInternalMsg("failed to cast ArrayBuilder") } builder.AppendValues(data, validData) @@ -337,11 +336,11 @@ func (w *NativePayloadWriter) AddBoolToPayload(data []bool, validData []bool) er func (w *NativePayloadWriter) AddByteToPayload(data []byte, validData []bool) error { if w.finished { - return errors.New("can't append data to finished byte payload") + return merr.WrapErrServiceInternalMsg("can't append data to finished byte payload") } if len(data) == 0 { - return errors.New("can't add empty msgs into byte payload") + return merr.WrapErrServiceInternalMsg("can't add empty msgs into byte payload") } if !w.nullable && len(validData) != 0 { @@ -356,7 +355,7 @@ func (w *NativePayloadWriter) AddByteToPayload(data []byte, validData []bool) er builder, ok := w.builder.(*array.Int8Builder) if !ok { - return errors.New("failed to cast ByteBuilder") + return merr.WrapErrServiceInternalMsg("failed to cast ByteBuilder") } builder.Reserve(len(data)) @@ -372,11 +371,11 @@ func (w *NativePayloadWriter) AddByteToPayload(data []byte, validData []bool) er func (w *NativePayloadWriter) AddInt8ToPayload(data []int8, validData []bool) error { if w.finished { - return errors.New("can't append data to finished int8 payload") + return merr.WrapErrServiceInternalMsg("can't append data to finished int8 payload") } if len(data) == 0 { - return errors.New("can't add empty msgs into int8 payload") + return merr.WrapErrServiceInternalMsg("can't add empty msgs into int8 payload") } if !w.nullable && len(validData) != 0 { @@ -391,7 +390,7 @@ func (w *NativePayloadWriter) AddInt8ToPayload(data []int8, validData []bool) er builder, ok := w.builder.(*array.Int8Builder) if !ok { - return errors.New("failed to cast Int8Builder") + return merr.WrapErrServiceInternalMsg("failed to cast Int8Builder") } builder.AppendValues(data, validData) @@ -400,11 +399,11 @@ func (w *NativePayloadWriter) AddInt8ToPayload(data []int8, validData []bool) er func (w *NativePayloadWriter) AddInt16ToPayload(data []int16, validData []bool) error { if w.finished { - return errors.New("can't append data to finished int16 payload") + return merr.WrapErrServiceInternalMsg("can't append data to finished int16 payload") } if len(data) == 0 { - return errors.New("can't add empty msgs into int16 payload") + return merr.WrapErrServiceInternalMsg("can't add empty msgs into int16 payload") } if !w.nullable && len(validData) != 0 { @@ -419,7 +418,7 @@ func (w *NativePayloadWriter) AddInt16ToPayload(data []int16, validData []bool) builder, ok := w.builder.(*array.Int16Builder) if !ok { - return errors.New("failed to cast Int16Builder") + return merr.WrapErrServiceInternalMsg("failed to cast Int16Builder") } builder.AppendValues(data, validData) @@ -428,11 +427,11 @@ func (w *NativePayloadWriter) AddInt16ToPayload(data []int16, validData []bool) func (w *NativePayloadWriter) AddInt32ToPayload(data []int32, validData []bool) error { if w.finished { - return errors.New("can't append data to finished int32 payload") + return merr.WrapErrServiceInternalMsg("can't append data to finished int32 payload") } if len(data) == 0 { - return errors.New("can't add empty msgs into int32 payload") + return merr.WrapErrServiceInternalMsg("can't add empty msgs into int32 payload") } if !w.nullable && len(validData) != 0 { @@ -447,7 +446,7 @@ func (w *NativePayloadWriter) AddInt32ToPayload(data []int32, validData []bool) builder, ok := w.builder.(*array.Int32Builder) if !ok { - return errors.New("failed to cast Int32Builder") + return merr.WrapErrServiceInternalMsg("failed to cast Int32Builder") } builder.AppendValues(data, validData) @@ -456,11 +455,11 @@ func (w *NativePayloadWriter) AddInt32ToPayload(data []int32, validData []bool) func (w *NativePayloadWriter) AddInt64ToPayload(data []int64, validData []bool) error { if w.finished { - return errors.New("can't append data to finished int64 payload") + return merr.WrapErrServiceInternalMsg("can't append data to finished int64 payload") } if len(data) == 0 { - return errors.New("can't add empty msgs into int64 payload") + return merr.WrapErrServiceInternalMsg("can't add empty msgs into int64 payload") } if !w.nullable && len(validData) != 0 { @@ -475,7 +474,7 @@ func (w *NativePayloadWriter) AddInt64ToPayload(data []int64, validData []bool) builder, ok := w.builder.(*array.Int64Builder) if !ok { - return errors.New("failed to cast Int64Builder") + return merr.WrapErrServiceInternalMsg("failed to cast Int64Builder") } builder.AppendValues(data, validData) @@ -484,11 +483,11 @@ func (w *NativePayloadWriter) AddInt64ToPayload(data []int64, validData []bool) func (w *NativePayloadWriter) AddFloatToPayload(data []float32, validData []bool) error { if w.finished { - return errors.New("can't append data to finished float payload") + return merr.WrapErrServiceInternalMsg("can't append data to finished float payload") } if len(data) == 0 { - return errors.New("can't add empty msgs into float payload") + return merr.WrapErrServiceInternalMsg("can't add empty msgs into float payload") } if !w.nullable && len(validData) != 0 { @@ -503,7 +502,7 @@ func (w *NativePayloadWriter) AddFloatToPayload(data []float32, validData []bool builder, ok := w.builder.(*array.Float32Builder) if !ok { - return errors.New("failed to cast FloatBuilder") + return merr.WrapErrServiceInternalMsg("failed to cast FloatBuilder") } builder.AppendValues(data, validData) @@ -512,11 +511,11 @@ func (w *NativePayloadWriter) AddFloatToPayload(data []float32, validData []bool func (w *NativePayloadWriter) AddDoubleToPayload(data []float64, validData []bool) error { if w.finished { - return errors.New("can't append data to finished double payload") + return merr.WrapErrServiceInternalMsg("can't append data to finished double payload") } if len(data) == 0 { - return errors.New("can't add empty msgs into double payload") + return merr.WrapErrServiceInternalMsg("can't add empty msgs into double payload") } if !w.nullable && len(validData) != 0 { @@ -531,7 +530,7 @@ func (w *NativePayloadWriter) AddDoubleToPayload(data []float64, validData []boo builder, ok := w.builder.(*array.Float64Builder) if !ok { - return errors.New("failed to cast DoubleBuilder") + return merr.WrapErrServiceInternalMsg("failed to cast DoubleBuilder") } builder.AppendValues(data, validData) @@ -540,11 +539,11 @@ func (w *NativePayloadWriter) AddDoubleToPayload(data []float64, validData []boo func (w *NativePayloadWriter) AddTimestamptzToPayload(data []int64, validData []bool) error { if w.finished { - return errors.New("can't append data to finished int64 payload") + return merr.WrapErrServiceInternalMsg("can't append data to finished int64 payload") } if len(data) == 0 { - return errors.New("can't add empty msgs into int64 payload") + return merr.WrapErrServiceInternalMsg("can't add empty msgs into int64 payload") } if !w.nullable && len(validData) != 0 { @@ -559,7 +558,7 @@ func (w *NativePayloadWriter) AddTimestamptzToPayload(data []int64, validData [] builder, ok := w.builder.(*array.Int64Builder) if !ok { - return errors.New("failed to cast Int64Builder") + return merr.WrapErrServiceInternalMsg("failed to cast Int64Builder") } builder.AppendValues(data, validData) @@ -568,7 +567,7 @@ func (w *NativePayloadWriter) AddTimestamptzToPayload(data []int64, validData [] func (w *NativePayloadWriter) AddOneStringToPayload(data string, isValid bool) error { if w.finished { - return errors.New("can't append data to finished string payload") + return merr.WrapErrServiceInternalMsg("can't append data to finished string payload") } if !w.nullable && !isValid { @@ -577,7 +576,7 @@ func (w *NativePayloadWriter) AddOneStringToPayload(data string, isValid bool) e builder, ok := w.builder.(*array.StringBuilder) if !ok { - return errors.New("failed to cast StringBuilder") + return merr.WrapErrServiceInternalMsg("failed to cast StringBuilder") } if !isValid { @@ -591,7 +590,7 @@ func (w *NativePayloadWriter) AddOneStringToPayload(data string, isValid bool) e func (w *NativePayloadWriter) AddOneArrayToPayload(data *schemapb.ScalarField, isValid bool) error { if w.finished { - return errors.New("can't append data to finished array payload") + return merr.WrapErrServiceInternalMsg("can't append data to finished array payload") } if !w.nullable && !isValid { @@ -600,12 +599,12 @@ func (w *NativePayloadWriter) AddOneArrayToPayload(data *schemapb.ScalarField, i bytes, err := proto.Marshal(data) if err != nil { - return errors.New("Marshal ListValue failed") + return merr.WrapErrServiceInternalMsg("Marshal ListValue failed") } builder, ok := w.builder.(*array.BinaryBuilder) if !ok { - return errors.New("failed to cast BinaryBuilder") + return merr.WrapErrServiceInternalMsg("failed to cast BinaryBuilder") } if !isValid { @@ -619,7 +618,7 @@ func (w *NativePayloadWriter) AddOneArrayToPayload(data *schemapb.ScalarField, i func (w *NativePayloadWriter) AddOneJSONToPayload(data []byte, isValid bool) error { if w.finished { - return errors.New("can't append data to finished json payload") + return merr.WrapErrServiceInternalMsg("can't append data to finished json payload") } if !w.nullable && !isValid { @@ -628,7 +627,7 @@ func (w *NativePayloadWriter) AddOneJSONToPayload(data []byte, isValid bool) err builder, ok := w.builder.(*array.BinaryBuilder) if !ok { - return errors.New("failed to cast JsonBuilder") + return merr.WrapErrServiceInternalMsg("failed to cast JsonBuilder") } if !isValid { @@ -642,7 +641,7 @@ func (w *NativePayloadWriter) AddOneJSONToPayload(data []byte, isValid bool) err func (w *NativePayloadWriter) AddOneGeometryToPayload(data []byte, isValid bool) error { if w.finished { - return errors.New("can't append data to finished geometry payload") + return merr.WrapErrServiceInternalMsg("can't append data to finished geometry payload") } if !w.nullable && !isValid { @@ -651,7 +650,7 @@ func (w *NativePayloadWriter) AddOneGeometryToPayload(data []byte, isValid bool) builder, ok := w.builder.(*array.BinaryBuilder) if !ok { - return errors.New("failed to cast geometryBuilder") + return merr.WrapErrServiceInternalMsg("failed to cast geometryBuilder") } if !isValid { @@ -672,7 +671,7 @@ func validateNullableVectorValidData(validData []bool) (int, error) { func (w *NativePayloadWriter) AddBinaryVectorToPayload(data []byte, dim int, validData []bool) error { if w.finished { - return errors.New("can't append data to finished binary vector payload") + return merr.WrapErrServiceInternalMsg("can't append data to finished binary vector payload") } byteLength := dim / 8 @@ -690,7 +689,7 @@ func (w *NativePayloadWriter) AddBinaryVectorToPayload(data []byte, dim int, val } } else { if len(data) == 0 { - return errors.New("can't add empty msgs into binary vector payload") + return merr.WrapErrServiceInternalMsg("can't add empty msgs into binary vector payload") } numRows = len(data) / byteLength if !w.nullable && len(validData) != 0 { @@ -702,7 +701,7 @@ func (w *NativePayloadWriter) AddBinaryVectorToPayload(data []byte, dim int, val if w.nullable { builder, ok := w.builder.(*array.BinaryBuilder) if !ok { - return errors.New("failed to cast to BinaryBuilder for nullable BinaryVector") + return merr.WrapErrServiceInternalMsg("failed to cast to BinaryBuilder for nullable BinaryVector") } builder.Reserve(numRows) @@ -718,7 +717,7 @@ func (w *NativePayloadWriter) AddBinaryVectorToPayload(data []byte, dim int, val } else { builder, ok := w.builder.(*array.FixedSizeBinaryBuilder) if !ok { - return errors.New("failed to cast to FixedSizeBinaryBuilder for non-nullable BinaryVector") + return merr.WrapErrServiceInternalMsg("failed to cast to FixedSizeBinaryBuilder for non-nullable BinaryVector") } builder.Reserve(numRows) @@ -732,7 +731,7 @@ func (w *NativePayloadWriter) AddBinaryVectorToPayload(data []byte, dim int, val func (w *NativePayloadWriter) AddFloatVectorToPayload(data []float32, dim int, validData []bool) error { if w.finished { - return errors.New("can't append data to finished float vector payload") + return merr.WrapErrServiceInternalMsg("can't append data to finished float vector payload") } var numRows int @@ -749,7 +748,7 @@ func (w *NativePayloadWriter) AddFloatVectorToPayload(data []float32, dim int, v } } else { if len(data) == 0 { - return errors.New("can't add empty msgs into float vector payload") + return merr.WrapErrServiceInternalMsg("can't add empty msgs into float vector payload") } numRows = len(data) / dim if !w.nullable && len(validData) != 0 { @@ -763,7 +762,7 @@ func (w *NativePayloadWriter) AddFloatVectorToPayload(data []float32, dim int, v if w.nullable { builder, ok := w.builder.(*array.BinaryBuilder) if !ok { - return errors.New("failed to cast to BinaryBuilder for nullable FloatVector") + return merr.WrapErrServiceInternalMsg("failed to cast to BinaryBuilder for nullable FloatVector") } builder.Reserve(numRows) @@ -785,7 +784,7 @@ func (w *NativePayloadWriter) AddFloatVectorToPayload(data []float32, dim int, v } else { builder, ok := w.builder.(*array.FixedSizeBinaryBuilder) if !ok { - return errors.New("failed to cast to FixedSizeBinaryBuilder for non-nullable FloatVector") + return merr.WrapErrServiceInternalMsg("failed to cast to FixedSizeBinaryBuilder for non-nullable FloatVector") } builder.Reserve(numRows) @@ -805,7 +804,7 @@ func (w *NativePayloadWriter) AddFloatVectorToPayload(data []float32, dim int, v func (w *NativePayloadWriter) AddFloat16VectorToPayload(data []byte, dim int, validData []bool) error { if w.finished { - return errors.New("can't append data to finished float16 payload") + return merr.WrapErrServiceInternalMsg("can't append data to finished float16 payload") } byteLength := dim * 2 @@ -823,7 +822,7 @@ func (w *NativePayloadWriter) AddFloat16VectorToPayload(data []byte, dim int, va } } else { if len(data) == 0 { - return errors.New("can't add empty msgs into float16 payload") + return merr.WrapErrServiceInternalMsg("can't add empty msgs into float16 payload") } numRows = len(data) / byteLength if !w.nullable && len(validData) != 0 { @@ -835,7 +834,7 @@ func (w *NativePayloadWriter) AddFloat16VectorToPayload(data []byte, dim int, va if w.nullable { builder, ok := w.builder.(*array.BinaryBuilder) if !ok { - return errors.New("failed to cast to BinaryBuilder for nullable Float16Vector") + return merr.WrapErrServiceInternalMsg("failed to cast to BinaryBuilder for nullable Float16Vector") } builder.Reserve(numRows) @@ -851,7 +850,7 @@ func (w *NativePayloadWriter) AddFloat16VectorToPayload(data []byte, dim int, va } else { builder, ok := w.builder.(*array.FixedSizeBinaryBuilder) if !ok { - return errors.New("failed to cast to FixedSizeBinaryBuilder for non-nullable Float16Vector") + return merr.WrapErrServiceInternalMsg("failed to cast to FixedSizeBinaryBuilder for non-nullable Float16Vector") } builder.Reserve(numRows) @@ -865,7 +864,7 @@ func (w *NativePayloadWriter) AddFloat16VectorToPayload(data []byte, dim int, va func (w *NativePayloadWriter) AddBFloat16VectorToPayload(data []byte, dim int, validData []bool) error { if w.finished { - return errors.New("can't append data to finished BFloat16 payload") + return merr.WrapErrServiceInternalMsg("can't append data to finished BFloat16 payload") } byteLength := dim * 2 @@ -883,7 +882,7 @@ func (w *NativePayloadWriter) AddBFloat16VectorToPayload(data []byte, dim int, v } } else { if len(data) == 0 { - return errors.New("can't add empty msgs into BFloat16 payload") + return merr.WrapErrServiceInternalMsg("can't add empty msgs into BFloat16 payload") } numRows = len(data) / byteLength if !w.nullable && len(validData) != 0 { @@ -895,7 +894,7 @@ func (w *NativePayloadWriter) AddBFloat16VectorToPayload(data []byte, dim int, v if w.nullable { builder, ok := w.builder.(*array.BinaryBuilder) if !ok { - return errors.New("failed to cast to BinaryBuilder for nullable BFloat16Vector") + return merr.WrapErrServiceInternalMsg("failed to cast to BinaryBuilder for nullable BFloat16Vector") } builder.Reserve(numRows) @@ -911,7 +910,7 @@ func (w *NativePayloadWriter) AddBFloat16VectorToPayload(data []byte, dim int, v } else { builder, ok := w.builder.(*array.FixedSizeBinaryBuilder) if !ok { - return errors.New("failed to cast to FixedSizeBinaryBuilder for non-nullable BFloat16Vector") + return merr.WrapErrServiceInternalMsg("failed to cast to FixedSizeBinaryBuilder for non-nullable BFloat16Vector") } builder.Reserve(numRows) @@ -925,7 +924,7 @@ func (w *NativePayloadWriter) AddBFloat16VectorToPayload(data []byte, dim int, v func (w *NativePayloadWriter) AddSparseFloatVectorToPayload(data *SparseFloatVectorFieldData) error { if w.finished { - return errors.New("can't append data to finished sparse float vector payload") + return merr.WrapErrServiceInternalMsg("can't append data to finished sparse float vector payload") } var numRows int @@ -949,7 +948,7 @@ func (w *NativePayloadWriter) AddSparseFloatVectorToPayload(data *SparseFloatVec builder, ok := w.builder.(*array.BinaryBuilder) if !ok { - return errors.New("failed to cast SparseFloatVectorBuilder") + return merr.WrapErrServiceInternalMsg("failed to cast SparseFloatVectorBuilder") } builder.Reserve(numRows) @@ -968,7 +967,7 @@ func (w *NativePayloadWriter) AddSparseFloatVectorToPayload(data *SparseFloatVec func (w *NativePayloadWriter) AddInt8VectorToPayload(data []int8, dim int, validData []bool) error { if w.finished { - return errors.New("can't append data to finished int8 vector payload") + return merr.WrapErrServiceInternalMsg("can't append data to finished int8 vector payload") } var numRows int @@ -985,7 +984,7 @@ func (w *NativePayloadWriter) AddInt8VectorToPayload(data []int8, dim int, valid } } else { if len(data) == 0 { - return errors.New("can't add empty msgs into int8 vector payload") + return merr.WrapErrServiceInternalMsg("can't add empty msgs into int8 vector payload") } numRows = len(data) / dim if !w.nullable && len(validData) != 0 { @@ -997,7 +996,7 @@ func (w *NativePayloadWriter) AddInt8VectorToPayload(data []int8, dim int, valid if w.nullable { builder, ok := w.builder.(*array.BinaryBuilder) if !ok { - return errors.New("failed to cast to BinaryBuilder for nullable Int8Vector") + return merr.WrapErrServiceInternalMsg("failed to cast to BinaryBuilder for nullable Int8Vector") } builder.Reserve(numRows) @@ -1015,7 +1014,7 @@ func (w *NativePayloadWriter) AddInt8VectorToPayload(data []int8, dim int, valid } else { builder, ok := w.builder.(*array.FixedSizeBinaryBuilder) if !ok { - return errors.New("failed to cast to FixedSizeBinaryBuilder for non-nullable Int8Vector") + return merr.WrapErrServiceInternalMsg("failed to cast to FixedSizeBinaryBuilder for non-nullable Int8Vector") } builder.Reserve(numRows) @@ -1031,7 +1030,7 @@ func (w *NativePayloadWriter) AddInt8VectorToPayload(data []int8, dim int, valid func (w *NativePayloadWriter) FinishPayloadWriter() error { if w.finished { - return errors.New("can't reuse a finished writer") + return merr.WrapErrServiceInternalMsg("can't reuse a finished writer") } w.finished = true @@ -1040,7 +1039,7 @@ func (w *NativePayloadWriter) FinishPayloadWriter() error { var metadata arrow.Metadata if w.dataType == schemapb.DataType_ArrayOfVector { if w.elementType == nil { - return errors.New("element type for DataType_ArrayOfVector must be set") + return merr.WrapErrServiceInternalMsg("element type for DataType_ArrayOfVector must be set") } metadata = arrow.NewMetadata( @@ -1099,7 +1098,7 @@ func (w *NativePayloadWriter) GetPayloadBufferFromWriter() ([]byte, error) { // The cpp version of payload writer handles the empty buffer as error if len(data) == 0 { - return nil, errors.New("empty buffer") + return nil, merr.WrapErrServiceInternalMsg("empty buffer") } return data, nil @@ -1176,16 +1175,16 @@ func MilvusDataTypeToArrowType(dataType schemapb.DataType, dim int) arrow.DataTy // AddVectorArrayFieldDataToPayload adds VectorArrayFieldData to payload using Arrow ListArray func (w *NativePayloadWriter) AddVectorArrayFieldDataToPayload(data *VectorArrayFieldData) error { if w.finished { - return errors.New("can't append data to finished vector array payload") + return merr.WrapErrServiceInternalMsg("can't append data to finished vector array payload") } if len(data.Data) == 0 { - return errors.New("can't add empty vector array field data") + return merr.WrapErrServiceInternalMsg("can't add empty vector array field data") } builder, ok := w.builder.(*array.ListBuilder) if !ok { - return errors.New("failed to cast to ListBuilder for VectorArray") + return merr.WrapErrServiceInternalMsg("failed to cast to ListBuilder for VectorArray") } switch data.ElementType { @@ -1200,7 +1199,7 @@ func (w *NativePayloadWriter) AddVectorArrayFieldDataToPayload(data *VectorArray case schemapb.DataType_Int8Vector: return w.addInt8VectorArrayToPayload(builder, data) default: - return merr.WrapErrParameterInvalidMsg(fmt.Sprintf("unsupported element type in VectorArray: %s", data.ElementType.String())) + return merr.WrapErrParameterInvalidMsg("unsupported element type in VectorArray: %s", data.ElementType.String()) } } diff --git a/internal/storage/pk_statistics.go b/internal/storage/pk_statistics.go index fb943d8440..cb6cd74797 100644 --- a/internal/storage/pk_statistics.go +++ b/internal/storage/pk_statistics.go @@ -17,15 +17,14 @@ package storage import ( - "fmt" "unsafe" - "github.com/cockroachdb/errors" "github.com/samber/lo" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/util/bloomfilter" "github.com/milvus-io/milvus/pkg/v3/common" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // pkStatistics contains pk field statistic information @@ -38,7 +37,7 @@ type PkStatistics struct { // update set pk min/max value if input value is beyond former range. func (st *PkStatistics) UpdateMinMax(pk PrimaryKey) error { if st == nil { - return errors.New("nil pk statistics") + return merr.WrapErrServiceInternalMsg("nil pk statistics") } if st.MinPK == nil { st.MinPK = pk @@ -78,7 +77,7 @@ func (st *PkStatistics) UpdatePKRange(ids FieldData) error { st.PkFilter.AddString(pk) } default: - return fmt.Errorf("invalid data type for primary key: %T", ids) + return merr.WrapErrServiceInternalMsg("invalid data type for primary key: %T", ids) } return nil } diff --git a/internal/storage/primary_key.go b/internal/storage/primary_key.go index e800166cea..f8adbd1f08 100644 --- a/internal/storage/primary_key.go +++ b/internal/storage/primary_key.go @@ -20,8 +20,6 @@ import ( "fmt" "strings" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/json" "github.com/milvus-io/milvus/pkg/v3/log" @@ -141,7 +139,7 @@ func (ip *Int64PrimaryKey) UnmarshalJSON(data []byte) error { func (ip *Int64PrimaryKey) SetValue(data interface{}) error { value, ok := data.(int64) if !ok { - return errors.New("wrong type value when setValue for Int64PrimaryKey") + return merr.WrapErrServiceInternalMsg("wrong type value when setValue for Int64PrimaryKey") } ip.Value = value @@ -241,7 +239,7 @@ func (vcp *VarCharPrimaryKey) UnmarshalJSON(data []byte) error { func (vcp *VarCharPrimaryKey) SetValue(data interface{}) error { value, ok := data.(string) if !ok { - return errors.New("wrong type value when setValue for VarCharPrimaryKey") + return merr.WrapErrServiceInternalMsg("wrong type value when setValue for VarCharPrimaryKey") } vcp.Value = value @@ -272,7 +270,7 @@ func GenPrimaryKeyByRawData(data interface{}, pkType schemapb.DataType) (Primary Value: data.(string), } default: - return nil, errors.New("not supported primary data type") + return nil, merr.WrapErrServiceInternalMsg("not supported primary data type") } return result, nil @@ -305,11 +303,11 @@ func GenVarcharPrimaryKeys(data ...string) ([]PrimaryKey, error) { func ParseFieldData2PrimaryKeys(data *schemapb.FieldData) ([]PrimaryKey, error) { ret := make([]PrimaryKey, 0) if data == nil { - return ret, errors.New("failed to parse pks from nil field data") + return ret, merr.WrapErrServiceInternalMsg("failed to parse pks from nil field data") } scalarData := data.GetScalars() if scalarData == nil { - return ret, errors.New("failed to parse pks from nil scalar data") + return ret, merr.WrapErrServiceInternalMsg("failed to parse pks from nil scalar data") } switch data.Type { @@ -324,7 +322,7 @@ func ParseFieldData2PrimaryKeys(data *schemapb.FieldData) ([]PrimaryKey, error) ret = append(ret, pk) } default: - return ret, errors.New("not supported primary data type") + return ret, merr.WrapErrServiceInternalMsg("not supported primary data type") } return ret, nil diff --git a/internal/storage/print_binlog.go b/internal/storage/print_binlog.go index 116450b54d..a675d8c33d 100644 --- a/internal/storage/print_binlog.go +++ b/internal/storage/print_binlog.go @@ -20,7 +20,6 @@ import ( "fmt" "os" - "github.com/cockroachdb/errors" "golang.org/x/exp/mmap" "google.golang.org/protobuf/proto" @@ -28,6 +27,7 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/json" "github.com/milvus-io/milvus/pkg/v3/common" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/tsoutil" ) @@ -89,7 +89,7 @@ func printBinlogFile(filename string) error { fmt.Printf("\tEndTimestamp: %v\n", physical) dataTypeName, ok := schemapb.DataType_name[int32(r.descriptorEvent.descriptorEventData.PayloadDataType)] if !ok { - return fmt.Errorf("undefine data type %d", r.descriptorEvent.descriptorEventData.PayloadDataType) + return merr.WrapErrServiceInternalMsg("undefine data type %d", r.descriptorEvent.descriptorEventData.PayloadDataType) } fmt.Printf("\tPayloadDataType: %v\n", dataTypeName) fmt.Printf("\tPostHeaderLengths: %v\n", r.descriptorEvent.descriptorEventData.PostHeaderLengths) @@ -112,7 +112,7 @@ func printBinlogFile(filename string) error { case InsertEventType: evd, ok := event.eventData.(*insertEventData) if !ok { - return errors.New("incorrect event data type") + return merr.WrapErrServiceInternalMsg("incorrect event data type") } fmt.Printf("event %d insert event:\n", eventNum) physical, _ = tsoutil.ParseTS(evd.StartTimestamp) @@ -125,7 +125,7 @@ func printBinlogFile(filename string) error { case DeleteEventType: evd, ok := event.eventData.(*deleteEventData) if !ok { - return errors.New("incorrect event data type") + return merr.WrapErrServiceInternalMsg("incorrect event data type") } fmt.Printf("event %d delete event:\n", eventNum) physical, _ = tsoutil.ParseTS(evd.StartTimestamp) @@ -138,7 +138,7 @@ func printBinlogFile(filename string) error { case CreateCollectionEventType: evd, ok := event.eventData.(*createCollectionEventData) if !ok { - return errors.New("incorrect event data type") + return merr.WrapErrServiceInternalMsg("incorrect event data type") } fmt.Printf("event %d create collection event:\n", eventNum) physical, _ = tsoutil.ParseTS(evd.StartTimestamp) @@ -151,7 +151,7 @@ func printBinlogFile(filename string) error { case DropCollectionEventType: evd, ok := event.eventData.(*dropCollectionEventData) if !ok { - return errors.New("incorrect event data type") + return merr.WrapErrServiceInternalMsg("incorrect event data type") } fmt.Printf("event %d drop collection event:\n", eventNum) physical, _ = tsoutil.ParseTS(evd.StartTimestamp) @@ -164,7 +164,7 @@ func printBinlogFile(filename string) error { case CreatePartitionEventType: evd, ok := event.eventData.(*createPartitionEventData) if !ok { - return errors.New("incorrect event data type") + return merr.WrapErrServiceInternalMsg("incorrect event data type") } fmt.Printf("event %d create partition event:\n", eventNum) physical, _ = tsoutil.ParseTS(evd.StartTimestamp) @@ -177,7 +177,7 @@ func printBinlogFile(filename string) error { case DropPartitionEventType: evd, ok := event.eventData.(*dropPartitionEventData) if !ok { - return errors.New("incorrect event data type") + return merr.WrapErrServiceInternalMsg("incorrect event data type") } fmt.Printf("event %d drop partition event:\n", eventNum) physical, _ = tsoutil.ParseTS(evd.StartTimestamp) @@ -193,14 +193,14 @@ func printBinlogFile(filename string) error { extra := make(map[string]interface{}) err = json.Unmarshal(extraBytes, &extra) if err != nil { - return fmt.Errorf("failed to unmarshal extra: %s", err.Error()) + return merr.WrapErrServiceInternalMsg("failed to unmarshal extra: %s", err.Error()) } fmt.Printf("indexBuildID: %v\n", extra["indexBuildID"]) fmt.Printf("indexName: %v\n", extra["indexName"]) fmt.Printf("indexID: %v\n", extra["indexID"]) evd, ok := event.eventData.(*indexFileEventData) if !ok { - return errors.New("incorrect event data type") + return merr.WrapErrServiceInternalMsg("incorrect event data type") } fmt.Printf("index file event num: %d\n", eventNum) physical, _ = tsoutil.ParseTS(evd.StartTimestamp) @@ -212,7 +212,7 @@ func printBinlogFile(filename string) error { return err } default: - return fmt.Errorf("undefined event typd %d", event.eventHeader.TypeCode) + return merr.WrapErrServiceInternalMsg("undefined event typd %d", event.eventHeader.TypeCode) } eventNum++ } @@ -421,7 +421,7 @@ func printPayloadValues(colType schemapb.DataType, reader PayloadReaderInterface } fmt.Println("===== SparseFloatVectorFieldData end =====") default: - return errors.New("undefined data type") + return merr.WrapErrServiceInternalMsg("undefined data type") } return nil } @@ -477,11 +477,11 @@ func printDDLPayloadValues(eventType EventTypeCode, colType schemapb.DataType, r } fmt.Printf("\t\t%d : drop partition: %v\n", i, req) default: - return fmt.Errorf("undefined ddl event type %d", eventType) + return merr.WrapErrServiceInternalMsg("undefined ddl event type %d", eventType) } } default: - return errors.New("undefined data type") + return merr.WrapErrServiceInternalMsg("undefined data type") } return nil } diff --git a/internal/storage/record_reader.go b/internal/storage/record_reader.go index b104e2d7db..831ce227ef 100644 --- a/internal/storage/record_reader.go +++ b/internal/storage/record_reader.go @@ -1,7 +1,6 @@ package storage import ( - "fmt" "io" "strconv" @@ -53,7 +52,7 @@ func newPackedRecordReader( ) (*packedRecordReader, error) { arrowSchema, err := ConvertToArrowSchema(schema, true) if err != nil { - return nil, merr.WrapErrParameterInvalid("convert collection schema [%s] to arrow schema error: %s", schema.Name, err.Error()) + return nil, merr.WrapErrSerializationFailed(err, "convert collection schema [%s] to arrow schema", schema.Name) } field2Col := make(map[FieldID]int) allFields := typeutil.GetAllFieldSchemas(schema) @@ -98,7 +97,7 @@ func (ir *IterativeRecordReader) Close() error { func (ir *IterativeRecordReader) Next() (rec Record, err error) { defer func() { if x := recover(); x != nil { - rec, err = nil, fmt.Errorf("internal error recovered: %v", x) + rec, err = nil, merr.WrapErrServiceInternalMsg("internal error recovered: %v", x) } }() if ir.cur == nil { @@ -169,7 +168,7 @@ func NewManifestReaderFromBinlogs(fieldBinlogs []*datapb.FieldBinlog, ) (*ManifestReader, error) { arrowSchema, err := ConvertToArrowSchema(schema, false) if err != nil { - return nil, merr.WrapErrParameterInvalid("convert collection schema [%s] to arrow schema error: %s", schema.Name, err.Error()) + return nil, merr.WrapErrSerializationFailed(err, "convert collection schema [%s] to arrow schema", schema.Name) } schemaHelper, err := typeutil.CreateSchemaHelper(schema) if err != nil { @@ -217,7 +216,7 @@ func NewManifestReader(manifest string, arrowSchema, err := ConvertToArrowSchema(schema, true) if err != nil { - return nil, merr.WrapErrParameterInvalid("convert collection schema [%s] to arrow schema error: %s", schema.Name, err.Error()) + return nil, merr.WrapErrSerializationFailed(err, "convert collection schema [%s] to arrow schema", schema.Name) } // The Arrow schema passed to storagev2 is a physical read contract, not a @@ -334,7 +333,7 @@ func (crr *CompositeBinlogRecordReader) Next() (Record, error) { nRows = int(r.NumRows()) } if nRows != int(r.NumRows()) { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("number of rows mismatch for field %d", f.FieldID)) + return nil, merr.WrapErrServiceInternalMsg("number of rows mismatch for field %d", f.FieldID) } } else { nonExistingFields = append(nonExistingFields, f) diff --git a/internal/storage/record_to_insert.go b/internal/storage/record_to_insert.go index f1ed85a333..e83c5721a0 100644 --- a/internal/storage/record_to_insert.go +++ b/internal/storage/record_to_insert.go @@ -15,11 +15,10 @@ package storage import ( - "fmt" - "github.com/apache/arrow/go/v17/arrow" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -54,13 +53,13 @@ func RecordToInsertData( col, ok := recordColumn(rec, fieldID) if !ok { if requiredFields != nil && requiredFields.Contain(fieldID) { - return nil, fmt.Errorf("required field %s (ID=%d) not found in record", + return nil, merr.WrapErrParameterInvalidMsg("required field %s (ID=%d) not found in record", field.GetName(), fieldID) } continue } if col.Len() != numRows { - return nil, fmt.Errorf("field %s (ID=%d) row count mismatch: %d != %d", + return nil, merr.WrapErrParameterInvalidMsg("field %s (ID=%d) row count mismatch: %d != %d", field.GetName(), fieldID, col.Len(), numRows) } dt := field.GetDataType() @@ -77,24 +76,24 @@ func RecordToInsertData( fd, ok := insertData.Data[fieldID] if !ok { - return nil, fmt.Errorf("field %s (ID=%d) not initialized in InsertData", + return nil, merr.WrapErrServiceInternalMsg("field %s (ID=%d) not initialized in InsertData", field.GetName(), fieldID) } entry, ok := serdeMap[dt] if !ok { - return nil, fmt.Errorf("unsupported data type %s for field %s", dt, field.GetName()) + return nil, merr.WrapErrParameterInvalidMsg("unsupported data type %s for field %s", dt, field.GetName()) } for i := 0; i < numRows; i++ { val, err := entry.deserialize(col, i, elementType, dim, true /* shouldCopy */) if err != nil { - return nil, fmt.Errorf("deserialize field %s row %d: %w", - field.GetName(), i, err) + return nil, merr.Wrapf(err, "deserialize field %s row %d", + field.GetName(), i) } if err := fd.AppendRow(val); err != nil { - return nil, fmt.Errorf("append field %s row %d: %w", - field.GetName(), i, err) + return nil, merr.Wrapf(err, "append field %s row %d", + field.GetName(), i) } } } @@ -106,7 +105,7 @@ func RecordToInsertData( rowNum = fd.RowNum() } if !ok || rowNum != numRows { - return nil, fmt.Errorf("required field ID=%d has %d rows, expected %d", + return nil, merr.WrapErrParameterInvalidMsg("required field ID=%d has %d rows, expected %d", fieldID, rowNum, numRows) } } diff --git a/internal/storage/record_writer.go b/internal/storage/record_writer.go index bfa36aa839..46cbbb2f67 100644 --- a/internal/storage/record_writer.go +++ b/internal/storage/record_writer.go @@ -152,15 +152,13 @@ func NewPackedRecordWriter( } writer, err := packed.NewPackedWriter(paths, arrowSchema, bufferSize, multiPartUploadSize, columnGroups, storageConfig, storagePluginContext) if err != nil { - return nil, merr.WrapErrServiceInternal( - fmt.Sprintf("can not new packed record writer %s", err.Error())) + return nil, merr.WrapErrStorage(err, "can not new packed record writer") } columnGroupUncompressed := make(map[typeutil.UniqueID]uint64) columnGroupCompressed := make(map[typeutil.UniqueID]uint64) pathsMap := make(map[typeutil.UniqueID]string) if len(paths) != len(columnGroups) { - return nil, merr.WrapErrParameterInvalid(len(paths), len(columnGroups), - "paths length is not equal to column groups length for packed record writer") + return nil, merr.WrapErrStorageMsg("paths length is not equal to column groups length for packed record writer: paths=%d columnGroups=%d", len(paths), len(columnGroups)) } for i, columnGroup := range columnGroups { columnGroupUncompressed[columnGroup.GroupID] = 0 @@ -345,8 +343,7 @@ func newPackedRecordBatchWriter( } writer, err := packed.NewFFIPackedWriter(basePath, arrowSchema, columnGroups, storageConfig, storagePluginContext, extraProperties) if err != nil { - return nil, merr.WrapErrServiceInternal( - fmt.Sprintf("can not new packed record writer %s", err.Error())) + return nil, merr.WrapErrStorage(err, "can not new packed record writer") } columnGroupUncompressed := make(map[typeutil.UniqueID]uint64) columnGroupCompressed := make(map[typeutil.UniqueID]uint64) @@ -380,8 +377,7 @@ func NewPackedSerializeWriter(bucketName string, paths []string, schema *schemap ) (*SerializeWriterImpl[*Value], error) { packedRecordWriter, err := NewPackedRecordWriter(bucketName, paths, schema, bufferSize, multiPartUploadSize, columnGroups, nil, nil) if err != nil { - return nil, merr.WrapErrServiceInternal( - fmt.Sprintf("can not new packed record writer %s", err.Error())) + return nil, merr.Wrap(err, "can not new packed record writer") } return NewSerializeRecordWriter(packedRecordWriter, func(v []*Value) (Record, error) { return ValueSerializer(v, schema) @@ -547,8 +543,7 @@ func NewPackedTextBatchWriter( writer, err := packed.NewFFISegmentWriter(arrowSchema, config, storageConfig) if err != nil { - return nil, merr.WrapErrServiceInternal( - fmt.Sprintf("can not new segment writer %s", err.Error())) + return nil, merr.WrapErrStorage(err, "can not new segment writer") } columnGroupUncompressed := make(map[typeutil.UniqueID]uint64) diff --git a/internal/storage/remote_chunk_manager.go b/internal/storage/remote_chunk_manager.go index 500f585c60..608115a64f 100644 --- a/internal/storage/remote_chunk_manager.go +++ b/internal/storage/remote_chunk_manager.go @@ -129,7 +129,7 @@ func (mcm *RemoteChunkManager) Path(ctx context.Context, filePath string) (strin return "", err } if !exist { - return "", errors.New("minio file manage cannot be found with filePath:" + filePath) + return "", merr.WrapErrServiceInternalMsg("minio file manage cannot be found with filePath:" + filePath) } return filePath, nil } @@ -194,7 +194,7 @@ func (mcm *RemoteChunkManager) MultiWrite(ctx context.Context, kvs map[string][] for key, value := range kvs { err := mcm.Write(ctx, key, value) if err != nil { - el = merr.Combine(el, errors.Wrapf(err, "failed to write %s", key)) + el = merr.Combine(el, merr.Wrapf(err, "failed to write %s", key)) } } return el @@ -259,7 +259,7 @@ func (mcm *RemoteChunkManager) MultiRead(ctx context.Context, keys []string) ([] for _, key := range keys { objectValue, err := mcm.Read(ctx, key) if err != nil { - el = merr.Combine(el, errors.Wrapf(err, "failed to read %s", key)) + el = merr.Combine(el, merr.Wrapf(err, "failed to read %s", key)) } objectsValues = append(objectsValues, objectValue) } @@ -268,7 +268,7 @@ func (mcm *RemoteChunkManager) MultiRead(ctx context.Context, keys []string) ([] } func (mcm *RemoteChunkManager) Mmap(ctx context.Context, filePath string) (*mmap.ReaderAt, error) { - return nil, errors.New("this method has not been implemented") + return nil, merr.WrapErrServiceInternalMsg("this method has not been implemented") } // ReadAt reads specific position data of minio storage if exists. @@ -310,7 +310,7 @@ func (mcm *RemoteChunkManager) MultiRemove(ctx context.Context, keys []string) e for _, key := range keys { err := mcm.Remove(ctx, key) if err != nil { - el = merr.Combine(el, errors.Wrapf(err, "failed to remove %s", key)) + el = merr.Combine(el, merr.Wrapf(err, "failed to remove %s", key)) } } return el diff --git a/internal/storage/rw.go b/internal/storage/rw.go index 8e3d0adfd8..ff68985ea4 100644 --- a/internal/storage/rw.go +++ b/internal/storage/rw.go @@ -19,7 +19,6 @@ package storage import ( "context" "encoding/base64" - "fmt" "io" "sort" @@ -91,7 +90,7 @@ func (o *rwOptions) validate() error { return merr.WrapErrServiceInternal("storage config is nil") } default: - return merr.WrapErrServiceInternal(fmt.Sprintf("unsupported storage version %d", o.version)) + return merr.WrapErrServiceInternalMsg("unsupported storage version %d", o.version) } return nil } @@ -335,7 +334,7 @@ func NewBinlogRecordReader(ctx context.Context, binlogs []*datapb.FieldBinlog, s // FIXME: add needed fields support rr = newIterativePackedRecordReader(paths, schema, rwOptions.bufferSize, rwOptions.storageConfig, pluginContext) default: - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("unsupported storage version %d", rwOptions.version)) + return nil, merr.WrapErrServiceInternalMsg("unsupported storage version %d", rwOptions.version) } if err != nil { return nil, err @@ -453,7 +452,7 @@ func NewBinlogRecordWriter(ctx context.Context, collectionID, partitionID, segme pluginContext, ) } - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("unsupported storage version %d", rwOptions.version)) + return nil, merr.WrapErrServiceInternalMsg("unsupported storage version %d", rwOptions.version) } func NewDeltalogWriter( @@ -493,7 +492,7 @@ func NewDeltalogWriter( []storagecommon.ColumnGroup{{GroupID: 0, Columns: []int{0, 1}, Fields: []int64{0, common.TimeStampField}}}, rwOptions.storageConfig, nil) default: - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("unsupported storage version %d", rwOptions.version)) + return nil, merr.WrapErrServiceInternalMsg("unsupported storage version %d", rwOptions.version) } } @@ -542,6 +541,6 @@ func NewDeltalogReader( }, }, nil default: - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("unsupported storage version %d", rwOptions.version)) + return nil, merr.WrapErrServiceInternalMsg("unsupported storage version %d", rwOptions.version) } } diff --git a/internal/storage/serde.go b/internal/storage/serde.go index ea422d3f67..202e6b62f6 100644 --- a/internal/storage/serde.go +++ b/internal/storage/serde.go @@ -58,10 +58,10 @@ type ( func validateVectorArrayElementCount(payloadLength int, elementsPerVector int) (int, error) { if elementsPerVector <= 0 { - return 0, fmt.Errorf("invalid vector width %d for ArrayOfVector", elementsPerVector) + return 0, merr.WrapErrStorageMsg("invalid vector width %d for ArrayOfVector", elementsPerVector) } if payloadLength%elementsPerVector != 0 { - return 0, fmt.Errorf("ArrayOfVector payload length %d is not divisible by vector width %d", payloadLength, elementsPerVector) + return 0, merr.WrapErrStorageMsg("ArrayOfVector payload length %d is not divisible by vector width %d", payloadLength, elementsPerVector) } return payloadLength / elementsPerVector, nil } @@ -125,7 +125,7 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { if arr, ok := a.(*array.Boolean); ok && i < arr.Len() { return arr.Value(i), nil } - return nil, fmt.Errorf("expected *array.Boolean, got %T", a) + return nil, merr.WrapErrServiceInternalMsg("expected *array.Boolean, got %T", a) }, serialize: func(b array.Builder, v any, _ schemapb.DataType) error { if v == nil { @@ -137,9 +137,9 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { builder.Append(v) return nil } - return fmt.Errorf("expected bool value, got %T", v) + return merr.WrapErrServiceInternalMsg("expected bool value, got %T", v) } - return fmt.Errorf("expected *array.BooleanBuilder, got %T", b) + return merr.WrapErrServiceInternalMsg("expected *array.BooleanBuilder, got %T", b) }, } m[schemapb.DataType_Int8] = serdeEntry{ @@ -153,7 +153,7 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { if arr, ok := a.(*array.Int8); ok && i < arr.Len() { return arr.Value(i), nil } - return nil, fmt.Errorf("expected *array.Int8, got %T", a) + return nil, merr.WrapErrServiceInternalMsg("expected *array.Int8, got %T", a) }, serialize: func(b array.Builder, v any, _ schemapb.DataType) error { if v == nil { @@ -165,9 +165,9 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { builder.Append(v) return nil } - return fmt.Errorf("expected int8 value, got %T", v) + return merr.WrapErrServiceInternalMsg("expected int8 value, got %T", v) } - return fmt.Errorf("expected *array.Int8Builder, got %T", b) + return merr.WrapErrServiceInternalMsg("expected *array.Int8Builder, got %T", b) }, } m[schemapb.DataType_Int16] = serdeEntry{ @@ -181,7 +181,7 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { if arr, ok := a.(*array.Int16); ok && i < arr.Len() { return arr.Value(i), nil } - return nil, fmt.Errorf("expected *array.Int16, got %T", a) + return nil, merr.WrapErrServiceInternalMsg("expected *array.Int16, got %T", a) }, serialize: func(b array.Builder, v any, _ schemapb.DataType) error { if v == nil { @@ -193,9 +193,9 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { builder.Append(v) return nil } - return fmt.Errorf("expected int16 value, got %T", v) + return merr.WrapErrServiceInternalMsg("expected int16 value, got %T", v) } - return fmt.Errorf("expected *array.Int16Builder, got %T", b) + return merr.WrapErrServiceInternalMsg("expected *array.Int16Builder, got %T", b) }, } m[schemapb.DataType_Int32] = serdeEntry{ @@ -209,7 +209,7 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { if arr, ok := a.(*array.Int32); ok && i < arr.Len() { return arr.Value(i), nil } - return nil, fmt.Errorf("expected *array.Int32, got %T", a) + return nil, merr.WrapErrServiceInternalMsg("expected *array.Int32, got %T", a) }, serialize: func(b array.Builder, v any, _ schemapb.DataType) error { if v == nil { @@ -221,9 +221,9 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { builder.Append(v) return nil } - return fmt.Errorf("expected int32 value, got %T", v) + return merr.WrapErrServiceInternalMsg("expected int32 value, got %T", v) } - return fmt.Errorf("expected *array.Int32Builder, got %T", b) + return merr.WrapErrServiceInternalMsg("expected *array.Int32Builder, got %T", b) }, } m[schemapb.DataType_Int64] = serdeEntry{ @@ -237,7 +237,7 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { if arr, ok := a.(*array.Int64); ok && i < arr.Len() { return arr.Value(i), nil } - return nil, fmt.Errorf("expected *array.Int64, got %T", a) + return nil, merr.WrapErrServiceInternalMsg("expected *array.Int64, got %T", a) }, serialize: func(b array.Builder, v any, _ schemapb.DataType) error { if v == nil { @@ -249,9 +249,9 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { builder.Append(v) return nil } - return fmt.Errorf("expected int64 value, got %T", v) + return merr.WrapErrServiceInternalMsg("expected int64 value, got %T", v) } - return fmt.Errorf("expected *array.Int64Builder, got %T", b) + return merr.WrapErrServiceInternalMsg("expected *array.Int64Builder, got %T", b) }, } m[schemapb.DataType_Float] = serdeEntry{ @@ -265,7 +265,7 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { if arr, ok := a.(*array.Float32); ok && i < arr.Len() { return arr.Value(i), nil } - return nil, fmt.Errorf("expected *array.Float32, got %T", a) + return nil, merr.WrapErrServiceInternalMsg("expected *array.Float32, got %T", a) }, serialize: func(b array.Builder, v any, _ schemapb.DataType) error { if v == nil { @@ -277,9 +277,9 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { builder.Append(v) return nil } - return fmt.Errorf("expected float32 value, got %T", v) + return merr.WrapErrServiceInternalMsg("expected float32 value, got %T", v) } - return fmt.Errorf("expected *array.Float32Builder, got %T", b) + return merr.WrapErrServiceInternalMsg("expected *array.Float32Builder, got %T", b) }, } m[schemapb.DataType_Double] = serdeEntry{ @@ -293,7 +293,7 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { if arr, ok := a.(*array.Float64); ok && i < arr.Len() { return arr.Value(i), nil } - return nil, fmt.Errorf("expected *array.Float64, got %T", a) + return nil, merr.WrapErrServiceInternalMsg("expected *array.Float64, got %T", a) }, serialize: func(b array.Builder, v any, _ schemapb.DataType) error { if v == nil { @@ -305,9 +305,9 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { builder.Append(v) return nil } - return fmt.Errorf("expected float64 value, got %T", v) + return merr.WrapErrServiceInternalMsg("expected float64 value, got %T", v) } - return fmt.Errorf("expected *array.Float64Builder, got %T", b) + return merr.WrapErrServiceInternalMsg("expected *array.Float64Builder, got %T", b) }, } m[schemapb.DataType_Timestamptz] = serdeEntry{ @@ -321,7 +321,7 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { if arr, ok := a.(*array.Int64); ok && i < arr.Len() { return arr.Value(i), nil } - return nil, fmt.Errorf("expected *array.Int64, got %T", a) + return nil, merr.WrapErrServiceInternalMsg("expected *array.Int64, got %T", a) }, serialize: func(b array.Builder, v any, _ schemapb.DataType) error { if v == nil { @@ -333,9 +333,9 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { builder.Append(v) return nil } - return fmt.Errorf("expected int64 value, got %T", v) + return merr.WrapErrServiceInternalMsg("expected int64 value, got %T", v) } - return fmt.Errorf("expected *array.Int64Builder, got %T", b) + return merr.WrapErrServiceInternalMsg("expected *array.Int64Builder, got %T", b) }, } stringEntry := serdeEntry{ @@ -353,7 +353,7 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { } return value, nil } - return nil, fmt.Errorf("expected *array.String, got %T", a) + return nil, merr.WrapErrServiceInternalMsg("expected *array.String, got %T", a) }, serialize: func(b array.Builder, v any, _ schemapb.DataType) error { if v == nil { @@ -365,9 +365,9 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { builder.Append(v) return nil } - return fmt.Errorf("expected string value, got %T", v) + return merr.WrapErrServiceInternalMsg("expected string value, got %T", v) } - return fmt.Errorf("expected *array.StringBuilder, got %T", b) + return merr.WrapErrServiceInternalMsg("expected *array.StringBuilder, got %T", b) }, } @@ -390,10 +390,10 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { if err := proto.Unmarshal(arr.Value(i), v); err == nil { return v, nil } else { - return nil, fmt.Errorf("failed to unmarshal ScalarField: %w", err) + return nil, merr.WrapErrSerializationFailed(err, "failed to unmarshal ScalarField") } } - return nil, fmt.Errorf("expected *array.Binary, got %T", a) + return nil, merr.WrapErrServiceInternalMsg("expected *array.Binary, got %T", a) }, serialize: func(b array.Builder, v any, _ schemapb.DataType) error { if v == nil { @@ -406,12 +406,12 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { builder.Append(bytes) return nil } else { - return fmt.Errorf("failed to marshal ScalarField: %w", err) + return merr.WrapErrSerializationFailed(err, "failed to marshal ScalarField") } } - return fmt.Errorf("expected *schemapb.ScalarField value, got %T", v) + return merr.WrapErrServiceInternalMsg("expected *schemapb.ScalarField value, got %T", v) } - return fmt.Errorf("expected *array.BinaryBuilder, got %T", b) + return merr.WrapErrServiceInternalMsg("expected *array.BinaryBuilder, got %T", b) }, } _ = eagerArrayEntry @@ -433,7 +433,7 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { } return value, nil } - return nil, fmt.Errorf("expected *array.Binary, got %T", a) + return nil, merr.WrapErrServiceInternalMsg("expected *array.Binary, got %T", a) }, serialize: func(b array.Builder, v any, _ schemapb.DataType) error { if v == nil { @@ -450,7 +450,7 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { builder.Append(bytes) return nil } else { - return fmt.Errorf("failed to marshal ScalarField: %w", err) + return merr.WrapErrSerializationFailed(err, "failed to marshal ScalarField") } } if vv, ok := v.(*schemapb.VectorField); ok { @@ -458,12 +458,12 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { builder.Append(bytes) return nil } else { - return fmt.Errorf("failed to marshal VectorField: %w", err) + return merr.WrapErrStorage(err, "failed to marshal VectorField") } } - return fmt.Errorf("expected []byte, *schemapb.ScalarField or *schemapb.VectorField value, got %T", v) + return merr.WrapErrServiceInternalMsg("expected []byte, *schemapb.ScalarField or *schemapb.VectorField value, got %T", v) } - return fmt.Errorf("expected *array.BinaryBuilder, got %T", b) + return merr.WrapErrServiceInternalMsg("expected *array.BinaryBuilder, got %T", b) }, } @@ -487,7 +487,7 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { vf, ok := v.(*schemapb.VectorField) if !ok { - return fmt.Errorf("expected *schemapb.VectorField, got %T", v) + return merr.WrapErrServiceInternalMsg("expected *schemapb.VectorField, got %T", v) } if vf == nil { b.AppendNull() @@ -496,7 +496,7 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { builder, ok := b.(*array.ListBuilder) if !ok { - return fmt.Errorf("expected *array.ListBuilder, got %T", b) + return merr.WrapErrServiceInternalMsg("expected *array.ListBuilder, got %T", b) } valueBuilder := builder.ValueBuilder().(*array.FixedSizeBinaryBuilder) @@ -519,7 +519,7 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { switch elementType { case schemapb.DataType_FloatVector: if vf.GetFloatVector() == nil { - return fmt.Errorf("FloatVector data is nil for elementType FloatVector") + return merr.WrapErrServiceInternalMsg("FloatVector data is nil for elementType FloatVector") } floatData := vf.GetFloatVector().GetData() floatsPerVector := bytesPerVector / 4 @@ -544,32 +544,32 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { case schemapb.DataType_BinaryVector: if vf.GetBinaryVector() == nil { - return fmt.Errorf("BinaryVector data is nil for elementType BinaryVector") + return merr.WrapErrServiceInternalMsg("BinaryVector data is nil for elementType BinaryVector") } return appendVectorChunks(vf.GetBinaryVector()) case schemapb.DataType_Float16Vector: if vf.GetFloat16Vector() == nil { - return fmt.Errorf("Float16Vector data is nil for elementType Float16Vector") + return merr.WrapErrServiceInternalMsg("Float16Vector data is nil for elementType Float16Vector") } return appendVectorChunks(vf.GetFloat16Vector()) case schemapb.DataType_BFloat16Vector: if vf.GetBfloat16Vector() == nil { - return fmt.Errorf("BFloat16Vector data is nil for elementType BFloat16Vector") + return merr.WrapErrServiceInternalMsg("BFloat16Vector data is nil for elementType BFloat16Vector") } return appendVectorChunks(vf.GetBfloat16Vector()) case schemapb.DataType_Int8Vector: if vf.GetInt8Vector() == nil { - return fmt.Errorf("Int8Vector data is nil for elementType Int8Vector") + return merr.WrapErrServiceInternalMsg("Int8Vector data is nil for elementType Int8Vector") } return appendVectorChunks(vf.GetInt8Vector()) case schemapb.DataType_SparseFloatVector: - return fmt.Errorf("SparseFloatVector in VectorArray not implemented yet") + return merr.WrapErrServiceInternalMsg("SparseFloatVector in VectorArray not implemented yet") default: - return fmt.Errorf("unsupported elementType for ArrayOfVector: %s", elementType.String()) + return merr.WrapErrServiceInternalMsg("unsupported elementType for ArrayOfVector: %s", elementType.String()) } }, } @@ -596,7 +596,7 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { } return value, nil } - return nil, fmt.Errorf("expected *array.FixedSizeBinary or *array.Binary, got %T", a) + return nil, merr.WrapErrServiceInternalMsg("expected *array.FixedSizeBinary or *array.Binary, got %T", a) } fixedSizeSerializer := func(b array.Builder, v any, _ schemapb.DataType) error { if v == nil { @@ -612,9 +612,9 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { builder.Append(v) return nil } - return fmt.Errorf("expected []byte value, got %T", v) + return merr.WrapErrServiceInternalMsg("expected []byte value, got %T", v) } - return fmt.Errorf("expected *array.FixedSizeBinaryBuilder, got %T", b) + return merr.WrapErrServiceInternalMsg("expected *array.FixedSizeBinaryBuilder, got %T", b) } m[schemapb.DataType_BinaryVector] = serdeEntry{ @@ -664,7 +664,7 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { } return int8s, nil } - return nil, fmt.Errorf("expected *array.FixedSizeBinary or *array.Binary, got %T", a) + return nil, merr.WrapErrServiceInternalMsg("expected *array.FixedSizeBinary or *array.Binary, got %T", a) }, serialize: func(b array.Builder, v any, _ schemapb.DataType) error { if v == nil { @@ -677,7 +677,7 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { } else if vv, ok := v.([]int8); ok { bytesData = arrow.Int8Traits.CastToBytes(vv) } else { - return fmt.Errorf("expected []byte or []int8 value, got %T", v) + return merr.WrapErrServiceInternalMsg("expected []byte or []int8 value, got %T", v) } if builder, ok := b.(*array.FixedSizeBinaryBuilder); ok { builder.Append(bytesData) @@ -687,7 +687,7 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { builder.Append(bytesData) return nil } - return fmt.Errorf("expected *array.FixedSizeBinaryBuilder, got %T", b) + return merr.WrapErrServiceInternalMsg("expected *array.FixedSizeBinaryBuilder, got %T", b) }, } m[schemapb.DataType_FloatVector] = serdeEntry{ @@ -718,7 +718,7 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { } return vector, nil } - return nil, fmt.Errorf("expected *array.FixedSizeBinary or *array.Binary, got %T", a) + return nil, merr.WrapErrServiceInternalMsg("expected *array.FixedSizeBinary or *array.Binary, got %T", a) }, serialize: func(b array.Builder, v any, _ schemapb.DataType) error { if v == nil { @@ -741,9 +741,9 @@ var serdeMap = func() map[schemapb.DataType]serdeEntry { builder.Append(bytesData) return nil } - return fmt.Errorf("expected *array.FixedSizeBinaryBuilder or *array.BinaryBuilder, got %T", b) + return merr.WrapErrServiceInternalMsg("expected *array.FixedSizeBinaryBuilder or *array.BinaryBuilder, got %T", b) } - return fmt.Errorf("expected *array.FixedSizeBinaryBuilder, got %T", b) + return merr.WrapErrServiceInternalMsg("expected *array.FixedSizeBinaryBuilder, got %T", b) }, } m[schemapb.DataType_SparseFloatVector] = byteEntry @@ -987,9 +987,9 @@ func createEmptyVectorField(elementType schemapb.DataType, dim int64) (*schemapb }, }, nil case schemapb.DataType_SparseFloatVector: - return nil, fmt.Errorf("SparseFloatVector in empty VectorArray not implemented yet") + return nil, merr.WrapErrServiceInternalMsg("SparseFloatVector in empty VectorArray not implemented yet") default: - return nil, fmt.Errorf("unsupported element type for empty ArrayOfVector: %s", elementType.String()) + return nil, merr.WrapErrServiceInternalMsg("unsupported element type for empty ArrayOfVector: %s", elementType.String()) } } @@ -1001,10 +1001,10 @@ func deserializeArrayOfVector(a arrow.Array, i int, elementType schemapb.DataTyp arr, ok := a.(*array.List) if !ok { - return nil, fmt.Errorf("expected *array.List for ArrayOfVector, got %T", a) + return nil, merr.WrapErrServiceInternalMsg("expected *array.List for ArrayOfVector, got %T", a) } if i >= arr.Len() { - return nil, fmt.Errorf("index %d out of bounds for array of length %d", i, arr.Len()) + return nil, merr.WrapErrServiceInternalMsg("index %d out of bounds for array of length %d", i, arr.Len()) } start, end := arr.ValueOffsets(i) @@ -1019,7 +1019,7 @@ func deserializeArrayOfVector(a arrow.Array, i int, elementType schemapb.DataTyp valuesArray := arr.ListValues() binaryArray, ok := valuesArray.(*array.FixedSizeBinary) if !ok { - return nil, fmt.Errorf("expected *array.FixedSizeBinary for ArrayOfVector values, got %T", valuesArray) + return nil, merr.WrapErrServiceInternalMsg("expected *array.FixedSizeBinary for ArrayOfVector values, got %T", valuesArray) } numVectors := int(totalElements) @@ -1088,9 +1088,9 @@ func deserializeArrayOfVector(a arrow.Array, i int, elementType schemapb.DataTyp }, }, nil case schemapb.DataType_SparseFloatVector: - return nil, fmt.Errorf("SparseFloatVector in VectorArray deserialization not implemented yet") + return nil, merr.WrapErrServiceInternalMsg("SparseFloatVector in VectorArray deserialization not implemented yet") default: - return nil, fmt.Errorf("unsupported element type for ArrayOfVector deserialization: %s", elementType.String()) + return nil, merr.WrapErrServiceInternalMsg("unsupported element type for ArrayOfVector deserialization: %s", elementType.String()) } } @@ -1337,7 +1337,7 @@ func BuildRecord(b *array.RecordBuilder, data *InsertData, schema *schemapb.Coll } if fieldData.RowNum() == 0 { - return merr.WrapErrServiceInternal(fmt.Sprintf("row num is 0 for field %s", field.Name)) + return merr.WrapErrServiceInternalMsg("row num is 0 for field %s", field.Name) } // Get element type for ArrayOfVector, otherwise use None @@ -1370,7 +1370,7 @@ func BuildRecord(b *array.RecordBuilder, data *InsertData, schema *schemapb.Coll rowData := fieldData.GetRow(j) err := typeEntry.serialize(fBuilder, rowData, elementType) if err != nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("serialize error on type %s: %v", field.DataType.String(), err)) + return merr.Wrapf(err, "serialize error on type %s", field.DataType.String()) } } } @@ -1378,7 +1378,7 @@ func BuildRecord(b *array.RecordBuilder, data *InsertData, schema *schemapb.Coll for j := 0; j < fieldData.RowNum(); j++ { err := typeEntry.serialize(fBuilder, fieldData.GetRow(j), elementType) if err != nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("serialize error on type %s: %v", field.DataType.String(), err)) + return merr.Wrapf(err, "serialize error on type %s", field.DataType.String()) } } } diff --git a/internal/storage/serde_delta.go b/internal/storage/serde_delta.go index 0d6ebd8c90..f8457a94cf 100644 --- a/internal/storage/serde_delta.go +++ b/internal/storage/serde_delta.go @@ -21,14 +21,12 @@ import ( "context" "encoding/binary" "encoding/json" - "fmt" "io" "strconv" "github.com/apache/arrow/go/v17/arrow" "github.com/apache/arrow/go/v17/arrow/array" "github.com/apache/arrow/go/v17/arrow/memory" - "github.com/cockroachdb/errors" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/pkg/v3/common" @@ -416,7 +414,7 @@ func newDeltalogMultiFieldWriter(eventWriter *MultiFieldDeltalogStreamWriter, ba pb.Append(pk) } default: - return nil, fmt.Errorf("unexpected pk type %v", v[0].PkType) + return nil, merr.WrapErrServiceInternalMsg("unexpected pk type %v", v[0].PkType) } for _, vv := range v { @@ -441,7 +439,7 @@ func newDeltalogMultiFieldReader(blobs []*Blob) (*DeserializeReaderImpl[*DeleteL return NewDeserializeReader(reader, func(r Record, v []*DeleteLog) error { rec, ok := r.(*simpleArrowRecord) if !ok { - return errors.New("can not cast to simple arrow record") + return merr.WrapErrServiceInternalMsg("can not cast to simple arrow record") } fields := rec.r.Schema().Fields() switch fields[0].Type.ID() { @@ -462,7 +460,7 @@ func newDeltalogMultiFieldReader(blobs []*Blob) (*DeserializeReaderImpl[*DeleteL v[j].Pk = NewVarCharPrimaryKey(arr.Value(j)) } default: - return fmt.Errorf("unexpected delta log pkType %v", fields[0].Type.Name()) + return merr.WrapErrServiceInternalMsg("unexpected delta log pkType %v", fields[0].Type.Name()) } arr := r.Column(1).(*array.Int64) @@ -561,7 +559,7 @@ func (w *LegacyDeltalogWriter) Write(rec Record) error { pk := NewVarCharPrimaryKey(rec.Column(0).(*array.String).Value(i)) return NewDeleteLog(pk, ts), nil default: - return nil, fmt.Errorf("unexpected pk type %v", w.pkType) + return nil, merr.WrapErrServiceInternalMsg("unexpected pk type %v", w.pkType) } } @@ -642,7 +640,7 @@ func (r *deleteLogToRecordReader) Next() (Record, error) { } pkArray = builder.NewArray() default: - return nil, fmt.Errorf("unsupported pk type: %v", r.pkType) + return nil, merr.WrapErrParameterInvalidMsg("unsupported pk type: %v", r.pkType) } tsBuilder := array.NewInt64Builder(allocator) diff --git a/internal/storage/serde_events.go b/internal/storage/serde_events.go index 48f7238cc6..186438419e 100644 --- a/internal/storage/serde_events.go +++ b/internal/storage/serde_events.go @@ -19,7 +19,6 @@ package storage import ( "bytes" "encoding/binary" - "fmt" "io" "math" "sort" @@ -206,7 +205,7 @@ func valueDeserializer(r Record, v []*Value, fields []*schemapb.FieldSchema, sho d, err := serdeMap[dt].deserialize(r.Column(j), i, elementType, dim, shouldCopy) if err != nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("deserialize error on type %s: %v", dt, err)) + return merr.WrapErrServiceInternalMsg("deserialize error on type %s: %v", dt, err) } m[j] = d // TODO: avoid memory copy here. } @@ -459,7 +458,7 @@ func ValueSerializer(v []*Value, schema *schemapb.CollectionSchema) (Record, err } if err := typeEntry.serialize(builders[fid], e, elementType); err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("serialize error on type %s: %v", types[fid], err)) + return nil, merr.WrapErrServiceInternalMsg("serialize error on type %s: %v", types[fid], err) } } } diff --git a/internal/storage/sort.go b/internal/storage/sort.go index e04b642c97..88205d99fa 100644 --- a/internal/storage/sort.go +++ b/internal/storage/sort.go @@ -114,7 +114,7 @@ func Sort(batchSize uint64, schema *schemapb.CollectionSchema, rr []RecordReader } comparators = append(comparators, f) default: - return 0, nil, merr.WrapErrParameterInvalidMsg("unsupported type for sorting key") + return 0, nil, merr.WrapErrStorageMsg("unsupported type for sorting key") } } @@ -281,7 +281,7 @@ func MergeSort(batchSize uint64, schema *schemapb.CollectionSchema, rr []RecordR return 0 }) default: - return 0, merr.WrapErrParameterInvalidMsg("unsupported type for sorting key") + return 0, merr.WrapErrStorageMsg("unsupported type for sorting key") } } diff --git a/internal/storage/stats.go b/internal/storage/stats.go index a22c765544..efe5a681ec 100644 --- a/internal/storage/stats.go +++ b/internal/storage/stats.go @@ -24,7 +24,6 @@ import ( "math" "path" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" @@ -102,7 +101,7 @@ func (stats *PrimaryKeyStats) UnmarshalJSON(data []byte) error { stats.MaxPk = &VarCharPrimaryKey{} stats.MinPk = &VarCharPrimaryKey{} default: - return errors.New("Invalid PK Data Type") + return merr.WrapErrServiceInternalMsg("Invalid PK Data Type") } if maxPkMessage, ok := messageMap["maxPk"]; ok && maxPkMessage != nil { @@ -283,10 +282,7 @@ func (sr *StatsReader) GetPrimaryKeyStats() (*PrimaryKeyStats, error) { stats := &PrimaryKeyStats{} err := json.Unmarshal(sr.buffer, &stats) if err != nil { - return nil, merr.WrapErrParameterInvalid( - "valid JSON", - string(sr.buffer), - err.Error()) + return nil, merr.WrapErrDataIntegrity(err, "PrimaryKeyStats unmarshal failed") } return stats, nil @@ -297,10 +293,7 @@ func (sr *StatsReader) GetPrimaryKeyStatsList() ([]*PrimaryKeyStats, error) { stats := []*PrimaryKeyStats{} err := json.Unmarshal(sr.buffer, &stats) if err != nil { - return nil, merr.WrapErrParameterInvalid( - "valid JSON", - string(sr.buffer), - err.Error()) + return nil, merr.WrapErrDataIntegrity(err, "PrimaryKeyStats list unmarshal failed") } return stats, nil diff --git a/internal/storage/stats_collector.go b/internal/storage/stats_collector.go index c1cbf734a6..cf4dbfd13f 100644 --- a/internal/storage/stats_collector.go +++ b/internal/storage/stats_collector.go @@ -20,13 +20,13 @@ import ( "strconv" "github.com/apache/arrow/go/v17/arrow/array" - "github.com/cockroachdb/errors" "github.com/samber/lo" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/allocator" "github.com/milvus-io/milvus/pkg/v3/proto/datapb" "github.com/milvus-io/milvus/pkg/v3/proto/etcdpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/metautil" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -202,7 +202,7 @@ func (c *Bm25StatsCollector) Collect(r Record) error { for fieldID, stats := range c.bm25Stats { field, ok := r.Column(fieldID).(*array.Binary) if !ok { - return errors.New("bm25 field value not found") + return merr.WrapErrServiceInternalMsg("bm25 field value not found") } for i := 0; i < rows; i++ { stats.AppendBytes(field.Value(i)) diff --git a/internal/storage/stats_test.go b/internal/storage/stats_test.go index 0229751e03..b63e2c7567 100644 --- a/internal/storage/stats_test.go +++ b/internal/storage/stats_test.go @@ -227,7 +227,7 @@ func TestDeserializeStatsFailed(t *testing.T) { } _, err := DeserializeStatsList(blob) - assert.ErrorIs(t, err, merr.ErrParameterInvalid) + assert.ErrorIs(t, err, merr.ErrDataIntegrity) } func TestDeserializeEmptyStats(t *testing.T) { diff --git a/internal/storage/utils.go b/internal/storage/utils.go index 7dfbc0e4c7..eb865c7633 100644 --- a/internal/storage/utils.go +++ b/internal/storage/utils.go @@ -29,7 +29,6 @@ import ( "strconv" "github.com/apache/arrow/go/v17/arrow" - "github.com/cockroachdb/errors" "github.com/samber/lo" "go.uber.org/zap" "google.golang.org/protobuf/proto" @@ -184,12 +183,12 @@ func TransferColumnBasedInsertDataToRowBased(data *InsertData) ( ) { if !checkTsField(data) { return nil, nil, nil, - errors.New("cannot get timestamps from insert data") + merr.WrapErrServiceInternalMsg("cannot get timestamps from insert data") } if !checkRowIDField(data) { return nil, nil, nil, - errors.New("cannot get row ids from insert data") + merr.WrapErrServiceInternalMsg("cannot get row ids from insert data") } tss := data.Data[common.TimeStampField].(*Int64FieldData) @@ -210,7 +209,7 @@ func TransferColumnBasedInsertDataToRowBased(data *InsertData) ( all = append(all, ls.datas...) if !checkNumRows(all...) { return nil, nil, nil, - errors.New("columns of insert data have different length") + merr.WrapErrServiceInternalMsg("columns of insert data have different length") } sortFieldDataList(ls) @@ -226,7 +225,7 @@ func TransferColumnBasedInsertDataToRowBased(data *InsertData) ( err := binary.Write(&buffer, common.Endian, d) if err != nil { return nil, nil, nil, - fmt.Errorf("failed to get binary row, err: %v", err) + merr.WrapErrServiceInternalMsg("failed to get binary row, err: %v", err) } } @@ -259,7 +258,7 @@ func GetDimFromParams(params []*commonpb.KeyValuePair) (int, error) { return dim, nil } } - return -1, errors.New("dim not found in params") + return -1, merr.WrapErrServiceInternalMsg("dim not found in params") } // ReadBinary read data in bytes and write it into receiver. @@ -420,7 +419,7 @@ func RowBasedInsertMsgToInsertData(msg *msgstream.InsertMsg, collSchema *schemap } if len(collSchema.StructArrayFields) > 0 { - return nil, errors.New("struct fields are not implemented in row based insert data") + return nil, merr.WrapErrServiceInternalMsg("struct fields are not implemented in row based insert data") } for _, field := range collSchema.Fields { @@ -482,7 +481,7 @@ func RowBasedInsertMsgToInsertData(msg *msgstream.InsertMsg, collSchema *schemap Dim: dim, } case schemapb.DataType_SparseFloatVector: - return nil, errors.New("Sparse Float Vector is not supported in row based data") + return nil, merr.WrapErrServiceInternalMsg("Sparse Float Vector is not supported in row based data") case schemapb.DataType_Int8Vector: dim, err := GetDimFromParams(field.TypeParams) @@ -1320,7 +1319,7 @@ func GetPkFromInsertData(collSchema *schemapb.CollectionSchema, data *InsertData pfData, ok := data.Data[pf.FieldID] if !ok { log.Warn("no primary field found in insert msg", zap.Int64("fieldID", pf.FieldID)) - return nil, errors.New("no primary field found in insert msg") + return nil, merr.WrapErrServiceInternalMsg("no primary field found in insert msg") } var realPfData FieldData @@ -1334,7 +1333,7 @@ func GetPkFromInsertData(collSchema *schemapb.CollectionSchema, data *InsertData } if !ok { log.Warn("primary field not in Int64 or VarChar format", zap.Int64("fieldID", pf.FieldID)) - return nil, errors.New("primary field not in Int64 or VarChar format") + return nil, merr.WrapErrServiceInternalMsg("primary field not in Int64 or VarChar format") } return realPfData, nil @@ -1343,16 +1342,16 @@ func GetPkFromInsertData(collSchema *schemapb.CollectionSchema, data *InsertData // GetTimestampFromInsertData returns the Int64FieldData for timestamp field. func GetTimestampFromInsertData(data *InsertData) (*Int64FieldData, error) { if data == nil { - return nil, errors.New("try to get timestamp from nil insert data") + return nil, merr.WrapErrServiceInternalMsg("try to get timestamp from nil insert data") } fieldData, ok := data.Data[common.TimeStampField] if !ok { - return nil, errors.New("no timestamp field in insert data") + return nil, merr.WrapErrServiceInternalMsg("no timestamp field in insert data") } ifd, ok := fieldData.(*Int64FieldData) if !ok { - return nil, errors.New("timestamp field is not Int64") + return nil, merr.WrapErrServiceInternalMsg("timestamp field is not Int64") } return ifd, nil @@ -1685,7 +1684,7 @@ func TransferInsertDataToInsertRecord(insertData *InsertData) (*segcorepb.Insert ValidData: rawData.ValidData, } default: - return insertRecord, errors.New("unsupported data type when transter storage.InsertData to internalpb.InsertRecord") + return insertRecord, merr.WrapErrServiceInternalMsg("unsupported data type when transter storage.InsertData to internalpb.InsertRecord") } insertRecord.FieldsData = append(insertRecord.FieldsData, fieldData) @@ -1820,7 +1819,7 @@ func fillMissingFields(schema *schemapb.CollectionSchema, insertData *InsertData // Create default field data if not found fieldData, err := NewFieldData(field.DataType, field, int(batchRows)) if err != nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("failed to create default field data for field %s: %v", field.Name, err)) + return merr.WrapErrServiceInternalMsg("failed to create default field data for field %s: %v", field.Name, err) } if field.GetDefaultValue() != nil { // Fill with default value @@ -1828,17 +1827,17 @@ func fillMissingFields(schema *schemapb.CollectionSchema, insertData *InsertData for j := 0; j < int(batchRows); j++ { if err := fieldData.AppendRow(defaultValue); err != nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("failed to append default value for field %s: %v", field.Name, err)) + return merr.WrapErrServiceInternalMsg("failed to append default value for field %s: %v", field.Name, err) } } } else if field.GetNullable() { // Fill with null values for j := 0; j < int(batchRows); j++ { if err := fieldData.AppendRow(nil); err != nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("failed to append null value for field %s: %v", field.Name, err)) + return merr.WrapErrServiceInternalMsg("failed to append null value for field %s: %v", field.Name, err) } } } else { - return merr.WrapErrServiceInternal(fmt.Sprintf("field %s is not nullable and has no default value", field.Name)) + return merr.WrapErrServiceInternalMsg("field %s is not nullable and has no default value", field.Name) } insertData.Data[field.GetFieldID()] = fieldData } @@ -1876,7 +1875,7 @@ func VectorArrayToArrowType(elementType schemapb.DataType, dim int) (arrow.DataT case schemapb.DataType_Int8Vector: return &arrow.FixedSizeBinaryType{ByteWidth: dim}, nil default: - return nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("unsupported element type in VectorArray: %s", elementType.String())) + return nil, merr.WrapErrParameterInvalidMsg("unsupported element type in VectorArray: %s", elementType.String()) } } diff --git a/internal/storagev2/filesystem_metrics.go b/internal/storagev2/filesystem_metrics.go index 5b56bd3d8f..4e9b61b803 100644 --- a/internal/storagev2/filesystem_metrics.go +++ b/internal/storagev2/filesystem_metrics.go @@ -27,7 +27,6 @@ package storagev2 import "C" import ( - "fmt" "strconv" "unsafe" @@ -36,6 +35,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/metrics" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -57,7 +57,7 @@ func getMetricsFromHandle(cFilesystem C.FileSystemHandle) (*FilesystemMetrics, e metricsResult := C.loon_filesystem_get_metrics(cFilesystem, &cMetrics) if err := HandleLoonFFIResult(metricsResult); err != nil { C.loon_filesystem_destroy(cFilesystem) - return nil, fmt.Errorf("failed to get filesystem metrics: %w", err) + return nil, merr.Wrap(err, "failed to get filesystem metrics") } fsMetrics := &FilesystemMetrics{ @@ -197,7 +197,7 @@ func makePropertiesFromConfig(storageConfig *indexpb.StorageConfig) (C.LoonPrope ) if err := HandleLoonFFIResult(result); err != nil { - return C.LoonProperties{}, fmt.Errorf("failed to create properties: %w", err) + return C.LoonProperties{}, merr.Wrap(err, "failed to create properties") } return props, nil @@ -207,7 +207,7 @@ func makePropertiesFromConfig(storageConfig *indexpb.StorageConfig) (C.LoonPrope // using full storage config properties for proper cache resolution. func GetFilesystemMetricsWithConfig(storageConfig *indexpb.StorageConfig) (*FilesystemMetrics, error) { if storageConfig == nil { - return nil, fmt.Errorf("storageConfig is required") + return nil, merr.WrapErrStorageMsg("storageConfig is required") } props, err := makePropertiesFromConfig(storageConfig) @@ -219,7 +219,7 @@ func GetFilesystemMetricsWithConfig(storageConfig *indexpb.StorageConfig) (*File var cFilesystem C.FileSystemHandle result := C.loon_filesystem_get(&props, nil, 0, &cFilesystem) if err := HandleLoonFFIResult(result); err != nil { - return nil, fmt.Errorf("failed to get cached filesystem: %w", err) + return nil, merr.Wrap(err, "failed to get cached filesystem") } return getMetricsFromHandle(cFilesystem) @@ -234,7 +234,7 @@ func HandleLoonFFIResult(ffiResult C.LoonFFIResult) error { if errMsg != nil { errStr = C.GoString(errMsg) } - return fmt.Errorf("loon FFI error: %s", errStr) + return merr.WrapErrStorageMsg("loon FFI error: %s", errStr) } return nil } diff --git a/internal/storagev2/packed/explore_ffi.go b/internal/storagev2/packed/explore_ffi.go index b08c586436..10779a088b 100644 --- a/internal/storagev2/packed/explore_ffi.go +++ b/internal/storagev2/packed/explore_ffi.go @@ -26,7 +26,6 @@ package packed import "C" import ( - "fmt" "net/url" "sort" "strings" @@ -36,6 +35,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // formatExtensions maps format names to their expected file extensions. @@ -107,16 +107,16 @@ func GetFileInfo( cProperties, err := MakePropertiesFromStorageConfig(storageConfig, nil) if err != nil { - return nil, fmt.Errorf("failed to create properties: %w", err) + return nil, merr.Wrap(err, "failed to create properties") } defer C.loon_properties_free(cProperties) if err := injectExternalSpecProperties(cProperties, extfs.CollectionID, extfs.Source, extfs.Spec); err != nil { - return nil, fmt.Errorf("inject extfs: %w", err) + return nil, merr.Wrap(err, "inject extfs") } normalizedFilePath, err := normalizeExternalPathForStorage(filePath, cProperties, extfs) if err != nil { - return nil, fmt.Errorf("normalize external file path: %w", err) + return nil, merr.WrapErrStorage(err, "normalize external file path") } cFilePath := C.CString(normalizedFilePath) defer C.free(unsafe.Pointer(cFilePath)) @@ -125,7 +125,7 @@ func GetFileInfo( result := C.loon_exttable_get_file_info(cFormat, cFilePath, cProperties, &numRows) if err := HandleLoonFFIResult(result); err != nil { - return nil, fmt.Errorf("loon_exttable_get_file_info failed: %w", err) + return nil, merr.WrapErrStorage(err, "loon_exttable_get_file_info failed") } return &FileInfo{ @@ -225,16 +225,16 @@ func ExploreFilesReturnManifestPath( cProperties, err := MakePropertiesFromStorageConfig(storageConfig, nil) if err != nil { - return nil, "", fmt.Errorf("failed to create properties: %w", err) + return nil, "", merr.Wrap(err, "failed to create properties") } defer C.loon_properties_free(cProperties) if err := injectExternalSpecProperties(cProperties, extfs.CollectionID, extfs.Source, extfs.Spec); err != nil { - return nil, "", fmt.Errorf("inject extfs: %w", err) + return nil, "", merr.Wrap(err, "inject extfs") } normalizedExploreDir, err := normalizeExternalPathForStorage(exploreDir, cProperties, extfs) if err != nil { - return nil, "", fmt.Errorf("normalize external explore path: %w", err) + return nil, "", merr.WrapErrStorage(err, "normalize external explore path") } cExploreDir := C.CString(normalizedExploreDir) defer C.free(unsafe.Pointer(cExploreDir)) @@ -253,10 +253,10 @@ func ExploreFilesReturnManifestPath( &numFiles, &outColumnGroupsPath, ) if err := HandleLoonFFIResult(result); err != nil { - return nil, "", fmt.Errorf("loon_exttable_explore failed: %w", err) + return nil, "", merr.WrapErrStorage(err, "loon_exttable_explore failed") } if outColumnGroupsPath == nil { - return nil, "", fmt.Errorf("loon_exttable_explore returned nil column groups path") + return nil, "", merr.WrapErrServiceInternalMsg("loon_exttable_explore returned nil column groups path") } manifestPath := C.GoString(outColumnGroupsPath) C.loon_free_cstr(outColumnGroupsPath) @@ -291,21 +291,21 @@ func ReadFileInfosFromManifestPath( cProperties, err := MakePropertiesFromStorageConfig(storageConfig, nil) if err != nil { - return nil, fmt.Errorf("failed to create properties: %w", err) + return nil, merr.Wrap(err, "failed to create properties") } defer C.loon_properties_free(cProperties) var manifest *C.LoonManifest result := C.loon_exttable_read_manifest(cManifestPath, cProperties, &manifest) if err := HandleLoonFFIResult(result); err != nil { - return nil, fmt.Errorf("loon_exttable_read_manifest failed: %w", err) + return nil, merr.WrapErrStorage(err, "loon_exttable_read_manifest failed") } defer C.loon_manifest_destroy(manifest) var fileInfos []FileInfo cgroups := &manifest.column_groups if cgroups.column_group_array == nil && cgroups.num_of_column_groups > 0 { - return nil, fmt.Errorf("column_group_array is nil but num_of_column_groups is %d", cgroups.num_of_column_groups) + return nil, merr.WrapErrServiceInternalMsg("column_group_array is nil but num_of_column_groups is %d", cgroups.num_of_column_groups) } cgArray := unsafe.Slice(cgroups.column_group_array, int(cgroups.num_of_column_groups)) @@ -317,7 +317,7 @@ func ReadFileInfosFromManifestPath( fileArray := unsafe.Slice(cg.files, int(cg.num_of_files)) for j := range fileArray { if fileArray[j].path == nil { - return nil, fmt.Errorf("file path is nil in column group %d, file %d", i, j) + return nil, merr.WrapErrServiceInternalMsg("file path is nil in column group %d, file %d", i, j) } fileInfos = append(fileInfos, FileInfo{ FilePath: C.GoString(fileArray[j].path), diff --git a/internal/storagev2/packed/ffi_common.go b/internal/storagev2/packed/ffi_common.go index 2f961d29d1..8997e71a1f 100644 --- a/internal/storagev2/packed/ffi_common.go +++ b/internal/storagev2/packed/ffi_common.go @@ -20,6 +20,7 @@ import ( _ "github.com/milvus-io/milvus/internal/util/cgo" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -81,7 +82,7 @@ func ExtfsPrefixForCollection(collectionID int64) string { // StorageConfig are mapped to corresponding key-value pairs in Properties. func MakePropertiesFromStorageConfig(storageConfig *indexpb.StorageConfig, extraKVs map[string]string) (*C.LoonProperties, error) { if storageConfig == nil { - return nil, fmt.Errorf("storageConfig is required") + return nil, merr.WrapErrStorageMsg("storageConfig is required") } // Prepare key-value pairs from StorageConfig @@ -236,7 +237,7 @@ func MakePropertiesFromStorageConfig(storageConfig *indexpb.StorageConfig, extra err := HandleLoonFFIResult(result) if err != nil { - return nil, err + return nil, merr.WrapErrStorage(err, "loon properties_create failed") } return properties, nil } @@ -268,7 +269,7 @@ func injectExternalSpecProperties(properties *C.LoonProperties, collectionID int externalSource, externalSpec string, ) error { if properties == nil { - return fmt.Errorf("injectExternalSpecProperties: properties is nil") + return merr.WrapErrStorageMsg("injectExternalSpecProperties: properties is nil") } if externalSource == "" { return nil @@ -282,7 +283,10 @@ func injectExternalSpecProperties(properties *C.LoonProperties, collectionID int } result := C.loon_properties_inject_external_spec( properties, C.int64_t(collectionID), cSource, cSpec) - return HandleLoonFFIResult(result) + if err := HandleLoonFFIResult(result); err != nil { + return merr.WrapErrStorage(err, "loon inject_external_spec failed") + } + return nil } func HandleLoonFFIResult(ffiResult C.LoonFFIResult) error { @@ -294,7 +298,7 @@ func HandleLoonFFIResult(ffiResult C.LoonFFIResult) error { errStr = C.GoString(errMsg) } - return errors.Wrapf(ErrLoonTransient, "FFI operation failed: %s", errStr) + return merr.Wrapf(ErrLoonTransient, "FFI operation failed: %s", errStr) } return nil } @@ -335,14 +339,14 @@ func CompareManifestPath(a, b string) (int, error) { bBase, bVer, bErr := UnmarshalManifestPath(b) if aErr != nil { - return 0, fmt.Errorf("failed to parse manifest path %q: %w", a, aErr) + return 0, merr.WrapErrStorage(aErr, "failed to parse manifest path %q", a) } if bErr != nil { - return 0, fmt.Errorf("failed to parse manifest path %q: %w", b, bErr) + return 0, merr.WrapErrStorage(bErr, "failed to parse manifest path %q", b) } if aBase != bBase { - return 0, fmt.Errorf("manifest paths have different base paths: %q vs %q", aBase, bBase) + return 0, merr.WrapErrServiceInternalMsg("manifest paths have different base paths: %q vs %q", aBase, bBase) } switch { @@ -375,7 +379,7 @@ func AddLobFilesToTransaction(basePath string, version int64, storageConfig *ind cProperties, err := MakePropertiesFromStorageConfig(storageConfig, nil) if err != nil { - return 0, fmt.Errorf("failed to make properties: %w", err) + return 0, merr.Wrap(err, "failed to make properties") } defer C.loon_properties_free(cProperties) @@ -386,7 +390,7 @@ func AddLobFilesToTransaction(basePath string, version int64, storageConfig *ind var cTransactionHandle C.LoonTransactionHandle result := C.loon_transaction_begin(cBasePath, cProperties, C.int64_t(version), C.int32_t(0) /* resolve_id */, C.uint32_t(1) /* retry_limit */, &cTransactionHandle) if err := HandleLoonFFIResult(result); err != nil { - return 0, fmt.Errorf("failed to begin transaction: %w", err) + return 0, merr.WrapErrStorage(err, "failed to begin transaction") } defer C.loon_transaction_destroy(cTransactionHandle) @@ -406,7 +410,7 @@ func AddLobFilesToTransaction(basePath string, version int64, storageConfig *ind C.free(unsafe.Pointer(cPath)) if err := HandleLoonFFIResult(result); err != nil { - return 0, fmt.Errorf("failed to add LOB file %s: %w", lobFile.Path, err) + return 0, merr.WrapErrStorage(err, "failed to add LOB file %s", lobFile.Path) } } @@ -414,7 +418,7 @@ func AddLobFilesToTransaction(basePath string, version int64, storageConfig *ind var committedVersion C.int64_t result = C.loon_transaction_commit(cTransactionHandle, &committedVersion) if err := HandleLoonFFIResult(result); err != nil { - return 0, fmt.Errorf("failed to commit transaction: %w", err) + return 0, merr.WrapErrStorage(err, "failed to commit transaction") } return int64(committedVersion), nil @@ -425,12 +429,12 @@ func AddLobFilesToTransaction(basePath string, version int64, storageConfig *ind func GetManifestLobFiles(manifestPath string, storageConfig *indexpb.StorageConfig) ([]LobFileInfo, error) { basePath, version, err := UnmarshalManifestPath(manifestPath) if err != nil { - return nil, fmt.Errorf("failed to unmarshal manifest path: %w", err) + return nil, merr.WrapErrStorage(err, "failed to unmarshal manifest path") } cProperties, err := MakePropertiesFromStorageConfig(storageConfig, nil) if err != nil { - return nil, fmt.Errorf("failed to make properties: %w", err) + return nil, merr.Wrap(err, "failed to make properties") } defer C.loon_properties_free(cProperties) @@ -441,7 +445,7 @@ func GetManifestLobFiles(manifestPath string, storageConfig *indexpb.StorageConf var cTransactionHandle C.LoonTransactionHandle result := C.loon_transaction_begin(cBasePath, cProperties, C.int64_t(version), C.int32_t(0) /* resolve_id */, C.uint32_t(1) /* retry_limit */, &cTransactionHandle) if err := HandleLoonFFIResult(result); err != nil { - return nil, fmt.Errorf("failed to begin transaction: %w", err) + return nil, merr.WrapErrStorage(err, "failed to begin transaction") } defer C.loon_transaction_destroy(cTransactionHandle) @@ -449,7 +453,7 @@ func GetManifestLobFiles(manifestPath string, storageConfig *indexpb.StorageConf var cManifest *C.LoonManifest result = C.loon_transaction_get_manifest(cTransactionHandle, &cManifest) if err := HandleLoonFFIResult(result); err != nil { - return nil, fmt.Errorf("failed to get manifest: %w", err) + return nil, merr.WrapErrStorage(err, "failed to get manifest") } defer C.loon_manifest_destroy(cManifest) diff --git a/internal/storagev2/packed/ffi_filesystem.go b/internal/storagev2/packed/ffi_filesystem.go index 58d80ac694..176793b631 100644 --- a/internal/storagev2/packed/ffi_filesystem.go +++ b/internal/storagev2/packed/ffi_filesystem.go @@ -23,11 +23,11 @@ package packed import "C" import ( - "fmt" "strings" "unsafe" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // WriteFile writes raw bytes to a file using milvus-storage filesystem FFI. @@ -39,7 +39,7 @@ func WriteFile( ) error { cProperties, err := MakePropertiesFromStorageConfig(storageConfig, nil) if err != nil { - return fmt.Errorf("failed to create properties: %w", err) + return merr.WrapErrStorage(err, "failed to create properties") } defer C.loon_properties_free(cProperties) @@ -51,7 +51,7 @@ func WriteFile( var fsHandle C.FileSystemHandle result := C.loon_filesystem_get(cProperties, cPath, pathLen, &fsHandle) if err := HandleLoonFFIResult(result); err != nil { - return fmt.Errorf("failed to get filesystem: %w", err) + return merr.WrapErrStorage(err, "failed to get filesystem") } defer C.loon_filesystem_destroy(fsHandle) @@ -64,7 +64,7 @@ func WriteFile( defer C.free(unsafe.Pointer(cDir)) result = C.loon_filesystem_create_dir(fsHandle, cDir, C.uint32_t(len(dir)), true) if err := HandleLoonFFIResult(result); err != nil { - return fmt.Errorf("failed to create parent directory %q: %w", dir, err) + return merr.WrapErrStorage(err, "failed to create parent directory %q", dir) } } } diff --git a/internal/storagev2/packed/ffi_sample.go b/internal/storagev2/packed/ffi_sample.go index 79cc0d375c..454800b90f 100644 --- a/internal/storagev2/packed/ffi_sample.go +++ b/internal/storagev2/packed/ffi_sample.go @@ -25,7 +25,6 @@ package packed import "C" import ( - "fmt" "runtime" "unsafe" @@ -33,6 +32,7 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // SampleExternalFieldSizes samples rows from an external segment via Take API @@ -52,19 +52,19 @@ func SampleExternalFieldSizes( storageConfig *indexpb.StorageConfig, ) (map[string]int64, error) { if storageConfig == nil { - return nil, fmt.Errorf("storageConfig is required for SampleExternalFieldSizes") + return nil, merr.WrapErrStorageMsg("storageConfig is required for SampleExternalFieldSizes") } if manifestPath == "" { - return nil, fmt.Errorf("manifest_path is empty for SampleExternalFieldSizes") + return nil, merr.WrapErrStorageMsg("manifest_path is empty for SampleExternalFieldSizes") } cProperties, err := MakePropertiesFromStorageConfig(storageConfig, nil) if err != nil { - return nil, fmt.Errorf("failed to create properties: %w", err) + return nil, merr.Wrap(err, "failed to create properties") } defer C.loon_properties_free(cProperties) if err := injectExternalSpecProperties(cProperties, collectionID, externalSource, externalSpec); err != nil { - return nil, fmt.Errorf("inject extfs: %w", err) + return nil, merr.Wrap(err, "inject extfs") } cManifestPath := C.CString(manifestPath) @@ -75,7 +75,7 @@ func SampleExternalFieldSizes( if schema != nil { schemaBytes, err = proto.Marshal(schema) if err != nil { - return nil, fmt.Errorf("failed to marshal collection schema: %w", err) + return nil, merr.WrapErrStorage(err, "failed to marshal collection schema") } if len(schemaBytes) > 0 { cSchema.proto_blob = unsafe.Pointer(&schemaBytes[0]) diff --git a/internal/storagev2/packed/ffi_stats.go b/internal/storagev2/packed/ffi_stats.go index 62dc326d67..c027d6d3a7 100644 --- a/internal/storagev2/packed/ffi_stats.go +++ b/internal/storagev2/packed/ffi_stats.go @@ -23,10 +23,10 @@ package packed import "C" import ( - "fmt" "unsafe" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // ManifestStat represents a stat entry from the manifest. @@ -104,7 +104,7 @@ func GetManifestStats( ) (map[string]ManifestStat, error) { cManifest, err := GetManifestHandle(manifestPath, storageConfig) if err != nil { - return nil, fmt.Errorf("failed to get manifest: %w", err) + return nil, merr.Wrap(err, "failed to get manifest") } defer C.loon_manifest_destroy(cManifest) diff --git a/internal/storagev2/packed/manifest_commit.go b/internal/storagev2/packed/manifest_commit.go index f2b4dbb22d..2c8771ecd7 100644 --- a/internal/storagev2/packed/manifest_commit.go +++ b/internal/storagev2/packed/manifest_commit.go @@ -19,10 +19,10 @@ package packed import "C" import ( - "fmt" "unsafe" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // WriterOutput is the data carrier returned by an FFI writer's Close. It @@ -96,7 +96,7 @@ func CommitManifestUpdates(basePath string, baseVersion int64, cProperties, err := MakePropertiesFromStorageConfig(storageConfig, nil) if err != nil { - return "", fmt.Errorf("commit manifest: %w", err) + return "", merr.Wrap(err, "commit manifest") } defer C.loon_properties_free(cProperties) @@ -115,7 +115,9 @@ func CommitManifestUpdates(basePath string, baseVersion int64, C.LOON_TRANSACTION_RESOLVE_OVERWRITE, getRetryLimit(), &handle) if err := HandleLoonFFIResult(res); err != nil { - return "", fmt.Errorf("commit manifest begin: %w", err) + // HandleLoonFFIResult returns a bare ErrLoonTransient chain; give it the + // storage wire code like transaction.go does, instead of leaking 65535. + return "", merr.WrapErrStorage(err, "commit manifest begin") } defer C.loon_transaction_destroy(handle) @@ -126,7 +128,7 @@ func CommitManifestUpdates(basePath string, baseVersion int64, var commitVersion C.int64_t res = C.loon_transaction_commit(handle, &commitVersion) if err := HandleLoonFFIResult(res); err != nil { - return "", fmt.Errorf("commit manifest commit: %w", err) + return "", merr.WrapErrStorage(err, "commit manifest commit") } return MarshalManifestPath(basePath, int64(commitVersion)), nil } @@ -145,13 +147,13 @@ func applyManifestUpdates(handle C.LoonTransactionHandle, updates *ManifestUpdat err := HandleLoonFFIResult(C.loon_transaction_add_delta_log(handle, cPath, C.int64_t(entry.NumEntries))) C.free(unsafe.Pointer(cPath)) if err != nil { - return fmt.Errorf("commit manifest add_delta_log: %w", err) + return merr.WrapErrStorage(err, "commit manifest add_delta_log") } } for _, entry := range updates.Stats { if err := UpdateTransactionStat(handle, entry.Key, entry.Files, entry.Metadata); err != nil { - return fmt.Errorf("commit manifest update_stat: %w", err) + return merr.WrapErrStorage(err, "commit manifest update_stat") } } return nil diff --git a/internal/storagev2/packed/manifest_ffi.go b/internal/storagev2/packed/manifest_ffi.go index 328788907b..dc70707624 100644 --- a/internal/storagev2/packed/manifest_ffi.go +++ b/internal/storagev2/packed/manifest_ffi.go @@ -36,6 +36,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // Fragment represents a data fragment from an external data source. @@ -110,7 +111,7 @@ func columnsToAppend(existing []manifestColumnGroup, requested []string, fragmen if existingSets, ok := existingFragments[column]; ok { for _, existingSet := range existingSets { if !sameFragmentSet(existingSet, fragments) { - return nil, fmt.Errorf("column %s already exists with different fragments", column) + return nil, merr.WrapErrServiceInternalMsg("column %s already exists with different fragments", column) } } continue @@ -136,20 +137,20 @@ func CreateManifestForSegment( storageConfig *indexpb.StorageConfig, ) (string, error) { if len(fragments) == 0 { - return "", fmt.Errorf("fragments cannot be empty") + return "", merr.WrapErrServiceInternalMsg("fragments cannot be empty") } // Create column groups from fragments columnGroups, err := createColumnGroups(columns, format, fragments) if err != nil { - return "", fmt.Errorf("failed to create column groups: %w", err) + return "", merr.Wrap(err, "failed to create column groups") } defer C.loon_column_groups_destroy(columnGroups) // Create properties from storage config cProperties, err := MakePropertiesFromStorageConfig(storageConfig, nil) if err != nil { - return "", fmt.Errorf("failed to create properties: %w", err) + return "", merr.Wrap(err, "failed to create properties") } defer C.loon_properties_free(cProperties) @@ -161,21 +162,21 @@ func CreateManifestForSegment( var transactionHandle C.LoonTransactionHandle result := C.loon_transaction_begin(cBasePath, cProperties, C.int64_t(0), C.LOON_TRANSACTION_RESOLVE_OVERWRITE /* resolve_id */, getRetryLimit() /* retry_limit */, &transactionHandle) if err := HandleLoonFFIResult(result); err != nil { - return "", fmt.Errorf("loon_transaction_begin failed: %w", err) + return "", merr.WrapErrStorage(err, "loon_transaction_begin failed") } defer C.loon_transaction_destroy(transactionHandle) // Append files to transaction result = C.loon_transaction_append_files(transactionHandle, columnGroups) if err := HandleLoonFFIResult(result); err != nil { - return "", fmt.Errorf("loon_transaction_append_files failed: %w", err) + return "", merr.WrapErrStorage(err, "loon_transaction_append_files failed") } // Commit transaction var committedVersion C.int64_t result = C.loon_transaction_commit(transactionHandle, &committedVersion) if err := HandleLoonFFIResult(result); err != nil { - return "", fmt.Errorf("loon_transaction_commit failed: %w", err) + return "", merr.WrapErrStorage(err, "loon_transaction_commit failed") } // Return manifest path using the helper function @@ -249,7 +250,7 @@ func createColumnGroups( ) if err := HandleLoonFFIResult(result); err != nil { - return nil, fmt.Errorf("loon_column_groups_create failed: %w", err) + return nil, merr.WrapErrStorage(err, "loon_column_groups_create failed") } return outColumnGroups, nil @@ -265,7 +266,7 @@ func GetManifestFieldIDs(manifestPath string, storageConfig *indexpb.StorageConf fields := make(map[int64]struct{}) cgroups := &manifest.column_groups if cgroups.column_group_array == nil && cgroups.num_of_column_groups > 0 { - return nil, fmt.Errorf("column_group_array is nil but num_of_column_groups is %d", cgroups.num_of_column_groups) + return nil, merr.WrapErrServiceInternalMsg("column_group_array is nil but num_of_column_groups is %d", cgroups.num_of_column_groups) } cgArray := unsafe.Slice(cgroups.column_group_array, int(cgroups.num_of_column_groups)) @@ -282,7 +283,7 @@ func GetManifestFieldIDs(manifestPath string, storageConfig *indexpb.StorageConf columnName := C.GoString(column) fieldID, err := strconv.ParseInt(columnName, 10, 64) if err != nil { - return nil, fmt.Errorf("invalid manifest column name %q: %w", columnName, err) + return nil, merr.WrapErrStorage(err, "invalid manifest column name %q", columnName) } fields[fieldID] = struct{}{} } @@ -395,7 +396,7 @@ func ResolveManifestSingleWriterFormat( return fallbackFormat, nil } if len(formats) > 1 { - return "", fmt.Errorf("mixed writer formats: single writer columns %v overlap mixed formats in manifest %s: %s", + return "", merr.WrapErrDataIntegrityMsg("mixed writer formats: single writer columns %v overlap mixed formats in manifest %s: %s", columns, manifestPath, formatSetString(formats)) } for format := range formats { @@ -419,14 +420,14 @@ func readColumnGroupsFromManifest( ) ([]manifestColumnGroup, error) { basePath, version, err := UnmarshalManifestPath(manifestPath) if err != nil { - return nil, fmt.Errorf("failed to parse manifest path: %w", err) + return nil, merr.Wrap(err, "failed to parse manifest path") } manifestFilePath := fmt.Sprintf("%s/_metadata/manifest-%d.avro", basePath, version) cProperties, err := MakePropertiesFromStorageConfig(storageConfig, nil) if err != nil { - return nil, fmt.Errorf("failed to create properties: %w", err) + return nil, merr.Wrap(err, "failed to create properties") } defer C.loon_properties_free(cProperties) @@ -436,16 +437,16 @@ func readColumnGroupsFromManifest( var manifest *C.LoonManifest result := C.loon_exttable_read_manifest(cManifestFilePath, cProperties, &manifest) if err := HandleLoonFFIResult(result); err != nil { - return nil, fmt.Errorf("loon_exttable_read_manifest failed: %w", err) + return nil, merr.Wrap(err, "loon_exttable_read_manifest failed") } if manifest == nil { - return nil, fmt.Errorf("loon_exttable_read_manifest returned nil manifest") + return nil, merr.WrapErrServiceInternalMsg("loon_exttable_read_manifest returned nil manifest") } defer C.loon_manifest_destroy(manifest) cgroups := &manifest.column_groups if cgroups.column_group_array == nil && cgroups.num_of_column_groups > 0 { - return nil, fmt.Errorf("column_group_array is nil but num_of_column_groups is %d", cgroups.num_of_column_groups) + return nil, merr.WrapErrServiceInternalMsg("column_group_array is nil but num_of_column_groups is %d", cgroups.num_of_column_groups) } if cgroups.column_group_array == nil { return nil, nil @@ -458,7 +459,7 @@ func readColumnGroupsFromManifest( group := manifestColumnGroup{} if cg.columns == nil && cg.num_of_columns > 0 { - return nil, fmt.Errorf("columns array is nil but num_of_columns is %d in column group %d", cg.num_of_columns, i) + return nil, merr.WrapErrServiceInternalMsg("columns array is nil but num_of_columns is %d in column group %d", cg.num_of_columns, i) } if cg.columns != nil { columnArray := unsafe.Slice(cg.columns, int(cg.num_of_columns)) @@ -475,15 +476,15 @@ func readColumnGroupsFromManifest( } if cg.files == nil && cg.num_of_files > 0 { - return nil, fmt.Errorf("files array is nil but num_of_files is %d in column group %d", cg.num_of_files, i) + return nil, merr.WrapErrServiceInternalMsg("files array is nil but num_of_files is %d in column group %d", cg.num_of_files, i) } if cg.num_of_files > 0 { if cg.format == nil { - return nil, fmt.Errorf("manifest column group %d has files but nil format", i) + return nil, merr.WrapErrDataIntegrityMsg("manifest column group %d has files but nil format", i) } group.Format = C.GoString(cg.format) if group.Format == "" { - return nil, fmt.Errorf("manifest column group %d has files but empty format", i) + return nil, merr.WrapErrDataIntegrityMsg("manifest column group %d has files but empty format", i) } } if cg.files != nil { @@ -534,12 +535,12 @@ func AppendSegmentManifestColumns( return oldManifestPath, nil } if len(fragments) == 0 { - return "", fmt.Errorf("fragments cannot be empty") + return "", merr.WrapErrServiceInternalMsg("fragments cannot be empty") } existingGroups, err := readColumnGroupsFromManifest(oldManifestPath, storageConfig) if err != nil { - return "", fmt.Errorf("failed to read manifest column groups: %w", err) + return "", merr.Wrap(err, "failed to read manifest column groups") } columns, err = columnsToAppend(existingGroups, columns, fragments) @@ -552,15 +553,15 @@ func AppendSegmentManifestColumns( basePath, version, err := UnmarshalManifestPath(oldManifestPath) if err != nil { - return "", fmt.Errorf("failed to parse manifest path: %w", err) + return "", merr.Wrap(err, "failed to parse manifest path") } columnGroups, err := createColumnGroups(columns, format, fragments) if err != nil { - return "", fmt.Errorf("failed to create column groups: %w", err) + return "", merr.Wrap(err, "failed to create column groups") } if columnGroups == nil { - return "", fmt.Errorf("loon_column_groups_create returned nil column groups") + return "", merr.WrapErrServiceInternalMsg("loon_column_groups_create returned nil column groups") } newFiles := &ColumnGroups{ @@ -570,7 +571,7 @@ func AppendSegmentManifestColumns( defer newFiles.Destroy() if columnGroups.column_group_array == nil && columnGroups.num_of_column_groups > 0 { - return "", fmt.Errorf("column_group_array is nil but num_of_column_groups is %d", columnGroups.num_of_column_groups) + return "", merr.WrapErrServiceInternalMsg("column_group_array is nil but num_of_column_groups is %d", columnGroups.num_of_column_groups) } return CommitManifestUpdates(basePath, version, storageConfig, &ManifestUpdates{ diff --git a/internal/storagev2/packed/packed_reader.go b/internal/storagev2/packed/packed_reader.go index 05170e7176..044940f931 100644 --- a/internal/storagev2/packed/packed_reader.go +++ b/internal/storagev2/packed/packed_reader.go @@ -25,7 +25,6 @@ package packed import "C" import ( - "fmt" "io" "unsafe" @@ -34,6 +33,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/indexcgopb" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) func NewPackedReader(filePaths []string, schema *arrow.Schema, bufferSize int64, storageConfig *indexpb.StorageConfig, storagePluginContext *indexcgopb.StoragePluginContext) (*PackedReader, error) { @@ -143,7 +143,7 @@ func (pr *PackedReader) ReadNext() (arrow.Record, error) { }() recordBatch, err := cdata.ImportCRecordBatch(goCArr, goCSchema) if err != nil { - return nil, fmt.Errorf("failed to convert ArrowArray to Record: %w", err) + return nil, merr.WrapErrStorage(err, "failed to convert ArrowArray to Record") } pr.currentBatch = recordBatch diff --git a/internal/storagev2/packed/packed_reader_ffi.go b/internal/storagev2/packed/packed_reader_ffi.go index 59e1c80a0a..75cdfa5968 100644 --- a/internal/storagev2/packed/packed_reader_ffi.go +++ b/internal/storagev2/packed/packed_reader_ffi.go @@ -25,18 +25,17 @@ package packed import "C" import ( - "fmt" "io" "unsafe" "github.com/apache/arrow/go/v17/arrow" "github.com/apache/arrow/go/v17/arrow/cdata" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/indexcgopb" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // ExternalReaderContext carries per-collection context needed by the FFI @@ -54,7 +53,7 @@ func NewFFIPackedReader(manifestPath string, schema *arrow.Schema, neededColumns externalSpec := ext.Spec cLoonManifest, err := GetManifestHandle(manifestPath, storageConfig) if err != nil { - return nil, errors.Wrap(err, "failed to get manifest") + return nil, merr.Wrap(err, "failed to get manifest") } defer C.loon_manifest_destroy(cLoonManifest) @@ -139,7 +138,7 @@ func NewFFIPackedReader(manifestPath string, schema *arrow.Schema, neededColumns status = C.NewPackedFFIReaderWithManifest(cLoonManifest, cSchema, cNeededColumnArray, cNumColumns, &cPackedReader, cStorageConfig, pluginContextPtr, C.int64_t(collectionID), cExternalSource, cExternalSpec) } else { - return nil, fmt.Errorf("storageConfig is required") + return nil, merr.WrapErrServiceInternalMsg("storageConfig is required") } if err := ConsumeCStatusIntoError(&status); err != nil { return nil, err @@ -150,14 +149,14 @@ func NewFFIPackedReader(manifestPath string, schema *arrow.Schema, neededColumns status = C.GetFFIReaderStream(cPackedReader, C.int64_t(8196), (*C.struct_ArrowArrayStream)(unsafe.Pointer(&cStream))) if err := ConsumeCStatusIntoError(&status); err != nil { C.CloseFFIReader(cPackedReader) - return nil, fmt.Errorf("failed to get reader stream: %w", err) + return nil, merr.WrapErrStorage(err, "failed to get reader stream") } // Import the stream as a RecordReader recordReader, err := cdata.ImportCRecordReader(&cStream, schema) if err != nil { C.CloseFFIReader(cPackedReader) - return nil, fmt.Errorf("failed to import record reader: %w", err) + return nil, merr.WrapErrStorage(err, "failed to import record reader") } return &FFIPackedReader{ @@ -178,7 +177,7 @@ func (r *FFIPackedReader) ReadNext() (rec arrow.Record, err error) { if err == io.EOF { return nil, io.EOF } - return nil, fmt.Errorf("failed to read next record: %w", err) + return nil, merr.WrapErrStorage(err, "failed to read next record") } return rec, nil diff --git a/internal/storagev2/packed/packed_writer.go b/internal/storagev2/packed/packed_writer.go index b3fa10cb32..367ef43bc0 100644 --- a/internal/storagev2/packed/packed_writer.go +++ b/internal/storagev2/packed/packed_writer.go @@ -30,11 +30,11 @@ import ( "github.com/apache/arrow/go/v17/arrow" "github.com/apache/arrow/go/v17/arrow/cdata" - "github.com/cockroachdb/errors" "github.com/milvus-io/milvus/internal/storagecommon" "github.com/milvus-io/milvus/pkg/v3/proto/indexcgopb" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) func NewPackedWriter(filePaths []string, schema *arrow.Schema, bufferSize int64, multiPartUploadSize int64, columnGroups []storagecommon.ColumnGroup, storageConfig *indexpb.StorageConfig, storagePluginContext *indexcgopb.StoragePluginContext) (*PackedWriter, error) { @@ -59,7 +59,7 @@ func NewPackedWriter(filePaths []string, schema *arrow.Schema, bufferSize int64, for _, group := range columnGroups { cGroup := C.malloc(C.size_t(len(group.Columns)) * C.size_t(unsafe.Sizeof(C.int(0)))) if cGroup == nil { - return nil, errors.New("failed to allocate memory for column groups") + return nil, merr.WrapErrServiceInternalMsg("failed to allocate memory for column groups") } cGroupSlice := (*[1 << 30]C.int)(cGroup)[:len(group.Columns):len(group.Columns)] for i, val := range group.Columns { @@ -135,7 +135,7 @@ func (pw *PackedWriter) WriteRecordBatch(recordBatch arrow.Record) error { return nil } if pw.cPackedWriter == nil { - return errors.New("packed writer is closed") + return merr.WrapErrStorageMsg("packed writer is closed") } cArrays := make([]CArrowArray, recordBatch.NumCols()) @@ -176,13 +176,13 @@ func (pw *PackedWriter) Close() error { func (pw *PackedWriter) CloseAndTell(numGroups int) ([]int64, error) { if numGroups <= 0 { - return nil, errors.New("numGroups must be greater than 0") + return nil, merr.WrapErrServiceInternalMsg("numGroups must be greater than 0") } if pw.cPackedWriter == nil { if len(pw.closedSizes) == numGroups { return append([]int64(nil), pw.closedSizes...), nil } - return nil, errors.New("packed writer is closed") + return nil, merr.WrapErrStorageMsg("packed writer is closed") } sizes := make([]int64, numGroups) diff --git a/internal/storagev2/packed/packed_writer_ffi.go b/internal/storagev2/packed/packed_writer_ffi.go index 5e5b7aadcc..140ba6e03b 100644 --- a/internal/storagev2/packed/packed_writer_ffi.go +++ b/internal/storagev2/packed/packed_writer_ffi.go @@ -28,7 +28,6 @@ package packed import "C" import ( - "fmt" "strings" "unsafe" @@ -39,6 +38,7 @@ import ( "github.com/milvus-io/milvus/internal/storagecommon" "github.com/milvus-io/milvus/pkg/v3/proto/indexcgopb" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -164,7 +164,7 @@ func NewFFIPackedWriter(basePath string, schema *arrow.Schema, columnGroups []st func SchemaBasedPattern(schema *arrow.Schema, columnGroups []storagecommon.ColumnGroup) (string, error) { if schema == nil { - return "", fmt.Errorf("arrow schema is required") + return "", merr.WrapErrParameterInvalidMsg("arrow schema is required") } return strings.Join(lo.Map(columnGroups, func(columnGroup storagecommon.ColumnGroup, _ int) string { return strings.Join(lo.Map(columnGroup.Columns, func(index int, _ int) string { @@ -248,13 +248,13 @@ func (f *ColumnGroups) applyTo(handle C.LoonTransactionHandle) error { slice := unsafe.Slice(f.cColumnGroups.column_group_array, num) for i := range slice { if err := HandleLoonFFIResult(C.loon_transaction_add_column_group(handle, &slice[i])); err != nil { - return fmt.Errorf("commit manifest add_column_group: %w", err) + return merr.Wrap(err, "commit manifest add_column_group") } } return nil } if err := HandleLoonFFIResult(C.loon_transaction_append_files(handle, f.cColumnGroups)); err != nil { - return fmt.Errorf("commit manifest append_files: %w", err) + return merr.Wrap(err, "commit manifest append_files") } return nil } @@ -270,7 +270,7 @@ func (f *ColumnGroups) applyTo(handle C.LoonTransactionHandle) error { // Close or Write calls fail. func (pw *FFIPackedWriter) Close() (WriterOutput, error) { if pw.closed { - return nil, fmt.Errorf("FFIPackedWriter already closed") + return nil, merr.WrapErrServiceInternalMsg("FFIPackedWriter already closed") } pw.closed = true defer func() { diff --git a/internal/storagev2/packed/segment_writer_ffi.go b/internal/storagev2/packed/segment_writer_ffi.go index ac935376c1..6074961425 100644 --- a/internal/storagev2/packed/segment_writer_ffi.go +++ b/internal/storagev2/packed/segment_writer_ffi.go @@ -25,7 +25,6 @@ package packed import "C" import ( - "fmt" "strings" "unsafe" @@ -35,6 +34,7 @@ import ( "github.com/milvus-io/milvus/internal/storagecommon" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // TextColumnConfig represents configuration for a TEXT column. @@ -80,7 +80,7 @@ func NewFFISegmentWriter( defer cdata.ReleaseCArrowSchema(&cas) if storageConfig == nil { - return nil, fmt.Errorf("storageConfig must not be nil") + return nil, merr.WrapErrStorageMsg("storageConfig must not be nil") } extra := segmentWriterProperties(schema, config) @@ -202,14 +202,14 @@ func (f *SegmentOutput) applyTo(handle C.LoonTransactionHandle) error { } if f.cOutput.column_groups != nil { if err := HandleLoonFFIResult(C.loon_transaction_append_files(handle, f.cOutput.column_groups)); err != nil { - return fmt.Errorf("commit manifest append_files (segment): %w", err) + return merr.Wrap(err, "commit manifest append_files (segment)") } } if f.cOutput.num_lob_files > 0 && f.cOutput.lob_files != nil { lob := unsafe.Slice(f.cOutput.lob_files, f.cOutput.num_lob_files) for i := range lob { if err := HandleLoonFFIResult(C.loon_transaction_add_lob_file(handle, &lob[i])); err != nil { - return fmt.Errorf("commit manifest add_lob_file: %w", err) + return merr.Wrap(err, "commit manifest add_lob_file") } } } @@ -227,7 +227,7 @@ func (f *SegmentOutput) applyTo(handle C.LoonTransactionHandle) error { // further Close or Write calls fail. func (w *FFISegmentWriter) Close() (WriterOutput, error) { if w.closed { - return nil, fmt.Errorf("FFISegmentWriter already closed") + return nil, merr.WrapErrServiceInternal("FFISegmentWriter already closed") } w.closed = true defer func() { diff --git a/internal/storagev2/packed/stats_resolver.go b/internal/storagev2/packed/stats_resolver.go index f54a6bfa66..5a43d97bed 100644 --- a/internal/storagev2/packed/stats_resolver.go +++ b/internal/storagev2/packed/stats_resolver.go @@ -23,6 +23,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/datapb" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" "github.com/milvus-io/milvus/pkg/v3/proto/querypb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // compoundStatsLogIdx is the log index that identifies compound stats format. @@ -364,7 +365,7 @@ func (r *StatsResolver) loadManifest() error { stats, err := GetManifestStats(r.manifestPath, r.storageConfig) if err != nil { - r.manifestErr = fmt.Errorf("failed to get manifest stats: %w", err) + r.manifestErr = merr.Wrap(err, "failed to get manifest stats") return r.manifestErr } r.manifestStats = stats diff --git a/internal/storagev2/packed/transaction.go b/internal/storagev2/packed/transaction.go index f939e18fd6..a28a2ed6a0 100644 --- a/internal/storagev2/packed/transaction.go +++ b/internal/storagev2/packed/transaction.go @@ -23,7 +23,6 @@ package packed import "C" import ( - "fmt" "math" "unsafe" @@ -31,6 +30,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -91,7 +91,7 @@ func addDeltaLogsToManifest( basePath, version, err := UnmarshalManifestPath(manifestPath) if err != nil { - return "", fmt.Errorf("failed to parse manifest path: %w", err) + return "", merr.WrapErrStorage(err, "failed to parse manifest path") } log.Debug("AddDeltaLogsToManifest", @@ -101,7 +101,7 @@ func addDeltaLogsToManifest( cProperties, err := MakePropertiesFromStorageConfig(storageConfig, nil) if err != nil { - return "", fmt.Errorf("failed to create properties: %w", err) + return "", merr.Wrap(err, "failed to create properties") } defer C.loon_properties_free(cProperties) @@ -112,7 +112,7 @@ func addDeltaLogsToManifest( var transactionHandle C.LoonTransactionHandle result := C.loon_transaction_begin(cBasePath, cProperties, C.int64_t(version), resolveID /* resolve_id */, getRetryLimit() /* retry_limit */, &transactionHandle) if err := HandleLoonFFIResult(result); err != nil { - return "", fmt.Errorf("failed to begin transaction: %w", err) + return "", merr.WrapErrStorage(err, "failed to begin transaction") } defer C.loon_transaction_destroy(transactionHandle) @@ -124,7 +124,7 @@ func addDeltaLogsToManifest( C.free(unsafe.Pointer(cPath)) if err := HandleLoonFFIResult(result); err != nil { - return "", fmt.Errorf("failed to add delta log %s: %w", deltaLog.Path, err) + return "", merr.WrapErrStorage(err, "failed to add delta log %s", deltaLog.Path) } log.Debug("Added delta log to transaction", @@ -136,7 +136,7 @@ func addDeltaLogsToManifest( var commitVersion C.int64_t result = C.loon_transaction_commit(transactionHandle, &commitVersion) if err := HandleLoonFFIResult(result); err != nil { - return "", fmt.Errorf("failed to commit transaction: %w", err) + return "", merr.WrapErrStorage(err, "failed to commit transaction") } newManifestPath := MarshalManifestPath(basePath, int64(commitVersion)) @@ -154,12 +154,12 @@ func GetDeltaLogPathsFromManifest( ) ([]string, error) { basePath, version, err := UnmarshalManifestPath(manifestPath) if err != nil { - return nil, fmt.Errorf("failed to parse manifest path: %w", err) + return nil, merr.WrapErrStorage(err, "failed to parse manifest path") } cProperties, err := MakePropertiesFromStorageConfig(storageConfig, nil) if err != nil { - return nil, fmt.Errorf("failed to create properties: %w", err) + return nil, merr.Wrap(err, "failed to create properties") } defer C.loon_properties_free(cProperties) @@ -169,14 +169,14 @@ func GetDeltaLogPathsFromManifest( var cTransactionHandle C.LoonTransactionHandle result := C.loon_transaction_begin(cBasePath, cProperties, C.int64_t(version), C.int32_t(0) /* resolve_id */, C.uint32_t(1) /* retry_limit */, &cTransactionHandle) if err := HandleLoonFFIResult(result); err != nil { - return nil, fmt.Errorf("failed to begin transaction: %w", err) + return nil, merr.WrapErrStorage(err, "failed to begin transaction") } defer C.loon_transaction_destroy(cTransactionHandle) var cManifest *C.LoonManifest result = C.loon_transaction_get_manifest(cTransactionHandle, &cManifest) if err := HandleLoonFFIResult(result); err != nil { - return nil, fmt.Errorf("failed to get manifest: %w", err) + return nil, merr.WrapErrStorage(err, "failed to get manifest") } defer C.loon_manifest_destroy(cManifest) @@ -221,7 +221,7 @@ func AddStatsToManifest( basePath, version, err := UnmarshalManifestPath(manifestPath) if err != nil { - return "", fmt.Errorf("failed to parse manifest path: %w", err) + return "", merr.WrapErrStorage(err, "failed to parse manifest path") } log.Debug("AddStatsToManifest", @@ -231,7 +231,7 @@ func AddStatsToManifest( cProperties, err := MakePropertiesFromStorageConfig(storageConfig, nil) if err != nil { - return "", fmt.Errorf("failed to create properties: %w", err) + return "", merr.Wrap(err, "failed to create properties") } defer C.loon_properties_free(cProperties) @@ -241,14 +241,14 @@ func AddStatsToManifest( var transactionHandle C.LoonTransactionHandle result := C.loon_transaction_begin(cBasePath, cProperties, C.int64_t(version), C.LOON_TRANSACTION_RESOLVE_OVERWRITE /* resolve_id */, getRetryLimit() /* retry_limit */, &transactionHandle) if err := HandleLoonFFIResult(result); err != nil { - return "", fmt.Errorf("failed to begin transaction: %w", err) + return "", merr.WrapErrStorage(err, "failed to begin transaction") } defer C.loon_transaction_destroy(transactionHandle) // The C++ loon library converts absolute paths to relative at commit time for _, stat := range stats { if err := UpdateTransactionStat(transactionHandle, stat.Key, stat.Files, stat.Metadata); err != nil { - return "", fmt.Errorf("failed to update stat %s: %w", stat.Key, err) + return "", merr.WrapErrStorage(err, "failed to update stat %s", stat.Key) } log.Debug("Added stat to transaction", zap.String("key", stat.Key), @@ -258,7 +258,7 @@ func AddStatsToManifest( var commitVersion C.int64_t result = C.loon_transaction_commit(transactionHandle, &commitVersion) if err := HandleLoonFFIResult(result); err != nil { - return "", fmt.Errorf("failed to commit transaction: %w", err) + return "", merr.WrapErrStorage(err, "failed to commit transaction") } newManifestPath := MarshalManifestPath(basePath, int64(commitVersion)) diff --git a/internal/storagev2/packed/utils.go b/internal/storagev2/packed/utils.go index 638132f63c..0cc1d33f33 100644 --- a/internal/storagev2/packed/utils.go +++ b/internal/storagev2/packed/utils.go @@ -16,7 +16,6 @@ package packed import ( "context" - "fmt" "time" "go.uber.org/zap" @@ -26,6 +25,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/datapb" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" "github.com/milvus-io/milvus/pkg/v3/util/conc" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) const ( @@ -147,7 +147,7 @@ func fetchRowCountsConcurrently( futures[k] = pool.Submit(func() (struct{}, error) { fetchedInfo, err := GetFileInfo(format, fileInfos[idx].FilePath, storageConfig, extfs) if err != nil { - return struct{}{}, fmt.Errorf("failed to get file info for %s: %w", fileInfos[idx].FilePath, err) + return struct{}{}, merr.Wrapf(err, "failed to get file info for %s", fileInfos[idx].FilePath) } // Distinct indexes across workers -> no race on rowCounts. rowCounts[idx] = fetchedInfo.NumRows @@ -184,7 +184,7 @@ func FetchFragmentsFromExternalSourceWithRange( log := log.Ctx(ctx) if exploreManifestPath == "" { - return nil, fmt.Errorf("explore manifest path is required") + return nil, merr.WrapErrServiceInternalMsg("explore manifest path is required") } extfs := ExternalSpecContext{ @@ -196,7 +196,7 @@ func FetchFragmentsFromExternalSourceWithRange( exploreStart := time.Now() fileInfos, err := ReadFileInfosFromManifestPath(exploreManifestPath, storageConfig) if err != nil { - return nil, fmt.Errorf("failed to read explore manifest: %w", err) + return nil, merr.Wrap(err, "failed to read explore manifest") } rawCount := len(fileInfos) // Apply the same sort+format-filter that DataCoord used to derive @@ -220,11 +220,11 @@ func FetchFragmentsFromExternalSourceWithRange( fileIndexEnd = int64(len(fileInfos)) } if fileIndexBegin >= int64(len(fileInfos)) { - return nil, fmt.Errorf("fileIndexBegin %d >= total files %d", fileIndexBegin, len(fileInfos)) + return nil, merr.WrapErrServiceInternalMsg("fileIndexBegin %d >= total files %d", fileIndexBegin, len(fileInfos)) } fileInfos = fileInfos[fileIndexBegin:fileIndexEnd] if len(fileInfos) == 0 { - return nil, fmt.Errorf("no files in range [%d, %d)", fileIndexBegin, fileIndexEnd) + return nil, merr.WrapErrServiceInternalMsg("no files in range [%d, %d)", fileIndexBegin, fileIndexEnd) } getFileInfoStart := time.Now() @@ -243,7 +243,7 @@ func FetchFragmentsFromExternalSourceWithRange( fragments = append(fragments, SplitFileToFragments(fi.FilePath, rowCounts[i], rowLimit, fragmentIDGenerator)...) } if len(fragments) == 0 { - return nil, fmt.Errorf("no data files in range [%d, %d)", fileIndexBegin, fileIndexEnd) + return nil, merr.WrapErrServiceInternalMsg("no data files in range [%d, %d)", fileIndexBegin, fileIndexEnd) } log.Info("Created fragments from file range", @@ -271,8 +271,7 @@ func BuildCurrentSegmentFragments( if seg.GetManifestPath() != "" && storageConfig != nil { fragments, err := ReadFragmentsFromManifest(seg.GetManifestPath(), storageConfig, columns) if err != nil { - return nil, fmt.Errorf("failed to read manifest for segment %d at %s: %w", - seg.GetID(), seg.GetManifestPath(), err) + return nil, merr.Wrapf(err, "failed to read manifest for segment %d at %s", seg.GetID(), seg.GetManifestPath()) } if len(fragments) > 0 { result[seg.GetID()] = fragments diff --git a/internal/streamingcoord/client/assignment/assignment_impl.go b/internal/streamingcoord/client/assignment/assignment_impl.go index e019ef8d01..cae890711a 100644 --- a/internal/streamingcoord/client/assignment/assignment_impl.go +++ b/internal/streamingcoord/client/assignment/assignment_impl.go @@ -14,6 +14,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/streamingpb" "github.com/milvus-io/milvus/pkg/v3/streaming/util/types" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/replicateutil" "github.com/milvus-io/milvus/pkg/v3/util/syncutil" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" @@ -102,7 +103,7 @@ func (c *AssignmentServiceImpl) ReportAssignmentError(ctx context.Context, pchan // wait for service ready. assignment, err := c.getAssignmentDiscoverOrWait(ctx) if err != nil { - return errors.Wrap(err, "at creating assignment service") + return merr.Wrap(err, "at creating assignment service") } assignment.ReportAssignmentError(pchannel, assignmentErr) return nil diff --git a/internal/streamingcoord/server/balancer/balancer_impl.go b/internal/streamingcoord/server/balancer/balancer_impl.go index 9bba16de1d..c4aaa76e7f 100644 --- a/internal/streamingcoord/server/balancer/balancer_impl.go +++ b/internal/streamingcoord/server/balancer/balancer_impl.go @@ -19,6 +19,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" "github.com/milvus-io/milvus/pkg/v3/streaming/util/types" "github.com/milvus-io/milvus/pkg/v3/util/contextutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/replicateutil" "github.com/milvus-io/milvus/pkg/v3/util/syncutil" @@ -31,6 +32,11 @@ const ( versionChecker300 = "<3.0.0-beta" ) +// errVersionWatchDone is an INTERNAL break-signal sentinel used by +// blockUntilRoleGreaterThanVersion to exit the resolver watch callback. +// Never crosses any gRPC boundary. +var errVersionWatchDone = errors.New("done") + // RecoverBalancer recover the balancer working. func RecoverBalancer( ctx context.Context, @@ -44,7 +50,7 @@ func RecoverBalancer( // Recover the channel view from catalog. manager, err := channel.RecoverChannelManager(ctx, provider.GetInitialChannels()...) if err != nil { - return nil, errors.Wrap(err, "fail to recover channel manager") + return nil, merr.Wrap(err, "fail to recover channel manager") } manager.SetLogger(resource.Resource().Logger().With(log.FieldComponent("channel-manager"))) ctx, cancel := context.WithCancelCause(context.Background()) @@ -497,7 +503,6 @@ func (b *balancerImpl) blockUntilExpectedInitialStreamingNodeNumReached(ctx cont // blockUntilRoleGreaterThanVersion blocks until every session of the role is outside versionChecker. func (b *balancerImpl) blockUntilRoleGreaterThanVersion(ctx context.Context, role string, versionChecker string) error { - doneErr := errors.New("done") logger := b.Logger().With(zap.String("role", role)) logger.Info("start to wait that the nodes is greater than version", zap.String("version", versionChecker)) // Check if there's any proxy or data node with version < 2.6.0. @@ -509,12 +514,12 @@ func (b *balancerImpl) blockUntilRoleGreaterThanVersion(ctx context.Context, rol r := rb.Resolver() err := r.Watch(ctx, func(vs resolver.VersionedState) error { if len(vs.Sessions()) == 0 { - return doneErr + return errVersionWatchDone } logger.Info("session changes", zap.Int("sessionCount", len(vs.Sessions()))) return nil }) - if err != nil && !errors.Is(err, doneErr) { + if err != nil && !errors.Is(err, errVersionWatchDone) { logger.Info("fail to wait that the nodes is greater than version", zap.String("version", versionChecker), zap.Error(err)) return err } @@ -569,14 +574,14 @@ func (b *balancerImpl) balance(ctx context.Context) (bool, error) { currentLayout := generateCurrentLayout(pchannelView, nodeStatus, accessMode) expectedLayout, err := b.policy.Balance(currentLayout) if err != nil { - return false, errors.Wrap(err, "fail to balance") + return false, merr.Wrap(err, "fail to balance") } b.Logger().Info("balance policy generate result success, try to assign...", zap.Stringer("expectedLayout", expectedLayout)) // bookkeeping the meta assignment started. modifiedChannels, err := b.channelMetaManager.AssignPChannels(ctx, expectedLayout.ChannelAssignment) if err != nil { - return false, errors.Wrap(err, "fail to assign pchannels") + return false, merr.Wrap(err, "fail to assign pchannels") } if len(modifiedChannels) == 0 { @@ -590,7 +595,7 @@ func (b *balancerImpl) balance(ctx context.Context) (bool, error) { func (b *balancerImpl) fetchStreamingNodeStatus(ctx context.Context, rgName string) (map[int64]*types.StreamingNodeStatus, error) { nodeStatus, err := resource.Resource().StreamingNodeManagerClient().CollectAllStatus(ctx, rgName) if err != nil { - return nil, errors.Wrap(err, "fail to collect all status") + return nil, merr.Wrap(err, "fail to collect all status") } // mark the frozen node as frozen in the node status. diff --git a/internal/streamingcoord/server/balancer/channel/manager.go b/internal/streamingcoord/server/balancer/channel/manager.go index d06bb63734..e0774f2100 100644 --- a/internal/streamingcoord/server/balancer/channel/manager.go +++ b/internal/streamingcoord/server/balancer/channel/manager.go @@ -12,6 +12,7 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus/internal/streamingcoord/server/resource" + "github.com/milvus-io/milvus/internal/util/streamingutil/status" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/streamingpb" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" @@ -122,7 +123,7 @@ func recoverCChannelMeta(ctx context.Context, incomingChannel ...string) (*strea } if cchannelMeta == nil { if len(incomingChannel) == 0 { - return nil, errors.New("no incoming channel while no control channel meta found") + return nil, status.NewInner("no incoming channel while no control channel meta found") } cchannelMeta = &streamingpb.CChannelMeta{ Pchannel: incomingChannel[0], @@ -363,7 +364,7 @@ func (cm *ChannelManager) MarkStreamingVersion(ctx context.Context, version int6 defer cm.cond.L.Unlock() if cm.streamingVersion == nil { - return errors.New("streaming service is not enabled, cannot mark streaming version") + return status.NewInner("streaming service is not enabled, cannot mark streaming version") } if cm.streamingVersion.Version >= version { return nil @@ -396,7 +397,7 @@ func (cm *ChannelManager) AllocVirtualChannels(ctx context.Context, param AllocV availableChannels := cm.sortAvailableChannelsByVChannelCount() if len(availableChannels) < param.Num { - return nil, errors.Errorf("not enough pchannels to allocate, expected: %d, got: %d", param.Num, len(availableChannels)) + return nil, status.NewInner("not enough pchannels to allocate, expected: %d, got: %d", param.Num, len(availableChannels)) } vchannels := make([]string, 0, param.Num) diff --git a/internal/streamingcoord/server/balancer/policy/vchannelfair/builder.go b/internal/streamingcoord/server/balancer/policy/vchannelfair/builder.go index ef2af66366..119b49df81 100644 --- a/internal/streamingcoord/server/balancer/policy/vchannelfair/builder.go +++ b/internal/streamingcoord/server/balancer/policy/vchannelfair/builder.go @@ -51,10 +51,15 @@ type policyConfig struct { RebalanceMaxStep int } -// Vaildate validates the vchannel fair policy config. +// errPolicyConfigNegative is returned by policyConfig.Validate when any +// numeric field is negative. INTERNAL: caller logs it (caller already +// dumps cfg via zap.Any), never crosses any gRPC boundary. +var errPolicyConfigNegative = errors.New("vchannel fair policy config has negative value(s)") + +// Validate validates the vchannel fair policy config. func (c policyConfig) Validate() error { if c.PChannelWeight < 0 || c.VChannelWeight < 0 || c.AntiAffinityWeight < 0 || c.RebalanceTolerance < 0 || c.RebalanceMaxStep < 0 { - return errors.Errorf("invalid vchannel fair policy config, %+v", c) + return errPolicyConfigNegative } return nil } diff --git a/internal/streamingcoord/server/balancer/policy/vchannelfair/vchannel_fair_policy.go b/internal/streamingcoord/server/balancer/policy/vchannelfair/vchannel_fair_policy.go index e014684738..f08dfc08c6 100644 --- a/internal/streamingcoord/server/balancer/policy/vchannelfair/vchannel_fair_policy.go +++ b/internal/streamingcoord/server/balancer/policy/vchannelfair/vchannel_fair_policy.go @@ -3,11 +3,11 @@ package vchannelfair import ( "math" - "github.com/cockroachdb/errors" "github.com/samber/lo" "go.uber.org/zap" "github.com/milvus-io/milvus/internal/streamingcoord/server/balancer" + "github.com/milvus-io/milvus/internal/util/streamingutil/status" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/streaming/util/types" ) @@ -30,7 +30,7 @@ func (p *policy) Name() string { // Balance will balance the load of streaming node by vchannel count. func (p *policy) Balance(currentLayout balancer.CurrentLayout) (layout balancer.ExpectedLayout, err error) { if currentLayout.TotalNodes() == 0 { - return balancer.ExpectedLayout{}, errors.New("no available streaming node") + return balancer.ExpectedLayout{}, status.NewInner("no available streaming node") } // update policy configuration before balancing. p.updatePolicyConfiguration() @@ -129,7 +129,7 @@ func (p *policy) updatePolicyConfiguration() { // try to fetch latest configuration. newCfg := newVChannelFairPolicyConfig() if err := newCfg.Validate(); err != nil { - p.Logger().Warn("invalid new incoming vchannel fair policy config", zap.Any("new", newCfg)) + p.Logger().Warn("invalid new incoming vchannel fair policy config", zap.Any("new", newCfg), zap.Error(err)) } else if p.cfg != newCfg { p.Logger().Info("vchannel fair policy config updated", zap.Any("old", p.cfg), zap.Any("new", newCfg)) p.cfg = newCfg diff --git a/internal/streamingcoord/server/broadcaster/ack_callback_scheduler.go b/internal/streamingcoord/server/broadcaster/ack_callback_scheduler.go index 12aa037cdd..63e1f156f9 100644 --- a/internal/streamingcoord/server/broadcaster/ack_callback_scheduler.go +++ b/internal/streamingcoord/server/broadcaster/ack_callback_scheduler.go @@ -8,12 +8,12 @@ import ( "time" "github.com/cenkalti/backoff/v4" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus/internal/streamingcoord/server/broadcaster/registry" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/syncutil" ) @@ -238,7 +238,7 @@ func (s *ackCallbackScheduler) fixIncompleteBroadcastsForForcePromote(ctx contex zap.String("messageType", task.msg.MessageType().String()), zap.Int("pendingVChannels", len(pending.pendingMessages))) if _, err := s.bm.broadcastScheduler.AddTask(ctx, pending); err != nil { - return errors.Wrapf(err, "failed to supplement task %d via broadcastScheduler", task.Header().BroadcastID) + return merr.Wrapf(err, "failed to supplement task %d via broadcastScheduler", task.Header().BroadcastID) } } s.Logger().Info("All incomplete broadcasts fixed and tombstoned") diff --git a/internal/streamingcoord/server/broadcaster/broadcast/singleton.go b/internal/streamingcoord/server/broadcaster/broadcast/singleton.go index 296f32b2f9..85fa253174 100644 --- a/internal/streamingcoord/server/broadcaster/broadcast/singleton.go +++ b/internal/streamingcoord/server/broadcaster/broadcast/singleton.go @@ -3,11 +3,10 @@ package broadcast import ( "context" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus/internal/streamingcoord/server/balancer/balance" "github.com/milvus-io/milvus/internal/streamingcoord/server/broadcaster" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/syncutil" ) @@ -39,7 +38,7 @@ func StartBroadcastWithResourceKeys(ctx context.Context, resourceKeys ...message return nil, err } if err := b.WaitUntilWALbasedDDLReady(ctx); err != nil { - return nil, errors.Wrap(err, "failed to wait until WAL based DDL ready") + return nil, merr.Wrap(err, "failed to wait until WAL based DDL ready") } return broadcaster.WithResourceKeys(ctx, resourceKeys...) } @@ -57,7 +56,7 @@ func StartBroadcastWithSecondaryClusterResourceKey(ctx context.Context) (broadca return nil, err } if err := b.WaitUntilWALbasedDDLReady(ctx); err != nil { - return nil, errors.Wrap(err, "failed to wait until WAL based DDL ready") + return nil, merr.Wrap(err, "failed to wait until WAL based DDL ready") } return broadcaster.WithSecondaryClusterResourceKey(ctx) } diff --git a/internal/streamingcoord/server/broadcaster/broadcast_manager.go b/internal/streamingcoord/server/broadcaster/broadcast_manager.go index 312f167b12..b6e2cf5245 100644 --- a/internal/streamingcoord/server/broadcaster/broadcast_manager.go +++ b/internal/streamingcoord/server/broadcaster/broadcast_manager.go @@ -5,7 +5,6 @@ import ( "sync" "time" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus/internal/streamingcoord/server/balancer/balance" @@ -16,6 +15,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/streamingpb" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" "github.com/milvus-io/milvus/pkg/v3/streaming/util/types" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/replicateutil" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -117,7 +117,7 @@ func (bm *broadcastTaskManager) WithResourceKeys(ctx context.Context, resourceKe id, err := resource.Resource().IDAllocator().Allocate(ctx) if err != nil { guards.Unlock() - return nil, errors.Wrapf(err, "allocate new id failed") + return nil, merr.Wrapf(err, "allocate new id failed") } if err := bm.checkClusterRole(ctx); err != nil { @@ -140,7 +140,7 @@ func (bm *broadcastTaskManager) WithResourceKeys(ctx context.Context, resourceKe func (bm *broadcastTaskManager) WithSecondaryClusterResourceKey(ctx context.Context) (BroadcastAPI, error) { id, err := resource.Resource().IDAllocator().Allocate(ctx) if err != nil { - return nil, errors.Wrapf(err, "allocate new id failed") + return nil, merr.Wrapf(err, "allocate new id failed") } startLockInstant := time.Now() diff --git a/internal/streamingcoord/server/broadcaster/broadcast_task.go b/internal/streamingcoord/server/broadcaster/broadcast_task.go index 70202a6927..e8ee689d6c 100644 --- a/internal/streamingcoord/server/broadcaster/broadcast_task.go +++ b/internal/streamingcoord/server/broadcaster/broadcast_task.go @@ -2,9 +2,9 @@ package broadcaster import ( "context" + "fmt" "sync" - "github.com/cockroachdb/errors" "go.uber.org/zap" "google.golang.org/protobuf/proto" @@ -15,6 +15,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" "github.com/milvus-io/milvus/pkg/v3/streaming/util/types" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // newBroadcastTaskFromProto creates a new broadcast task from the proto. @@ -252,7 +253,7 @@ func (b *broadcastTask) MarkIgnore() error { msg := message.NewBroadcastMutableMessageBeforeAppend(b.task.Message.Payload, copiedProps) alterMsg, err := message.AsMutableAlterReplicateConfigMessageV2(msg) if err != nil { - return errors.Wrap(err, "failed to parse message as AlterReplicateConfigMessage") + return merr.Wrap(err, "failed to parse message as AlterReplicateConfigMessage") } // Get current header and set ignore to true @@ -402,9 +403,9 @@ func (b *broadcastTask) copyAndSetAckedCheckpoints(msgs ...message.ImmutableMess task := proto.Clone(b.task).(*streamingpb.BroadcastTask) for _, msg := range msgs { vchannel := msg.VChannel() - idx, err := findIdxOfVChannel(vchannel, b.header().VChannels) - if err != nil { - panic(err) + idx := findIdxOfVChannel(vchannel, b.header().VChannels) + if idx < 0 { + panic(fmt.Sprintf("broadcast task invariant violated: vchannel %s not in task's own VChannels list", vchannel)) } if len(task.AckedVchannelBitmap) == 0 { task.AckedVchannelBitmap = make([]byte, len(b.header().VChannels)) @@ -433,14 +434,17 @@ func (b *broadcastTask) copyAndSetAckedCheckpoints(msgs ...message.ImmutableMess return } -// findIdxOfVChannel finds the index of the vchannel in the broadcast task. -func findIdxOfVChannel(vchannel string, vchannels []string) (int, error) { +// findIdxOfVChannel finds the index of the vchannel in the broadcast task's +// VChannels list, returning -1 if not present. By construction the vchannel +// must be present (it came from the task's own messages); callers panic on +// -1 because that signals a task-invariant violation. +func findIdxOfVChannel(vchannel string, vchannels []string) int { for i, channelName := range vchannels { if channelName == vchannel { - return i, nil + return i } } - return -1, errors.Errorf("unreachable: vchannel is %s not found in the broadcast task", vchannel) + return -1 } // FastAck trigger a fast ack operation when the broadcast operation is done. diff --git a/internal/streamingcoord/server/broadcaster/registry/ack_message_callback.go b/internal/streamingcoord/server/broadcaster/registry/ack_message_callback.go index 52e9ff96b3..fe1774add6 100644 --- a/internal/streamingcoord/server/broadcaster/registry/ack_message_callback.go +++ b/internal/streamingcoord/server/broadcaster/registry/ack_message_callback.go @@ -5,10 +5,10 @@ import ( "fmt" "sync" - "github.com/cockroachdb/errors" "google.golang.org/protobuf/proto" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/syncutil" ) @@ -60,7 +60,7 @@ func CallMessageAckCallback(ctx context.Context, msg message.BroadcastMutableMes } callback, err := callbackFuture.GetWithContext(ctx) if err != nil { - return errors.Wrap(err, "when waiting callback registered") + return merr.Wrap(err, "when waiting callback registered") } return callback(ctx, msg, result) } diff --git a/internal/streamingcoord/server/broadcaster/registry/ack_once_message_callback.go b/internal/streamingcoord/server/broadcaster/registry/ack_once_message_callback.go index a445c3f744..f74039c5a6 100644 --- a/internal/streamingcoord/server/broadcaster/registry/ack_once_message_callback.go +++ b/internal/streamingcoord/server/broadcaster/registry/ack_once_message_callback.go @@ -5,10 +5,10 @@ import ( "fmt" "sync" - "github.com/cockroachdb/errors" "google.golang.org/protobuf/proto" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/syncutil" ) @@ -86,7 +86,7 @@ func callMessageAckOnceCallback(ctx context.Context, msg message.ImmutableMessag } callback, err := callbackFuture.GetWithContext(ctx) if err != nil { - return errors.Wrap(err, "when waiting callback registered") + return merr.Wrap(err, "when waiting callback registered") } return callback(ctx, msg) } diff --git a/internal/streamingcoord/server/broadcaster/resource_key_locker.go b/internal/streamingcoord/server/broadcaster/resource_key_locker.go index 0d6de6c1c1..3a31193bc7 100644 --- a/internal/streamingcoord/server/broadcaster/resource_key_locker.go +++ b/internal/streamingcoord/server/broadcaster/resource_key_locker.go @@ -8,6 +8,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/messagespb" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" "github.com/milvus-io/milvus/pkg/v3/util/lock" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -99,7 +100,7 @@ func (r *resourceKeyLocker) FastLock(keys ...message.ResourceKey) (*lockGuards, continue } g.Unlock() - return nil, errors.Wrapf(errFastLockFailed, "fast lock failed at resource key %s", key.String()) + return nil, merr.Wrapf(errFastLockFailed, "fast lock failed at resource key %s", key.String()) } return g, nil } diff --git a/internal/streamingcoord/server/service/assignment.go b/internal/streamingcoord/server/service/assignment.go index 21cb3116cf..ce9f4d8e63 100644 --- a/internal/streamingcoord/server/service/assignment.go +++ b/internal/streamingcoord/server/service/assignment.go @@ -30,7 +30,13 @@ import ( var _ streamingpb.StreamingCoordAssignmentServiceServer = (*assignmentServiceImpl)(nil) -var errReplicateConfigurationSame = errors.New("same replicate configuration") +var ( + errReplicateConfigurationSame = errors.New("same replicate configuration") + // errAssignmentDone is returned from a WatchChannelAssignments callback to + // signal "target configuration reached, stop watching". The outer call site + // identifies it via errors.Is and treats it as success. + errAssignmentDone = errors.New("done") +) // NewAssignmentService returns a new assignment service. func NewAssignmentService() streamingpb.StreamingCoordAssignmentServiceServer { @@ -125,14 +131,13 @@ func (s *assignmentServiceImpl) waitUntilPrimaryChangeOrConfigurationSame(ctx co if err != nil { return err } - errDone := errors.New("done") err = b.WatchChannelAssignments(ctx, func(param balancer.WatchChannelAssignmentsCallbackParam) error { if proto.Equal(config, param.ReplicateConfiguration) { - return errDone + return errAssignmentDone } return nil }) - if errors.Is(err, errDone) { + if errors.Is(err, errAssignmentDone) { return nil } return err @@ -191,19 +196,19 @@ func validateForcePromoteConfiguration(config *commonpb.ReplicateConfiguration, // Use config helper to validate the configuration structure helper, err := replicateutil.NewConfigHelper(currentClusterID, config) if err != nil { - return status.NewInvaildArgument("invalid replicate configuration for force promote: %v", err) + return status.NewInvalidArgument("invalid replicate configuration for force promote: %v", err) } // Check that configuration contains exactly one cluster (the current cluster) if len(config.Clusters) != 1 { - return status.NewInvaildArgument( + return status.NewInvalidArgument( "force promote requires configuration with exactly one cluster (current cluster only), got %d clusters", len(config.Clusters)) } // Check that the single cluster is the current cluster if config.Clusters[0].ClusterId != currentClusterID { - return status.NewInvaildArgument( + return status.NewInvalidArgument( "force promote requires configuration with only current cluster %s, got cluster %s", currentClusterID, config.Clusters[0].ClusterId) @@ -211,14 +216,14 @@ func validateForcePromoteConfiguration(config *commonpb.ReplicateConfiguration, // Check that there is NO topology (no replication) if len(config.CrossClusterTopology) > 0 { - return status.NewInvaildArgument( + return status.NewInvalidArgument( "force promote requires configuration with no topology (single primary cluster), got %d topology edges", len(config.CrossClusterTopology)) } // Verify the cluster role is primary (should be true for single cluster with no topology) if helper.GetCurrentCluster().Role() != replicateutil.RolePrimary { - return status.NewInvaildArgument("force promote configuration must result in current cluster being primary") + return status.NewInvalidArgument("force promote configuration must result in current cluster being primary") } return nil @@ -235,7 +240,7 @@ func (s *assignmentServiceImpl) handleForcePromote(ctx context.Context, config * broadcaster, err := broadcast.StartBroadcastWithSecondaryClusterResourceKey(ctx) if err != nil { if errors.Is(err, broadcast.ErrNotSecondary) { - return nil, status.NewInvaildArgument("force promote can only be used on secondary clusters, current cluster is primary") + return nil, status.NewInvalidArgument("force promote can only be used on secondary clusters, current cluster is primary") } return nil, err } @@ -305,7 +310,7 @@ func (s *assignmentServiceImpl) buildForcePromoteConfiguration(ctx context.Conte // Force promote requires current config to exist (secondary must have been configured) if currentConfig == nil { - return nil, nil, status.NewInvaildArgument("force promote requires existing replicate configuration; current cluster has no configuration") + return nil, nil, status.NewInvalidArgument("force promote requires existing replicate configuration; current cluster has no configuration") } // Get the current cluster from existing config directly (don't construct a new one) @@ -317,7 +322,7 @@ func (s *assignmentServiceImpl) buildForcePromoteConfiguration(ctx context.Conte } } if currentCluster == nil { - return nil, nil, status.NewInvaildArgument("force promote requires current cluster in existing configuration; cluster %s not found in config", currentClusterID) + return nil, nil, status.NewInvalidArgument("force promote requires current cluster in existing configuration; cluster %s not found in config", currentClusterID) } // Get pchannels from PChannelView for validation diff --git a/internal/streamingnode/client/handler/consumer/consumer_impl.go b/internal/streamingnode/client/handler/consumer/consumer_impl.go index 9a93301abf..ff9912cf3a 100644 --- a/internal/streamingnode/client/handler/consumer/consumer_impl.go +++ b/internal/streamingnode/client/handler/consumer/consumer_impl.go @@ -255,8 +255,8 @@ func (c *consumerImpl) handleTxnMessage(msg message.ImmutableMessage) error { Ctx: c.ctx, Message: msg, }); result.Error != nil { - c.logger.Warn("message handle canceled at txn", zap.Error(err)) - return errors.Wrap(err, "At Handler Of Txn") + c.logger.Warn("message handle canceled at txn", zap.Error(result.Error)) + return errors.Wrapf(result.Error, "At Handler Of Txn") } default: if c.txnBuilder == nil { diff --git a/internal/streamingnode/client/handler/handler_client_impl.go b/internal/streamingnode/client/handler/handler_client_impl.go index f82fe5c130..d08f3250d2 100644 --- a/internal/streamingnode/client/handler/handler_client_impl.go +++ b/internal/streamingnode/client/handler/handler_client_impl.go @@ -78,7 +78,7 @@ func (hc *handlerClientImpl) GetReplicateCheckpoint(ctx context.Context, pchanne logger := log.With(zap.String("pchannel", pchannel), zap.String("handler", "replicate checkpoint")) cp, err := hc.createHandlerAfterStreamingNodeReady(ctx, logger, pchannel, func(ctx context.Context, assign *types.PChannelInfoAssigned) (any, error) { if assign.Channel.AccessMode != types.AccessModeRW { - return nil, errors.New("replicate checkpoint can only be read for RW channel") + return nil, status.NewInvalidArgument("replicate checkpoint can only be read for RW channel") } localWAL, err := registry.GetLocalAvailableWAL(assign.Channel) if err == nil { @@ -115,7 +115,7 @@ func (hc *handlerClientImpl) GetSalvageCheckpoint(ctx context.Context, pchannel logger := log.With(zap.String("pchannel", pchannel), zap.String("handler", "salvage checkpoint")) cps, err := hc.createHandlerAfterStreamingNodeReady(ctx, logger, pchannel, func(ctx context.Context, assign *types.PChannelInfoAssigned) (any, error) { if assign.Channel.AccessMode != types.AccessModeRW { - return nil, errors.New("salvage checkpoint can only be read for RW channel") + return nil, status.NewInvalidArgument("salvage checkpoint can only be read for RW channel") } localWAL, err := registry.GetLocalAvailableWAL(assign.Channel) if err == nil { @@ -218,7 +218,7 @@ func (hc *handlerClientImpl) CreateProducer(ctx context.Context, opts *ProducerO logger := log.With(zap.String("pchannel", opts.PChannel), zap.String("handler", "producer")) p, err := hc.createHandlerAfterStreamingNodeReady(ctx, logger, opts.PChannel, func(ctx context.Context, assign *types.PChannelInfoAssigned) (any, error) { if assign.Channel.AccessMode != types.AccessModeRW { - return nil, errors.New("producer can only be created for RW channel") + return nil, status.NewInvalidArgument("producer can only be created for RW channel") } // Check if the localWAL is assigned at local localWAL, err := registry.GetLocalAvailableWAL(assign.Channel) diff --git a/internal/streamingnode/client/handler/producer/producer_impl.go b/internal/streamingnode/client/handler/producer/producer_impl.go index 887d015e54..9b24cbbe51 100644 --- a/internal/streamingnode/client/handler/producer/producer_impl.go +++ b/internal/streamingnode/client/handler/producer/producer_impl.go @@ -250,7 +250,7 @@ func (p *producerImpl) sendLoop() (err error) { for { select { case <-p.recvExitCh: - return errors.New("recv arm of stream closed") + return status.NewOnShutdownError("recv arm of stream closed") case req, ok := <-p.requestCh: if !ok { // all message has been sent, sent close response. diff --git a/internal/streamingnode/server/flusher/flusherimpl/flusher_components.go b/internal/streamingnode/server/flusher/flusherimpl/flusher_components.go index 2fef411c84..89aa22527b 100644 --- a/internal/streamingnode/server/flusher/flusherimpl/flusher_components.go +++ b/internal/streamingnode/server/flusher/flusherimpl/flusher_components.go @@ -223,7 +223,7 @@ func (impl *flusherComponents) buildDataSyncServiceWithRetry(ctx context.Context } logger.Info("append flush message for segments that not created by streaming service into wal", zap.Stringer("msgID", appendResult.MessageID), zap.Uint64("timeTick", appendResult.TimeTick)) return nil - }, retry.AttemptAlways()); err != nil { + }, retry.AttemptAlways(), retry.RetryErr(func(error) bool { return true })); err != nil { return nil, err } } @@ -233,7 +233,7 @@ func (impl *flusherComponents) buildDataSyncServiceWithRetry(ctx context.Context var err error ds, err = impl.buildDataSyncService(ctx, recoverInfo) return err - }, retry.AttemptAlways()) + }, retry.AttemptAlways(), retry.RetryErr(func(error) bool { return true })) if err != nil { return nil, err } diff --git a/internal/streamingnode/server/flusher/flusherimpl/msg_handler_impl.go b/internal/streamingnode/server/flusher/flusherimpl/msg_handler_impl.go index fe1fece282..478f4359c2 100644 --- a/internal/streamingnode/server/flusher/flusherimpl/msg_handler_impl.go +++ b/internal/streamingnode/server/flusher/flusherimpl/msg_handler_impl.go @@ -93,7 +93,7 @@ func (impl *msgHandlerImpl) createNewGrowingSegment(ctx context.Context, vchanne } logger.Info("alloc growing segment at datacoord", zap.Int32("schemaVersion", h.SchemaVersion)) return nil - }, retry.AttemptAlways()) + }, retry.AttemptAlways(), retry.RetryErr(func(error) bool { return true })) } func (impl *msgHandlerImpl) HandleFlush(flushMsg message.ImmutableFlushMessageV2) error { diff --git a/internal/streamingnode/server/flusher/flusherimpl/util.go b/internal/streamingnode/server/flusher/flusherimpl/util.go index 024f575211..2dfd8712a8 100644 --- a/internal/streamingnode/server/flusher/flusherimpl/util.go +++ b/internal/streamingnode/server/flusher/flusherimpl/util.go @@ -90,7 +90,7 @@ func (impl *WALFlusherImpl) getRecoveryInfo(ctx context.Context, vchannel string return retry.Unrecoverable(errChannelLifetimeUnrecoverable) } return nil - }, retry.AttemptAlways()) + }, retry.AttemptAlways(), retry.RetryErr(func(error) bool { return true })) return resp, err } diff --git a/internal/streamingnode/server/service/handler/consumer/consume_server.go b/internal/streamingnode/server/service/handler/consumer/consume_server.go index 19858d7134..16ca532589 100644 --- a/internal/streamingnode/server/service/handler/consumer/consume_server.go +++ b/internal/streamingnode/server/service/handler/consumer/consume_server.go @@ -38,7 +38,7 @@ import ( func CreateConsumeServer(walManager walmanager.Manager, streamServer streamingpb.StreamingNodeHandlerService_ConsumeServer) (*ConsumeServer, error) { createReq, err := contextutil.GetCreateConsumer(streamServer.Context()) if err != nil { - return nil, status.NewInvaildArgument("create consumer request is required") + return nil, status.NewInvalidArgument("create consumer request is required") } l, err := walManager.GetAvailableWAL(types.NewPChannelInfoFromProto(createReq.GetPchannel())) @@ -56,11 +56,11 @@ func CreateConsumeServer(walManager walmanager.Manager, streamServer streamingpb req, err := streamServer.Recv() if err != nil { - return nil, errors.New("receive create consumer request failed") + return nil, status.NewInvalidArgument("receive create consumer request failed") } createVChannelReq := req.GetCreateVchannelConsumer() if createVChannelReq == nil { - return nil, errors.New("The first message must be create vchannel consumer request") + return nil, status.NewInvalidArgument("The first message must be create vchannel consumer request") } scanner, err := l.Read(streamServer.Context(), wal.ReadOption{ VChannel: createVChannelReq.GetVchannel(), diff --git a/internal/streamingnode/server/service/handler/producer/produce_server.go b/internal/streamingnode/server/service/handler/producer/produce_server.go index c6bd6f103b..e7394aa44e 100644 --- a/internal/streamingnode/server/service/handler/producer/produce_server.go +++ b/internal/streamingnode/server/service/handler/producer/produce_server.go @@ -29,7 +29,7 @@ import ( func CreateProduceServer(walManager walmanager.Manager, streamServer streamingpb.StreamingNodeHandlerService_ProduceServer) (*ProduceServer, error) { createReq, err := contextutil.GetCreateProducer(streamServer.Context()) if err != nil { - return nil, status.NewInvaildArgument("create producer request is required") + return nil, status.NewInvalidArgument("create producer request is required") } l, err := walManager.GetAvailableWAL(types.NewPChannelInfoFromProto(createReq.GetPchannel())) if err != nil { @@ -116,7 +116,7 @@ func (p *ProduceServer) sendLoop() (err error) { // Recv arm will be closed by context cancel of stream server. // Send an unavailable response to ask client to release resource. p.produceServer.SendClosed() - return errors.New("send loop is stopped for close of wal") + return status.NewOnShutdownError("send loop is stopped for close of wal") case resp, ok := <-p.produceMessageCh: if !ok { // all message has been sent, sent close response. @@ -215,7 +215,7 @@ func (p *ProduceServer) handleProduce(req *streamingpb.ProduceMessageRequest) { func (p *ProduceServer) validateMessage(msg message.MutableMessage) error { // validate the msg. if !msg.MessageType().Valid() { - return status.NewInvaildArgument("unsupported message type") + return status.NewInvalidArgument("unsupported message type") } return nil } diff --git a/internal/streamingnode/server/service/release_manual_flush_preparer.go b/internal/streamingnode/server/service/release_manual_flush_preparer.go index 46a15befd3..fff8d67f5e 100644 --- a/internal/streamingnode/server/service/release_manual_flush_preparer.go +++ b/internal/streamingnode/server/service/release_manual_flush_preparer.go @@ -32,10 +32,10 @@ func (p *releaseManualFlushPreparer) PrepareReleaseManualFlush(ctx context.Conte return false, status.NewInner("write buffer manager is not initialized") } if vchannel == "" { - return false, status.NewInvaildArgument("vchannel is empty") + return false, status.NewInvalidArgument("vchannel is empty") } if collectionID == 0 { - return false, status.NewInvaildArgument("collection id is empty") + return false, status.NewInvalidArgument("collection id is empty") } if len(releaseSegmentIDs) == 0 { return false, nil diff --git a/internal/streamingnode/server/wal/adaptor/opener.go b/internal/streamingnode/server/wal/adaptor/opener.go index 87bf9dd14d..1fd131a06c 100644 --- a/internal/streamingnode/server/wal/adaptor/opener.go +++ b/internal/streamingnode/server/wal/adaptor/opener.go @@ -306,7 +306,7 @@ func (o *openerAdaptorImpl) handleAlterWAL(ctx context.Context, l walimpls.WALIm } targetWALName := snapshot.AlterWALInfo.TargetWALName - return nil, errors.Errorf("WAL switch success: %s switch to %s finish, re-opening required", opt.Channel.Name, targetWALName) + return nil, status.NewInner("WAL switch success: %s switch to %s finish, re-opening required", opt.Channel.Name, targetWALName) } func (o *openerAdaptorImpl) handleAlterWALFlushingStage(ctx context.Context, opt *wal.OpenOption, roWAL *roWALAdaptorImpl, @@ -367,7 +367,7 @@ func (o *openerAdaptorImpl) handleAlterWALFlushingStage(ctx context.Context, opt log.Ctx(ctx).Warn("timeout waiting for flush completion", zap.String("channel", opt.Channel.Name), zap.Duration("timeout", defaultWALSwitchFlushTimeout)) - return errors.Newf("timeout waiting for flush completion during WAL switch") + return status.NewInner("timeout waiting for flush completion during WAL switch") case <-ctx.Done(): log.Ctx(ctx).Warn("context canceled while waiting for flush completion", zap.String("channel", opt.Channel.Name), diff --git a/internal/streamingnode/server/wal/interceptors/shard/shards/segment_flush_worker.go b/internal/streamingnode/server/wal/interceptors/shard/shards/segment_flush_worker.go index 8d6a4b6c1a..cf2f191fc3 100644 --- a/internal/streamingnode/server/wal/interceptors/shard/shards/segment_flush_worker.go +++ b/internal/streamingnode/server/wal/interceptors/shard/shards/segment_flush_worker.go @@ -98,7 +98,7 @@ func (w *segmentFlushWorker) waitForTxnManagerRecoverDone() error { w.Logger().Info("flush segment canceled", zap.Error(w.ctx.Err())) return w.ctx.Err() case <-w.wal.Available(): - return errors.New("wal is unavailable") + return status.NewOnShutdownError("wal is unavailable") } } diff --git a/internal/streamingnode/server/wal/interceptors/shard/shards/shard_manager_collection.go b/internal/streamingnode/server/wal/interceptors/shard/shards/shard_manager_collection.go index 1155e8dad4..a4e7a6a010 100644 --- a/internal/streamingnode/server/wal/interceptors/shard/shards/shard_manager_collection.go +++ b/internal/streamingnode/server/wal/interceptors/shard/shards/shard_manager_collection.go @@ -1,12 +1,12 @@ package shards import ( - "github.com/cockroachdb/errors" "go.uber.org/zap" "google.golang.org/protobuf/proto" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/streamingnode/server/wal/interceptors/shard/policy" + "github.com/milvus-io/milvus/internal/util/streamingutil/status" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/streamingpb" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" @@ -176,7 +176,7 @@ func (m *shardManagerImpl) AlterCollection(msg message.MutableAlterCollectionMes // in downstream GetSchema paths. logger.Error("schema change indicated by UpdateMask but schema body is nil", zap.Int64("collectionID", collectionID)) - return nil, errors.New("schema change message has nil schema body") + return nil, status.NewInvalidArgument("schema change message has nil schema body") } collectionInfo := m.collections[collectionID] collectionInfo.Schema = &streamingpb.CollectionSchemaOfVChannel{ diff --git a/internal/streamingnode/server/wal/interceptors/shard/stats/config.go b/internal/streamingnode/server/wal/interceptors/shard/stats/config.go index 99bf4d63ce..7c26f9c774 100644 --- a/internal/streamingnode/server/wal/interceptors/shard/stats/config.go +++ b/internal/streamingnode/server/wal/interceptors/shard/stats/config.go @@ -3,8 +3,7 @@ package stats import ( "time" - "github.com/cockroachdb/errors" - + "github.com/milvus-io/milvus/internal/util/streamingutil/status" "github.com/milvus-io/milvus/pkg/v3/util/hardware" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -64,7 +63,7 @@ func (c statsConfig) Validate() error { c.l1MinSizeFromIdleTime <= 0 || c.maxBinlogFileNum <= 0 || c.l0MaxLifetime <= 0 { - return errors.Errorf("invalid stats config, cfg: %+v", c) + return status.NewInvalidArgument("invalid stats config, cfg: %+v", c) } return nil } diff --git a/internal/streamingnode/server/wal/interceptors/txn/txn_manager.go b/internal/streamingnode/server/wal/interceptors/txn/txn_manager.go index dc418f6917..90d805592c 100644 --- a/internal/streamingnode/server/wal/interceptors/txn/txn_manager.go +++ b/internal/streamingnode/server/wal/interceptors/txn/txn_manager.go @@ -114,7 +114,7 @@ func (m *TxnManager) buildTxnContext(ctx context.Context, msg message.MutableBeg keepalive = paramtable.Get().StreamingCfg.TxnDefaultKeepaliveTimeout.GetAsDurationByParse() } if keepalive < 1*time.Millisecond { - return nil, status.NewInvaildArgument("keepalive must be greater than 1ms") + return nil, status.NewInvalidArgument("keepalive must be greater than 1ms") } id, err := resource.Resource().IDAllocator().Allocate(ctx) if err != nil { diff --git a/internal/streamingnode/server/wal/recovery/config.go b/internal/streamingnode/server/wal/recovery/config.go index c6ea2f8d99..10c709a23b 100644 --- a/internal/streamingnode/server/wal/recovery/config.go +++ b/internal/streamingnode/server/wal/recovery/config.go @@ -3,8 +3,7 @@ package recovery import ( "time" - "github.com/cockroachdb/errors" - + "github.com/milvus-io/milvus/internal/util/streamingutil/status" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -34,13 +33,13 @@ type config struct { func (cfg *config) validate() error { if cfg.persistInterval <= 0 { - return errors.New("persist interval must be greater than 0") + return status.NewInvalidArgument("persist interval must be greater than 0") } if cfg.maxDirtyMessages <= 0 { - return errors.New("max dirty messages must be greater than 0") + return status.NewInvalidArgument("max dirty messages must be greater than 0") } if cfg.gracefulTimeout <= 0 { - return errors.New("graceful timeout must be greater than 0") + return status.NewInvalidArgument("graceful timeout must be greater than 0") } return nil } diff --git a/internal/streamingnode/server/wal/recovery/recovery_storage_impl.go b/internal/streamingnode/server/wal/recovery/recovery_storage_impl.go index d03568a188..f0fa9ef3d6 100644 --- a/internal/streamingnode/server/wal/recovery/recovery_storage_impl.go +++ b/internal/streamingnode/server/wal/recovery/recovery_storage_impl.go @@ -4,7 +4,6 @@ import ( "context" "sync" - "github.com/cockroachdb/errors" "github.com/samber/lo" "go.uber.org/zap" @@ -12,6 +11,7 @@ import ( "github.com/milvus-io/milvus/internal/distributed/streaming" "github.com/milvus-io/milvus/internal/streamingnode/server/resource" "github.com/milvus-io/milvus/internal/streamingnode/server/wal/utility" + "github.com/milvus-io/milvus/internal/util/streamingutil/status" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/streamingpb" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" @@ -142,11 +142,11 @@ func (r *recoveryStorageImpl) GetSchema(ctx context.Context, vchannel string, ti if _, schema = vchannelInfo.GetSchema(0); schema != nil { return schema, nil } - return nil, errors.Errorf("critical error: schema not found, vchannel: %s, timetick: %d", vchannel, timetick) + return nil, status.NewInner("critical error: schema not found, vchannel: %s, timetick: %d", vchannel, timetick) } return schema, nil } - return nil, errors.Errorf("critical error: vchannel not found, vchannel: %s, timetick: %d", vchannel, timetick) + return nil, status.NewInner("critical error: vchannel not found, vchannel: %s, timetick: %d", vchannel, timetick) } // ObserveMessage is called when a new message is observed. diff --git a/internal/streamingnode/server/wal/recovery/vchannel_recovery_info.go b/internal/streamingnode/server/wal/recovery/vchannel_recovery_info.go index 4944541ff3..f2ce27507f 100644 --- a/internal/streamingnode/server/wal/recovery/vchannel_recovery_info.go +++ b/internal/streamingnode/server/wal/recovery/vchannel_recovery_info.go @@ -3,10 +3,10 @@ package recovery import ( "math" - "github.com/cockroachdb/errors" "google.golang.org/protobuf/proto" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" + "github.com/milvus-io/milvus/internal/util/streamingutil/status" "github.com/milvus-io/milvus/pkg/v3/proto/streamingpb" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message/messageutil" @@ -123,7 +123,7 @@ func (info *vchannelRecoveryInfo) UpdateFlushCheckpoint(checkpoint *WALCheckpoin } return nil } - return errors.Errorf("update illegal checkpoint of flusher, current: %s, target: %s", info.flusherCheckpoint.MessageID.String(), checkpoint.MessageID.String()) + return status.NewInner("update illegal checkpoint of flusher, current: %s, target: %s", info.flusherCheckpoint.MessageID.String(), checkpoint.MessageID.String()) } // ObserveSchemaChange is called when a schema change message is observed. diff --git a/internal/streamingnode/server/wal/utility/reorder_buffer.go b/internal/streamingnode/server/wal/utility/reorder_buffer.go index 52d60c0986..cfd20232d3 100644 --- a/internal/streamingnode/server/wal/utility/reorder_buffer.go +++ b/internal/streamingnode/server/wal/utility/reorder_buffer.go @@ -3,6 +3,7 @@ package utility import ( "github.com/cockroachdb/errors" + "github.com/milvus-io/milvus/internal/util/streamingutil/status" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -37,7 +38,7 @@ func (r *ReOrderByTimeTickBuffer) Push(msg message.ImmutableMessage) error { } msgID := msg.MessageID().Marshal() if r.messageIDs.Contain(msgID) { - return errors.Errorf("message is duplicated: %s", msgID) + return status.NewInner("message is duplicated: %s", msgID) } r.messageHeap.Push(msg) r.messageIDs.Insert(msgID) diff --git a/internal/tso/global_allocator.go b/internal/tso/global_allocator.go index 02cbb88b6e..00c7ce2d89 100644 --- a/internal/tso/global_allocator.go +++ b/internal/tso/global_allocator.go @@ -33,11 +33,11 @@ import ( "sync/atomic" "time" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus/pkg/v3/kv" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/tsoutil" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -110,7 +110,7 @@ func (gta *GlobalTSOAllocator) SetTSO(tso uint64) error { func (gta *GlobalTSOAllocator) GenerateTSO(count uint32) (uint64, error) { var physical, logical int64 if count == 0 { - return 0, errors.New("tso count should be positive") + return 0, merr.WrapErrParameterInvalidMsg("tso count should be positive") } maxRetryCount := 10 @@ -134,7 +134,7 @@ func (gta *GlobalTSOAllocator) GenerateTSO(count uint32) (uint64, error) { } return tsoutil.ComposeTS(physical, logical), nil } - return 0, errors.New("can not get timestamp") + return 0, merr.WrapErrServiceInternalMsg("can not get timestamp") } // Alloc allocates a batch of timestamps. What is returned is the starting timestamp. diff --git a/internal/tso/tso.go b/internal/tso/tso.go index 23a32ed3c8..360a93c80a 100644 --- a/internal/tso/tso.go +++ b/internal/tso/tso.go @@ -40,6 +40,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/kv" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/tsoutil" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -138,11 +139,11 @@ func (t *timestampOracle) ResetUserTimestamp(tso uint64) error { // do not update if typeutil.SubTimeByWallClock(next, prev.physical) <= 3*updateTimestampGuard { - return errors.New("the specified ts too small than now") + return merr.WrapErrParameterInvalidMsg("the specified ts too small than now") } if typeutil.SubTimeByWallClock(next, prev.physical) >= t.maxResetTSGap() { - return errors.New("the specified ts too large than now") + return merr.WrapErrParameterInvalidMsg("the specified ts too large than now") } save := next.Add(t.saveInterval) diff --git a/internal/util/analyzecgowrapper/helper.go b/internal/util/analyzecgowrapper/helper.go index 4794dbb842..59e2303b5d 100644 --- a/internal/util/analyzecgowrapper/helper.go +++ b/internal/util/analyzecgowrapper/helper.go @@ -44,12 +44,10 @@ func HandleCStatus(status *C.CStatus, extraInfo string) error { logMsg := fmt.Sprintf("%s, C Runtime Exception: %s\n", extraInfo, errorMsg) log.Warn(logMsg) - if errorCode == 2003 { - return merr.WrapErrSegcoreUnsupported(int32(errorCode), logMsg) - } - if errorCode == 2033 { + if merr.IsSegcoreSignal(int32(errorCode)) { log.Info("fake finished the task") - return merr.ErrSegcorePretendFinished } - return merr.WrapErrSegcore(int32(errorCode), logMsg) + // Pass the raw errorMsg (not the polluted logMsg) so the merr reason stays + // clean; the extraInfo breadcrumb lives in the log above. + return merr.SegcoreError(int32(errorCode), errorMsg) } diff --git a/internal/util/analyzer/canalyzer/helper.go b/internal/util/analyzer/canalyzer/helper.go index a2714f89f4..bcadc1f613 100644 --- a/internal/util/analyzer/canalyzer/helper.go +++ b/internal/util/analyzer/canalyzer/helper.go @@ -26,12 +26,10 @@ func HandleCStatus(status *C.CStatus, extraInfo string) error { logMsg := fmt.Sprintf("%s, C Runtime Exception: %s\n", extraInfo, errorMsg) log.Warn(logMsg) - if errorCode == 2003 { - return merr.WrapErrSegcoreUnsupported(int32(errorCode), logMsg) - } - if errorCode == 2033 { + if merr.IsSegcoreSignal(int32(errorCode)) { log.Info("fake finished the task") - return merr.ErrSegcorePretendFinished } - return merr.WrapErrSegcore(int32(errorCode), logMsg) + // Pass the raw errorMsg (not the polluted logMsg) so the merr reason stays + // clean; the extraInfo breadcrumb lives in the log above. + return merr.SegcoreError(int32(errorCode), errorMsg) } diff --git a/internal/util/bloomfilter/bloom_filter.go b/internal/util/bloomfilter/bloom_filter.go index 7e026561b4..28676cb590 100644 --- a/internal/util/bloomfilter/bloom_filter.go +++ b/internal/util/bloomfilter/bloom_filter.go @@ -17,13 +17,13 @@ package bloomfilter import ( "github.com/bits-and-blooms/bloom/v3" - "github.com/cockroachdb/errors" "github.com/greatroar/blobloom" "github.com/zeebo/xxh3" "go.uber.org/zap" "github.com/milvus-io/milvus/internal/json" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type BFType int @@ -307,20 +307,20 @@ func UnmarshalJSON(data []byte, bfType BFType) (BloomFilterInterface, error) { bf := &blockedBloomFilter{} err := json.Unmarshal(data, bf) if err != nil { - return nil, errors.Wrap(err, "failed to unmarshal blocked bloom filter") + return nil, merr.Wrap(err, "failed to unmarshal blocked bloom filter") } return bf, nil case BasicBF: bf := &basicBloomFilter{} err := json.Unmarshal(data, bf) if err != nil { - return nil, errors.Wrap(err, "failed to unmarshal blocked bloom filter") + return nil, merr.Wrap(err, "failed to unmarshal blocked bloom filter") } return bf, nil case AlwaysTrueBF: return AlwaysTrueBloomFilter, nil default: - return nil, errors.Errorf("unsupported bloom filter type: %d", bfType) + return nil, merr.WrapErrParameterInvalidMsg("unsupported bloom filter type: %d", bfType) } } diff --git a/internal/util/cgo/futures.go b/internal/util/cgo/futures.go index c396348f43..eb69343201 100644 --- a/internal/util/cgo/futures.go +++ b/internal/util/cgo/futures.go @@ -29,8 +29,6 @@ import ( "github.com/milvus-io/milvus/pkg/v3/util/merr" ) -var ErrConsumed = errors.New("future is already consumed") - // Would put this in futures.go but for the documented issue with // exports and functions in preamble // (https://code.google.com/p/go-wiki/wiki/cgo#Global_functions) @@ -57,7 +55,7 @@ type Future interface { basicFuture // BlockAndLeakyGet block until the future is ready or canceled, and return the leaky result. - // Caller should only call once for BlockAndLeakyGet, otherwise the ErrConsumed will returned. + // Caller should only call once for BlockAndLeakyGet, otherwise a merr.ErrServiceInternal (future is already consumed) will be returned. // Caller will get the merr.ErrSegcoreCancel or merr.ErrSegcoreTimeout respectively if the future is canceled or timeout. // Caller will get other error if the underlying cgo function throws, otherwise caller will get result. // Caller should free the result after used (defined by caller), otherwise the memory of result is leaked. @@ -131,7 +129,8 @@ func (f *futureImpl) BlockAndLeakyGet() (unsafe.Pointer, error) { guard := f.state.LockForConsume() if guard == nil { - return nil, ErrConsumed + // Double-consume is a caller-code lifecycle bug, never user input. + return nil, merr.WrapErrServiceInternalMsg("future is already consumed") } defer guard.Unlock() diff --git a/internal/util/cgo/futures_test.go b/internal/util/cgo/futures_test.go index b1eaffdab1..f35cc332ef 100644 --- a/internal/util/cgo/futures_test.go +++ b/internal/util/cgo/futures_test.go @@ -72,7 +72,7 @@ func TestFutureWithSuccessCase(t *testing.T) { runtime.GC() _, err = future.BlockAndLeakyGet() - assert.ErrorIs(t, err, ErrConsumed) + assert.ErrorIs(t, err, merr.ErrServiceInternal) } func TestFutureWithCaseNoInterrupt(t *testing.T) { @@ -128,7 +128,11 @@ func TestFutures(t *testing.T) { future.BlockUntilReady() // test block until ready too. result, err := future.BlockAndLeakyGet() assert.Error(t, err) - assert.ErrorIs(t, err, merr.ErrSegcoreUnsupported) + // A std::runtime_error surfaces as C++ UnexpectedError(2001), the generic + // catch-all. It must map to the generic ErrSegcore (retriable at the + // scheduler), NOT ErrSegcoreUnsupported (whose merr-code 2001 only coincides; + // the real C++ Unsupported is 2003). + assert.ErrorIs(t, err, merr.ErrSegcore) assert.Nil(t, result) // The inner function sleep 1 seconds, so the future cost must be greater than 0.5 seconds. assert.Greater(t, time.Since(start).Seconds(), 0.5) @@ -161,7 +165,10 @@ func TestFutures(t *testing.T) { future.BlockUntilReady() // test block until ready too. result, err = future.BlockAndLeakyGet() assert.Error(t, err) - assert.ErrorIs(t, err, merr.ErrSegcorePretendFinished) + // C++ NotImplemented(2002) is a real failure, not a pretend-finished signal + // (only ClusterSkip 2033 is). It maps to the generic ErrSegcore; the merr-code + // 2002 of ErrSegcorePretendFinished only coincides with the C++ value. + assert.ErrorIs(t, err, merr.ErrSegcore) assert.Nil(t, result) // The inner function sleep 1 seconds, so the future cost must be greater than 0.5 seconds. assert.Greater(t, time.Since(start).Seconds(), 0.5) diff --git a/internal/util/componentutil/componentutil.go b/internal/util/componentutil/componentutil.go index 90a3e015ed..c3c44f3fdb 100644 --- a/internal/util/componentutil/componentutil.go +++ b/internal/util/componentutil/componentutil.go @@ -18,7 +18,6 @@ package componentutil import ( "context" - "fmt" "time" "go.uber.org/zap" @@ -53,10 +52,11 @@ func WaitForComponentStates[T interface { } } if !meet { - return fmt.Errorf( - "WaitForComponentStates, not meet, %s current state: %s", + return merr.WrapErrServiceNotReady( serviceName, - resp.State.StateCode.String()) + 0, + resp.State.StateCode.String(), + "WaitForComponentStates, not meet") } log.Info("WaitForComponentStates success", zap.String("current state", resp.State.StateCode.String())) return nil diff --git a/internal/util/credentials/credentials.go b/internal/util/credentials/credentials.go index e611b49853..f9c02f1582 100644 --- a/internal/util/credentials/credentials.go +++ b/internal/util/credentials/credentials.go @@ -20,8 +20,9 @@ package credentials import ( "encoding/base64" - "fmt" "strings" + + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) const ( @@ -55,7 +56,7 @@ func (c *Credentials) GetAPIKeyCredential(name string) (string, error) { k := credentialKey(name, APIKey) apikey, exist := c.confMap[k] if !exist { - return "", fmt.Errorf("%s is not a apikey crediential, can not find key: %s", name, k) + return "", merr.WrapErrParameterInvalidMsg("%s is not a apikey crediential, can not find key: %s", name, k) } return apikey, nil } @@ -64,13 +65,13 @@ func (c *Credentials) GetAKSKCredential(name string) (string, string, error) { IdKey := credentialKey(name, AccessKeyId) accessKeyId, exist := c.confMap[IdKey] if !exist { - return "", "", fmt.Errorf("%s is not a aksk crediential, can not find key: %s", name, IdKey) + return "", "", merr.WrapErrParameterInvalidMsg("%s is not a aksk crediential, can not find key: %s", name, IdKey) } AccessKey := credentialKey(name, SecretAccessKey) secretAccessKey, exist := c.confMap[AccessKey] if !exist { - return "", "", fmt.Errorf("%s is not a aksk crediential, can not find key: %s", name, AccessKey) + return "", "", merr.WrapErrParameterInvalidMsg("%s is not a aksk crediential, can not find key: %s", name, AccessKey) } return accessKeyId, secretAccessKey, nil } @@ -79,12 +80,12 @@ func (c *Credentials) GetGcpCredential(name string) ([]byte, error) { k := credentialKey(name, CredentialJSON) jsonByte, exist := c.confMap[k] if !exist { - return nil, fmt.Errorf("%s is not a gcp crediential, can not find key: %s ", name, k) + return nil, merr.WrapErrParameterInvalidMsg("%s is not a gcp crediential, can not find key: %s ", name, k) } decode, err := base64.StdEncoding.DecodeString(jsonByte) if err != nil { - return nil, fmt.Errorf("parse gcp credential:%s faild, err: %s", name, err) + return nil, merr.WrapErrParameterInvalidMsg("parse gcp credential:%s faild, err: %s", name, err) } return decode, nil } diff --git a/internal/util/dependency/factory.go b/internal/util/dependency/factory.go index cfb301debf..69d898f8d0 100644 --- a/internal/util/dependency/factory.go +++ b/internal/util/dependency/factory.go @@ -14,6 +14,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/metrics" "github.com/milvus-io/milvus/pkg/v3/mq/msgstream" "github.com/milvus-io/milvus/pkg/v3/objectstorage" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -107,7 +108,7 @@ func (f *DefaultFactory) initMQ(standalone bool, params *paramtable.ComponentPar f.msgStreamFactory = msgstream.NewWpmsFactory(¶ms.ServiceParam) } if f.msgStreamFactory == nil { - return errors.New("failed to create MQ: check the milvus log for initialization failures") + return merr.WrapErrServiceInternalMsg("failed to create MQ: check the milvus log for initialization failures") } return nil } @@ -142,10 +143,10 @@ func mustSelectMQType(standalone bool, mqType string, enable mqEnable) string { // Validate mq type. func validateMQType(standalone bool, mqType string) error { if mqType != mqTypeRocksmq && mqType != mqTypeKafka && mqType != mqTypePulsar && mqType != mqTypeWoodpecker { - return errors.Newf("mq type %s is invalid", mqType) + return merr.WrapErrParameterInvalidMsg("mq type %s is invalid", mqType) } if !standalone && mqType == mqTypeRocksmq { - return errors.Newf("mq %s is only valid in standalone mode") + return merr.WrapErrParameterInvalidMsg("mq %s is only valid in standalone mode", mqType) } return nil } diff --git a/internal/util/exprutil/expr_checker.go b/internal/util/exprutil/expr_checker.go index d058098f7b..61aca8851f 100644 --- a/internal/util/exprutil/expr_checker.go +++ b/internal/util/exprutil/expr_checker.go @@ -3,11 +3,11 @@ package exprutil import ( "math" - "github.com/cockroachdb/errors" "github.com/samber/lo" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/pkg/v3/proto/planpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -22,7 +22,7 @@ func ParseExprFromPlan(plan *planpb.PlanNode) (*planpb.Expr, error) { node := plan.GetNode() if node == nil { - return nil, errors.New("can't get expr from empty plan node") + return nil, merr.WrapErrParameterInvalidMsg("can't get expr from empty plan node") } var expr *planpb.Expr @@ -32,7 +32,7 @@ func ParseExprFromPlan(plan *planpb.PlanNode) (*planpb.Expr, error) { case *planpb.PlanNode_Query: expr = node.Query.GetPredicates() default: - return nil, errors.New("unsupported plan node type") + return nil, merr.WrapErrParameterInvalidMsg("unsupported plan node type") } return expr, nil @@ -342,7 +342,7 @@ func ValidatePartitionKeyIsolation(expr *planpb.Expr) error { return err } if !foundPartitionKey { - return errors.New("partition key not found in expr or the expr is invalid when validating partition key isolation") + return merr.WrapErrParameterInvalidMsg("partition key not found in expr or the expr is invalid when validating partition key isolation") } return nil } @@ -389,7 +389,7 @@ func validatePartitionKeyIsolationFromBinaryExpr(expr *planpb.BinaryExpr) (bool, // if either side has partition key, but OR them // e.g. partition_key_field == 1 || other_field > 10 if leftRes || rightRes { - return true, errors.New("partition key isolation does not support OR") + return true, merr.WrapErrParameterInvalidMsg("partition key isolation does not support OR") } // if none of them has partition key return false, nil @@ -404,7 +404,7 @@ func validatePartitionKeyIsolationFromUnaryExpr(expr *planpb.UnaryExpr) (bool, e } if expr.Op == planpb.UnaryExpr_Not { if res { - return true, errors.New("partition key isolation does not support NOT") + return true, merr.WrapErrParameterInvalidMsg("partition key isolation does not support NOT") } return false, nil } @@ -414,7 +414,7 @@ func validatePartitionKeyIsolationFromUnaryExpr(expr *planpb.UnaryExpr) (bool, e func validatePartitionKeyIsolationFromTermExpr(expr *planpb.TermExpr) (bool, error) { if expr.GetColumnInfo().GetIsPartitionKey() { // e.g. partition_key_field in [1, 2, 3] - return true, errors.New("partition key isolation does not support IN") + return true, merr.WrapErrParameterInvalidMsg("partition key isolation does not support IN") } return false, nil } @@ -425,14 +425,14 @@ func validatePartitionKeyIsolationFromRangeExpr(expr *planpb.UnaryRangeExpr) (bo // e.g. partition_key_field == 1 return true, nil } - return true, errors.Newf("partition key isolation does not support %s", expr.GetOp().String()) + return true, merr.WrapErrParameterInvalidMsg("partition key isolation does not support %s", expr.GetOp().String()) } return false, nil } func validatePartitionKeyIsolationFromBinaryRangeExpr(expr *planpb.BinaryRangeExpr) (bool, error) { if expr.GetColumnInfo().GetIsPartitionKey() { - return true, errors.New("partition key isolation does not support BinaryRange") + return true, merr.WrapErrParameterInvalidMsg("partition key isolation does not support BinaryRange") } return false, nil } diff --git a/internal/util/flowgraph/flow_graph.go b/internal/util/flowgraph/flow_graph.go index 6e7f4f87cf..2058561ea4 100644 --- a/internal/util/flowgraph/flow_graph.go +++ b/internal/util/flowgraph/flow_graph.go @@ -22,8 +22,9 @@ import ( "sync" "time" - "github.com/cockroachdb/errors" "go.uber.org/atomic" + + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // Flow Graph is no longer a graph rather than a simple pipeline, this simplified our code and increase recovery speed - xiaofan. @@ -56,12 +57,12 @@ func (fg *TimeTickedFlowGraph) SetEdges(nodeName string, out []string) error { currentNode, ok := fg.nodeCtx[nodeName] if !ok { errMsg := "Cannot find node:" + nodeName - return errors.New(errMsg) + return merr.WrapErrParameterInvalidMsg(errMsg) } if len(out) > 1 { errMsg := "Flow graph now support only pipeline mode, with only one or zero output:" + nodeName - return errors.New(errMsg) + return merr.WrapErrParameterInvalidMsg(errMsg) } // init current node's downstream @@ -70,7 +71,7 @@ func (fg *TimeTickedFlowGraph) SetEdges(nodeName string, out []string) error { outNode, ok := fg.nodeCtx[name] if !ok { errMsg := "Cannot find out node:" + name - return errors.New(errMsg) + return merr.WrapErrParameterInvalidMsg(errMsg) } maxQueueLength := outNode.node.MaxQueueLength() outNode.inputChannel = make(chan []Msg, maxQueueLength) @@ -163,7 +164,7 @@ func (fg *TimeTickedFlowGraph) AssembleNodes(orderedNodes ...Node) error { err := fg.SetEdges(node.Name(), []string{orderedNodes[i+1].Name()}) if err != nil { errMsg := fmt.Sprintf("set edges failed for flow graph, node=%s", node.Name()) - return errors.New(errMsg) + return merr.WrapErrParameterInvalidMsg(errMsg) } } } diff --git a/internal/util/function/bm25_function.go b/internal/util/function/bm25_function.go index b2f923b5d3..ea3253eace 100644 --- a/internal/util/function/bm25_function.go +++ b/internal/util/function/bm25_function.go @@ -20,10 +20,8 @@ package function import ( "context" - "fmt" "sync" - "github.com/cockroachdb/errors" "github.com/samber/lo" "go.uber.org/zap" @@ -137,7 +135,7 @@ func NewAnalyzerRunner(field *schemapb.FieldSchema) (Analyzer, error) { func NewBM25FunctionRunner(coll *schemapb.CollectionSchema, schema *schemapb.FunctionSchema) (FunctionRunner, error) { if len(schema.GetOutputFieldIds()) != 1 { - return nil, fmt.Errorf("bm25 function should only have one output field, but now %d", len(schema.GetOutputFieldIds())) + return nil, merr.WrapErrParameterInvalidMsg("bm25 function should only have one output field, but now %d", len(schema.GetOutputFieldIds())) } var inputField, outputField *schemapb.FieldSchema @@ -153,7 +151,7 @@ func NewBM25FunctionRunner(coll *schemapb.CollectionSchema, schema *schemapb.Fun } if outputField == nil { - return nil, errors.New("no output field") + return nil, merr.WrapErrParameterInvalidMsg("no output field") } if params, ok := getMultiAnalyzerParams(inputField); ok { @@ -209,16 +207,16 @@ func (v *BM25FunctionRunner) BatchRun(inputs ...any) ([]any, error) { defer v.mu.RUnlock() if v.closed { - return nil, errors.New("analyzer receview request after function closed") + return nil, merr.WrapErrServiceInternalMsg("analyzer receview request after function closed") } if len(inputs) > 1 { - return nil, errors.New("BM25 function received more than one input column") + return nil, merr.WrapErrParameterInvalidMsg("BM25 function received more than one input column") } text, ok := inputs[0].([]string) if !ok { - return nil, errors.New("BM25 function batch input not string list") + return nil, merr.WrapErrParameterInvalidMsg("BM25 function batch input not string list") } rowNum := len(text) @@ -292,16 +290,16 @@ func (v *BM25FunctionRunner) BatchAnalyze(withDetail bool, withHash bool, inputs defer v.mu.RUnlock() if v.closed { - return nil, errors.New("analyzer receview request after function closed") + return nil, merr.WrapErrServiceInternalMsg("analyzer receview request after function closed") } if len(inputs) > 1 { - return nil, errors.New("analyze received should only receive text input column(not set analyzer name)") + return nil, merr.WrapErrParameterInvalidMsg("analyze received should only receive text input column(not set analyzer name)") } text, ok := inputs[0].([]string) if !ok { - return nil, errors.New("batch input not string list") + return nil, merr.WrapErrParameterInvalidMsg("batch input not string list") } rowNum := len(text) diff --git a/internal/util/function/chain/chain.go b/internal/util/function/chain/chain.go index 3ec695b451..e6b935c67f 100644 --- a/internal/util/function/chain/chain.go +++ b/internal/util/function/chain/chain.go @@ -117,12 +117,12 @@ func (fc *FuncChain) addWithError(op Operator, err error) *FuncChain { func (fc *FuncChain) Validate() error { // Check for errors accumulated during fluent API calls if fc.buildError != nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("chain build error: %v", fc.buildError)) + return merr.WrapErrServiceInternalMsg("chain build error: %v", fc.buildError) } // Stage is required if fc.stage == "" { - return merr.WrapErrParameterInvalidMsg("chain stage is required") + return merr.WrapErrParameterMissingMsg("chain stage is required") } // Validate all operators including stage compatibility @@ -134,13 +134,13 @@ func (fc *FuncChain) Validate() error { func (fc *FuncChain) validateOperators(stage string) error { for i, op := range fc.operators { if op == nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("operator[%d] is nil", i)) + return merr.WrapErrServiceInternalMsg("operator[%d] is nil", i) } // Validate MapOp if mapOp, ok := op.(*MapOp); ok { if mapOp.function == nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("operator[%d] MapOp has nil function", i)) + return merr.WrapErrServiceInternalMsg("operator[%d] MapOp has nil function", i) } if !mapOp.function.IsRunnable(stage) { return merr.WrapErrParameterInvalidMsg("operator[%d] function %q does not support stage %q", @@ -151,7 +151,7 @@ func (fc *FuncChain) validateOperators(stage string) error { // Validate FilterOp if filterOp, ok := op.(*FilterOp); ok { if filterOp.function == nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("operator[%d] FilterOp has nil function", i)) + return merr.WrapErrServiceInternalMsg("operator[%d] FilterOp has nil function", i) } if !filterOp.function.IsRunnable(stage) { return merr.WrapErrParameterInvalidMsg("operator[%d] filter function %q does not support stage %q", @@ -176,7 +176,7 @@ func (fc *FuncChain) Execute(input *DataFrame) (*DataFrame, error) { // Supports multiple inputs when the first operator is MergeOp. func (fc *FuncChain) ExecuteWithContext(ctx context.Context, inputs ...*DataFrame) (*DataFrame, error) { if len(inputs) == 0 { - return nil, merr.WrapErrParameterInvalidMsg("at least one input is required") + return nil, merr.WrapErrParameterMissingMsg("at least one input is required") } // Validate chain before execution @@ -195,7 +195,7 @@ func (fc *FuncChain) ExecuteWithContext(ctx context.Context, inputs ...*DataFram var err error result, err = mergeOp.ExecuteMulti(funcCtx, inputs) if err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("%s failed: %v", mergeOp.Name(), err)) + return nil, merr.Wrapf(err, "%s failed", mergeOp.Name()) } startIdx = 1 } else { @@ -226,7 +226,7 @@ func (fc *FuncChain) ExecuteWithContext(ctx context.Context, inputs ...*DataFram newResult, err := op.Execute(funcCtx, result) if err != nil { fc.releaseIfOwned(result, inputs) - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("%s failed: %v", op.Name(), err)) + return nil, merr.Wrapf(err, "%s failed", op.Name()) } // Release intermediate results (but not the original inputs) @@ -346,7 +346,7 @@ func (fc *FuncChain) GroupBy(groupByField string, groupSize, limit, offset int64 // chain.GroupByWithScorer("category", 3, 10, 0, GroupScorerAvg) // use average score for group ranking func (fc *FuncChain) GroupByWithScorer(groupByField string, groupSize, limit, offset int64, scorer GroupScorer) *FuncChain { if groupByField == "" { - return fc.addWithError(nil, merr.WrapErrParameterInvalidMsg("groupByField cannot be empty")) + return fc.addWithError(nil, merr.WrapErrParameterMissingMsg("groupByField cannot be empty")) } if groupSize <= 0 { return fc.addWithError(nil, merr.WrapErrParameterInvalidMsg("groupSize must be positive, got %d", groupSize)) diff --git a/internal/util/function/chain/converter.go b/internal/util/function/chain/converter.go index f6946a02c5..6e0b80e07a 100644 --- a/internal/util/function/chain/converter.go +++ b/internal/util/function/chain/converter.go @@ -19,7 +19,6 @@ package chain import ( - "fmt" "strconv" "github.com/apache/arrow/go/v17/arrow" @@ -55,7 +54,7 @@ func ToArrowType(t schemapb.DataType) (arrow.DataType, error) { case schemapb.DataType_String, schemapb.DataType_VarChar, schemapb.DataType_Text: return arrow.BinaryTypes.String, nil default: - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("unsupported data type: %s", t.String())) + return nil, merr.WrapErrServiceInternalMsg("unsupported data type: %s", t.String()) } } @@ -79,7 +78,7 @@ func ToMilvusType(t arrow.DataType) (schemapb.DataType, error) { case arrow.STRING: return schemapb.DataType_VarChar, nil default: - return schemapb.DataType_None, merr.WrapErrServiceInternal(fmt.Sprintf("unsupported arrow type: %s", t.Name())) + return schemapb.DataType_None, merr.WrapErrServiceInternalMsg("unsupported arrow type: %s", t.Name()) } } @@ -171,7 +170,7 @@ func exportChunkedValues[T any, A valueAccessor[T]](col *arrow.Chunked, colName for i := 0; i < len(col.Chunks()); i++ { chunk, ok := col.Chunk(i).(A) if !ok { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("column %s chunk %d type mismatch", colName, i)) + return nil, merr.WrapErrServiceInternalMsg("column %s chunk %d type mismatch", colName, i) } for j := 0; j < chunk.Len(); j++ { data = append(data, chunk.Value(j)) @@ -223,7 +222,7 @@ func exportIntFieldData(col *arrow.Chunked, name string) ([]int32, error) { data = append(data, arr.Value(j)) } default: - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("column %s chunk %d type mismatch, expected int type", name, i)) + return nil, merr.WrapErrServiceInternalMsg("column %s chunk %d type mismatch, expected int type", name, i) } } return data, nil @@ -268,14 +267,14 @@ func FromSearchResultData(resultData *schemapb.SearchResultData, alloc memory.Al // Validate data lengths against totalRows to prevent out-of-bounds panics from malformed input. if ids := resultData.GetIds(); ids != nil && totalRows > 0 { if intIds := ids.GetIntId(); intIds != nil && int64(len(intIds.GetData())) < totalRows { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("ID data length (%d) is less than totalRows (%d)", len(intIds.GetData()), totalRows)) + return nil, merr.WrapErrServiceInternalMsg("ID data length (%d) is less than totalRows (%d)", len(intIds.GetData()), totalRows) } if strIds := ids.GetStrId(); strIds != nil && int64(len(strIds.GetData())) < totalRows { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("ID data length (%d) is less than totalRows (%d)", len(strIds.GetData()), totalRows)) + return nil, merr.WrapErrServiceInternalMsg("ID data length (%d) is less than totalRows (%d)", len(strIds.GetData()), totalRows) } } if scores := resultData.GetScores(); len(scores) > 0 && int64(len(scores)) < totalRows { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("scores length (%d) is less than totalRows (%d)", len(scores), totalRows)) + return nil, merr.WrapErrServiceInternalMsg("scores length (%d) is less than totalRows (%d)", len(scores), totalRows) } // Import ID column ($id) @@ -322,10 +321,10 @@ func FromSearchResultData(resultData *schemapb.SearchResultData, alloc memory.Al continue } if seenFieldIDs[fieldID] { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("duplicate field id %d (fieldName=%q)", fieldID, fieldName)) + return nil, merr.WrapErrServiceInternalMsg("duplicate field id %d (fieldName=%q)", fieldID, fieldName) } if seenFieldNames[fieldName] { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("duplicate field name %q (fieldId=%d conflicts with existing field)", fieldName, fieldID)) + return nil, merr.WrapErrServiceInternalMsg("duplicate field name %q (fieldId=%d conflicts with existing field)", fieldName, fieldID) } seenFieldIDs[fieldID] = true seenFieldNames[fieldName] = true @@ -445,7 +444,7 @@ func importFieldData(builder *DataFrameBuilder, fieldData *schemapb.FieldData, o func importFieldDataWithName(builder *DataFrameBuilder, fieldData *schemapb.FieldData, fieldName string, offsets []int64, alloc memory.Allocator) error { if fieldName == "" { - return merr.WrapErrServiceInternal(fmt.Sprintf("importFieldData: field_name is empty for field_id %d", fieldData.GetFieldId())) + return merr.WrapErrServiceInternalMsg("importFieldData: field_name is empty for field_id %d", fieldData.GetFieldId()) } totalRows := offsets[len(offsets)-1] @@ -453,7 +452,7 @@ func importFieldDataWithName(builder *DataFrameBuilder, fieldData *schemapb.Fiel validData := fieldData.GetValidData() nullable := len(validData) > 0 if nullable && int64(len(validData)) < totalRows { - return merr.WrapErrServiceInternal(fmt.Sprintf("field %s: validData length (%d) is less than totalRows (%d)", fieldName, len(validData), totalRows)) + return merr.WrapErrServiceInternalMsg("field %s: validData length (%d) is less than totalRows (%d)", fieldName, len(validData), totalRows) } getValidSlice := func(chunkIdx int) []bool { @@ -466,7 +465,7 @@ func importFieldDataWithName(builder *DataFrameBuilder, fieldData *schemapb.Fiel // validateLen checks that the extracted data slice has enough elements for totalRows. validateLen := func(dataLen int) error { if int64(dataLen) < totalRows { - return merr.WrapErrServiceInternal(fmt.Sprintf("field %s: data length (%d) is less than totalRows (%d)", fieldName, dataLen, totalRows)) + return merr.WrapErrServiceInternalMsg("field %s: data length (%d) is less than totalRows (%d)", fieldName, dataLen, totalRows) } return nil } @@ -567,7 +566,7 @@ func importFieldDataWithName(builder *DataFrameBuilder, fieldData *schemapb.Fiel chunks = importChunkedBatch(data, offsets, getValidSlice, array.NewStringBuilder, alloc) default: - return merr.WrapErrServiceInternal(fmt.Sprintf("unsupported field type: %s", fieldData.GetType().String())) + return merr.WrapErrServiceInternalMsg("unsupported field type: %s", fieldData.GetType().String()) } builder.SetFieldType(fieldName, fieldData.GetType()) @@ -583,11 +582,11 @@ func importFieldDataWithName(builder *DataFrameBuilder, fieldData *schemapb.Fiel func getScalarBoolData(fieldData *schemapb.FieldData, fieldName string) ([]bool, error) { scalars := fieldData.GetScalars() if scalars == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("field %s: scalars is nil", fieldName)) + return nil, merr.WrapErrServiceInternalMsg("field %s: scalars is nil", fieldName) } boolData := scalars.GetBoolData() if boolData == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("field %s: bool data is nil", fieldName)) + return nil, merr.WrapErrServiceInternalMsg("field %s: bool data is nil", fieldName) } return boolData.GetData(), nil } @@ -595,11 +594,11 @@ func getScalarBoolData(fieldData *schemapb.FieldData, fieldName string) ([]bool, func getScalarIntData(fieldData *schemapb.FieldData, fieldName string) ([]int32, error) { scalars := fieldData.GetScalars() if scalars == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("field %s: scalars is nil", fieldName)) + return nil, merr.WrapErrServiceInternalMsg("field %s: scalars is nil", fieldName) } intData := scalars.GetIntData() if intData == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("field %s: int data is nil", fieldName)) + return nil, merr.WrapErrServiceInternalMsg("field %s: int data is nil", fieldName) } return intData.GetData(), nil } @@ -607,11 +606,11 @@ func getScalarIntData(fieldData *schemapb.FieldData, fieldName string) ([]int32, func getScalarLongData(fieldData *schemapb.FieldData, fieldName string) ([]int64, error) { scalars := fieldData.GetScalars() if scalars == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("field %s: scalars is nil", fieldName)) + return nil, merr.WrapErrServiceInternalMsg("field %s: scalars is nil", fieldName) } longData := scalars.GetLongData() if longData == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("field %s: long data is nil", fieldName)) + return nil, merr.WrapErrServiceInternalMsg("field %s: long data is nil", fieldName) } return longData.GetData(), nil } @@ -619,11 +618,11 @@ func getScalarLongData(fieldData *schemapb.FieldData, fieldName string) ([]int64 func getScalarTimestamptzData(fieldData *schemapb.FieldData, fieldName string) ([]int64, error) { scalars := fieldData.GetScalars() if scalars == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("field %s: scalars is nil", fieldName)) + return nil, merr.WrapErrServiceInternalMsg("field %s: scalars is nil", fieldName) } timestamptzData := scalars.GetTimestamptzData() if timestamptzData == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("field %s: timestamptz data is nil", fieldName)) + return nil, merr.WrapErrServiceInternalMsg("field %s: timestamptz data is nil", fieldName) } return timestamptzData.GetData(), nil } @@ -631,11 +630,11 @@ func getScalarTimestamptzData(fieldData *schemapb.FieldData, fieldName string) ( func getScalarFloatData(fieldData *schemapb.FieldData, fieldName string) ([]float32, error) { scalars := fieldData.GetScalars() if scalars == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("field %s: scalars is nil", fieldName)) + return nil, merr.WrapErrServiceInternalMsg("field %s: scalars is nil", fieldName) } floatData := scalars.GetFloatData() if floatData == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("field %s: float data is nil", fieldName)) + return nil, merr.WrapErrServiceInternalMsg("field %s: float data is nil", fieldName) } return floatData.GetData(), nil } @@ -643,11 +642,11 @@ func getScalarFloatData(fieldData *schemapb.FieldData, fieldName string) ([]floa func getScalarDoubleData(fieldData *schemapb.FieldData, fieldName string) ([]float64, error) { scalars := fieldData.GetScalars() if scalars == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("field %s: scalars is nil", fieldName)) + return nil, merr.WrapErrServiceInternalMsg("field %s: scalars is nil", fieldName) } doubleData := scalars.GetDoubleData() if doubleData == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("field %s: double data is nil", fieldName)) + return nil, merr.WrapErrServiceInternalMsg("field %s: double data is nil", fieldName) } return doubleData.GetData(), nil } @@ -655,11 +654,11 @@ func getScalarDoubleData(fieldData *schemapb.FieldData, fieldName string) ([]flo func getScalarStringData(fieldData *schemapb.FieldData, fieldName string) ([]string, error) { scalars := fieldData.GetScalars() if scalars == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("field %s: scalars is nil", fieldName)) + return nil, merr.WrapErrServiceInternalMsg("field %s: scalars is nil", fieldName) } stringData := scalars.GetStringData() if stringData == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("field %s: string data is nil", fieldName)) + return nil, merr.WrapErrServiceInternalMsg("field %s: string data is nil", fieldName) } return stringData.GetData(), nil } @@ -770,7 +769,7 @@ func ToSearchResultDataWithOptions(df *DataFrame, opts *ExportOptions) (*schemap func exportIDs(df *DataFrame) (*schemapb.IDs, error) { col := df.Column(types.IDFieldName) if col == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("exportIDs: column %s not found", types.IDFieldName)) + return nil, merr.WrapErrServiceInternalMsg("exportIDs: column %s not found", types.IDFieldName) } dataType, _ := df.FieldType(types.IDFieldName) @@ -778,7 +777,7 @@ func exportIDs(df *DataFrame) (*schemapb.IDs, error) { case schemapb.DataType_Int64: data, err := exportChunkedValues[int64, *array.Int64](col, types.IDFieldName) if err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("exportIDs: %v", err)) + return nil, merr.WrapErrServiceInternalMsg("exportIDs: %v", err) } return &schemapb.IDs{ IdField: &schemapb.IDs_IntId{ @@ -789,7 +788,7 @@ func exportIDs(df *DataFrame) (*schemapb.IDs, error) { case schemapb.DataType_VarChar, schemapb.DataType_String: data, err := exportChunkedValues[string, *array.String](col, types.IDFieldName) if err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("exportIDs: %v", err)) + return nil, merr.WrapErrServiceInternalMsg("exportIDs: %v", err) } return &schemapb.IDs{ IdField: &schemapb.IDs_StrId{ @@ -798,7 +797,7 @@ func exportIDs(df *DataFrame) (*schemapb.IDs, error) { }, nil default: - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("exportIDs: unsupported ID type: %s", dataType.String())) + return nil, merr.WrapErrServiceInternalMsg("exportIDs: unsupported ID type: %s", dataType.String()) } } @@ -806,12 +805,12 @@ func exportIDs(df *DataFrame) (*schemapb.IDs, error) { func exportScores(df *DataFrame) ([]float32, error) { col := df.Column(types.ScoreFieldName) if col == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("exportScores: column %s not found", types.ScoreFieldName)) + return nil, merr.WrapErrServiceInternalMsg("exportScores: column %s not found", types.ScoreFieldName) } data, err := exportChunkedValues[float32, *array.Float32](col, types.ScoreFieldName) if err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("exportScores: %v", err)) + return nil, merr.WrapErrServiceInternalMsg("exportScores: %v", err) } return data, nil } @@ -820,7 +819,7 @@ func exportScores(df *DataFrame) ([]float32, error) { func exportFieldData(df *DataFrame, name string) (*schemapb.FieldData, error) { col := df.Column(name) if col == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("exportFieldData: column %s not found", name)) + return nil, merr.WrapErrServiceInternalMsg("exportFieldData: column %s not found", name) } dataType, _ := df.FieldType(name) @@ -924,11 +923,11 @@ func exportFieldData(df *DataFrame, name string) (*schemapb.FieldData, error) { } default: - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("exportFieldData: unsupported type %s for column %s", dataType.String(), name)) + return nil, merr.WrapErrServiceInternalMsg("exportFieldData: unsupported type %s for column %s", dataType.String(), name) } if err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("exportFieldData: %v", err)) + return nil, merr.WrapErrServiceInternalMsg("exportFieldData: %v", err) } // Export validity data for nullable fields @@ -954,7 +953,7 @@ func exportGeometryFieldData(col *arrow.Chunked, name string) ([][]byte, error) data = append(data, append([]byte(nil), chunk.Value(j)...)) } default: - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("column %s chunk %d type mismatch", name, i)) + return nil, merr.WrapErrServiceInternalMsg("column %s chunk %d type mismatch", name, i) } } return data, nil diff --git a/internal/util/function/chain/dataframe.go b/internal/util/function/chain/dataframe.go index 7a85fc3527..4605c7f6d9 100644 --- a/internal/util/function/chain/dataframe.go +++ b/internal/util/function/chain/dataframe.go @@ -19,8 +19,6 @@ package chain import ( - "fmt" - "github.com/apache/arrow/go/v17/arrow" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" @@ -286,11 +284,11 @@ func (b *DataFrameBuilder) addColumn(name string, col *arrow.Chunked) error { if col != nil { col.Release() } - return merr.WrapErrServiceInternal(fmt.Sprintf("column %s already exists", name)) + return merr.WrapErrServiceInternalMsg("column %s already exists", name) } if col == nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("column %s is nil", name)) + return merr.WrapErrServiceInternalMsg("column %s is nil", name) } b.addColumnUnchecked(name, col) @@ -327,7 +325,7 @@ func (b *DataFrameBuilder) AddColumns(names []string, cols []*arrow.Chunked) err if len(names) != len(cols) { releaseAll() - return merr.WrapErrServiceInternal(fmt.Sprintf("names count (%d) != cols count (%d)", len(names), len(cols))) + return merr.WrapErrServiceInternalMsg("names count (%d) != cols count (%d)", len(names), len(cols)) } // Validate all before adding any @@ -335,16 +333,16 @@ func (b *DataFrameBuilder) AddColumns(names []string, cols []*arrow.Chunked) err for i, name := range names { if _, exists := b.result.nameIndex[name]; exists { releaseAll() - return merr.WrapErrServiceInternal(fmt.Sprintf("column %s already exists", name)) + return merr.WrapErrServiceInternalMsg("column %s already exists", name) } if seen[name] { releaseAll() - return merr.WrapErrServiceInternal(fmt.Sprintf("duplicate column name %s in batch", name)) + return merr.WrapErrServiceInternalMsg("duplicate column name %s in batch", name) } seen[name] = true if cols[i] == nil { releaseAll() - return merr.WrapErrServiceInternal(fmt.Sprintf("column %s is nil", name)) + return merr.WrapErrServiceInternalMsg("column %s is nil", name) } } @@ -364,7 +362,7 @@ func (b *DataFrameBuilder) AddColumnFrom(source *DataFrame, colName string) erro col := source.Column(colName) if col == nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("column %s not found in source", colName)) + return merr.WrapErrServiceInternalMsg("column %s not found in source", colName) } col.Retain() diff --git a/internal/util/function/chain/expr/base_expr.go b/internal/util/function/chain/expr/base_expr.go index 33bb1ed55d..07a6e420d5 100644 --- a/internal/util/function/chain/expr/base_expr.go +++ b/internal/util/function/chain/expr/base_expr.go @@ -19,8 +19,6 @@ package expr import ( - "fmt" - "github.com/apache/arrow/go/v17/arrow" "github.com/apache/arrow/go/v17/arrow/array" @@ -192,6 +190,6 @@ func GetNumericValue(arr arrow.Array, idx int) (float64, error) { case *array.Float64: return a.Value(idx), nil default: - return 0, merr.WrapErrServiceInternal(fmt.Sprintf("unsupported input column type %T, expected numeric type", arr)) + return 0, merr.WrapErrServiceInternalMsg("unsupported input column type %T, expected numeric type", arr) } } diff --git a/internal/util/function/chain/expr/decay_expr.go b/internal/util/function/chain/expr/decay_expr.go index f4b3aeb876..a8f4db3755 100644 --- a/internal/util/function/chain/expr/decay_expr.go +++ b/internal/util/function/chain/expr/decay_expr.go @@ -19,7 +19,6 @@ package expr import ( - "fmt" "math" "github.com/apache/arrow/go/v17/arrow" @@ -215,7 +214,7 @@ func (d *DecayExpr) OutputDataTypes() []arrow.DataType { // returns: decay factor column (Float32, values in [0, 1]) func (d *DecayExpr) Execute(ctx *types.FuncContext, inputs []*arrow.Chunked) ([]*arrow.Chunked, error) { if len(inputs) != 1 { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("decay: expected 1 input column, got %d", len(inputs))) + return nil, merr.WrapErrServiceInternalMsg("decay: expected 1 input column, got %d", len(inputs)) } inputCol := inputs[0] @@ -262,7 +261,7 @@ func (d *DecayExpr) processChunk(ctx *types.FuncContext, inputChunk arrow.Array) distance, err := GetNumericValue(inputChunk, i) if err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("decay: %v", err)) + return nil, merr.WrapErrServiceInternalMsg("decay: %v", err) } decayScore := d.decayFunc(d.origin, d.scale, d.decay, d.offset, distance) diff --git a/internal/util/function/chain/expr/rerank_model_expr.go b/internal/util/function/chain/expr/rerank_model_expr.go index 7984530c32..bd877b6a42 100644 --- a/internal/util/function/chain/expr/rerank_model_expr.go +++ b/internal/util/function/chain/expr/rerank_model_expr.go @@ -20,7 +20,6 @@ package expr import ( "context" - "fmt" "github.com/apache/arrow/go/v17/arrow" "github.com/apache/arrow/go/v17/arrow/array" @@ -51,7 +50,7 @@ func NewRerankModelExpr(provider rerank.ModelProvider, queries []string) (*Reran return nil, merr.WrapErrServiceInternal("model: provider is nil") } if len(queries) == 0 { - return nil, merr.WrapErrParameterInvalidMsg("model: queries must not be empty") + return nil, merr.WrapErrParameterMissingMsg("model: queries must not be empty") } return &RerankModelExpr{ BaseExpr: *NewBaseExpr("model", []string{types.StageL2Rerank}), @@ -69,14 +68,14 @@ func (m *RerankModelExpr) OutputDataTypes() []arrow.DataType { // Execute calls the external rerank service for each chunk (NQ) and returns model scores. func (m *RerankModelExpr) Execute(ctx *types.FuncContext, inputs []*arrow.Chunked) ([]*arrow.Chunked, error) { if len(inputs) != 1 { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("model: expected 1 input column (text), got %d", len(inputs))) + return nil, merr.WrapErrServiceInternalMsg("model: expected 1 input column (text), got %d", len(inputs)) } textCol := inputs[0] numChunks := len(textCol.Chunks()) if len(m.queries) != numChunks { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("model: queries count (%d) != nq count (%d)", len(m.queries), numChunks)) + return nil, merr.WrapErrServiceInternalMsg("model: queries count (%d) != nq count (%d)", len(m.queries), numChunks) } scoreChunks := make([]arrow.Array, numChunks) @@ -107,7 +106,7 @@ func (m *RerankModelExpr) Execute(ctx *types.FuncContext, inputs []*arrow.Chunke func (m *RerankModelExpr) processChunk(ctx *types.FuncContext, chunk arrow.Array, query string) (arrow.Array, error) { stringArr, ok := chunk.(*array.String) if !ok { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("model: input column must be String/VarChar, got %T", chunk)) + return nil, merr.WrapErrServiceInternalMsg("model: input column must be String/VarChar, got %T", chunk) } n := stringArr.Len() @@ -159,7 +158,7 @@ func (m *RerankModelExpr) rerankBatch(ctx context.Context, query string, texts [ return nil, err } if len(batchScores) != end-i { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("model: rerank service returned %d scores for %d docs", len(batchScores), end-i)) + return nil, merr.WrapErrServiceInternalMsg("model: rerank service returned %d scores for %d docs", len(batchScores), end-i) } scores = append(scores, batchScores...) } diff --git a/internal/util/function/chain/expr/round_decimal_expr.go b/internal/util/function/chain/expr/round_decimal_expr.go index 32f18d0b9b..65bbc06ed5 100644 --- a/internal/util/function/chain/expr/round_decimal_expr.go +++ b/internal/util/function/chain/expr/round_decimal_expr.go @@ -19,7 +19,6 @@ package expr import ( - "fmt" "math" "github.com/apache/arrow/go/v17/arrow" @@ -65,7 +64,7 @@ func (e *RoundDecimalExpr) OutputDataTypes() []arrow.DataType { func (e *RoundDecimalExpr) Execute(ctx *types.FuncContext, inputs []*arrow.Chunked) ([]*arrow.Chunked, error) { if len(inputs) != 1 { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("round_decimal: expected 1 input column, got %d", len(inputs))) + return nil, merr.WrapErrServiceInternalMsg("round_decimal: expected 1 input column, got %d", len(inputs)) } scoreCol := inputs[0] @@ -78,7 +77,7 @@ func (e *RoundDecimalExpr) Execute(ctx *types.FuncContext, inputs []*arrow.Chunk for i := 0; i < chunkIdx; i++ { newChunks[i].Release() } - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("round_decimal: input chunk %d must be Float32, got %T", chunkIdx, scoreCol.Chunk(chunkIdx))) + return nil, merr.WrapErrServiceInternalMsg("round_decimal: input chunk %d must be Float32, got %T", chunkIdx, scoreCol.Chunk(chunkIdx)) } builder := array.NewFloat32Builder(ctx.Pool()) diff --git a/internal/util/function/chain/expr/score_combine_expr.go b/internal/util/function/chain/expr/score_combine_expr.go index 34a03d58f8..102016e42b 100644 --- a/internal/util/function/chain/expr/score_combine_expr.go +++ b/internal/util/function/chain/expr/score_combine_expr.go @@ -19,7 +19,6 @@ package expr import ( - "fmt" "math" "github.com/apache/arrow/go/v17/arrow" @@ -143,17 +142,17 @@ func (s *ScoreCombineExpr) OutputDataTypes() []arrow.DataType { // Execute executes the score combine function on input columns and returns output columns. func (s *ScoreCombineExpr) Execute(ctx *types.FuncContext, inputs []*arrow.Chunked) ([]*arrow.Chunked, error) { if len(inputs) < 2 { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("score_combine: expected at least 2 input columns, got %d", len(inputs))) + return nil, merr.WrapErrServiceInternalMsg("score_combine: expected at least 2 input columns, got %d", len(inputs)) } if s.mode == ModeWeighted && len(s.weights) != len(inputs) { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("score_combine: weighted mode requires %d weights, got %d", len(inputs), len(s.weights))) + return nil, merr.WrapErrServiceInternalMsg("score_combine: weighted mode requires %d weights, got %d", len(inputs), len(s.weights)) } numChunks := len(inputs[0].Chunks()) for idx := 1; idx < len(inputs); idx++ { if len(inputs[idx].Chunks()) != numChunks { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("score_combine: input 0 has %d chunks but input %d has %d chunks", numChunks, idx, len(inputs[idx].Chunks()))) + return nil, merr.WrapErrServiceInternalMsg("score_combine: input 0 has %d chunks but input %d has %d chunks", numChunks, idx, len(inputs[idx].Chunks())) } } resultChunks := make([]arrow.Array, numChunks) @@ -212,7 +211,7 @@ func (s *ScoreCombineExpr) processChunk(ctx *types.FuncContext, inputs []*arrow. for colIdx, input := range inputs { val, err := GetNumericValue(input.Chunk(chunkIdx), rowIdx) if err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("score_combine: column %d: %v", colIdx, err)) + return nil, merr.WrapErrServiceInternalMsg("score_combine: column %d: %v", colIdx, err) } values[colIdx] = val } diff --git a/internal/util/function/chain/operator_base.go b/internal/util/function/chain/operator_base.go index 68afb13753..c7bc1a67ff 100644 --- a/internal/util/function/chain/operator_base.go +++ b/internal/util/function/chain/operator_base.go @@ -20,7 +20,6 @@ package chain import ( "cmp" - "fmt" "github.com/apache/arrow/go/v17/arrow" "github.com/apache/arrow/go/v17/arrow/array" @@ -54,7 +53,7 @@ func pickByIndices[T any, A typedArray[T], B typedBuilder[T]](arr A, builder B, arrLen := arr.Len() for _, idx := range indices { if idx < 0 || idx >= arrLen { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("index out of bounds: %d (array length: %d)", idx, arrLen)) + return nil, merr.WrapErrServiceInternalMsg("index out of bounds: %d (array length: %d)", idx, arrLen) } if arr.IsNull(idx) { builder.AppendNull() @@ -102,7 +101,7 @@ func dispatchPickByIndices(pool memory.Allocator, data arrow.Array, indices []in case *array.String: return pickByIndices(arr, array.NewStringBuilder(pool), indices) default: - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("unsupported array type %T", data)) + return nil, merr.WrapErrServiceInternalMsg("unsupported array type %T", data) } } @@ -126,7 +125,7 @@ func (o *BaseOp) ReadInputColumns(opName string, df *DataFrame) ([]*arrow.Chunke for i, name := range o.inputs { col := df.Column(name) if col == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("%s: column %q not found", opName, name)) + return nil, merr.WrapErrServiceInternalMsg("%s: column %q not found", opName, name) } cols[i] = col } diff --git a/internal/util/function/chain/operator_filter.go b/internal/util/function/chain/operator_filter.go index 7f53046a50..58755f70e5 100644 --- a/internal/util/function/chain/operator_filter.go +++ b/internal/util/function/chain/operator_filter.go @@ -52,10 +52,10 @@ func NewFilterOp(function types.FunctionExpr, inputCols []string) (*FilterOp, er outputTypes := function.OutputDataTypes() if outputTypes != nil { if len(outputTypes) != 1 { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("filter_op: function must return exactly 1 output, got %d", len(outputTypes))) + return nil, merr.WrapErrServiceInternalMsg("filter_op: function must return exactly 1 output, got %d", len(outputTypes)) } if outputTypes[0].ID() != arrow.BOOL { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("filter_op: function must return boolean type, got %s", outputTypes[0].Name())) + return nil, merr.WrapErrServiceInternalMsg("filter_op: function must return boolean type, got %s", outputTypes[0].Name()) } } @@ -82,7 +82,7 @@ func (o *FilterOp) Execute(ctx *types.FuncContext, input *DataFrame) (*DataFrame // Execute FunctionExpr to get boolean result outputs, err := o.function.Execute(ctx, inputs) if err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("filter_op: function execution failed: %v", err)) + return nil, merr.WrapErrFunctionFailed(err, "filter_op: function execution failed") } // Validate output at runtime (especially important for dynamic output types) @@ -92,7 +92,7 @@ func (o *FilterOp) Execute(ctx *types.FuncContext, input *DataFrame) (*DataFrame out.Release() } } - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("filter_op: function must return exactly 1 output, got %d", len(outputs))) + return nil, merr.WrapErrFunctionFailedMsg("filter_op: function must return exactly 1 output, got %d", len(outputs)) } filterCol := outputs[0] @@ -100,7 +100,7 @@ func (o *FilterOp) Execute(ctx *types.FuncContext, input *DataFrame) (*DataFrame // Validate the output is boolean type if filterCol.DataType().ID() != arrow.BOOL { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("filter_op: function must return boolean type, got %s", filterCol.DataType().Name())) + return nil, merr.WrapErrFunctionFailedMsg("filter_op: function must return boolean type, got %s", filterCol.DataType().Name()) } // Create builder for result DataFrame @@ -113,7 +113,7 @@ func (o *FilterOp) Execute(ctx *types.FuncContext, input *DataFrame) (*DataFrame for chunkIdx := range input.NumChunks() { boolChunk, ok := filterCol.Chunk(chunkIdx).(*array.Boolean) if !ok { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("filter_op: chunk %d is not a boolean array", chunkIdx)) + return nil, merr.WrapErrFunctionFailedMsg("filter_op: chunk %d is not a boolean array", chunkIdx) } filterChunks[chunkIdx] = boolChunk @@ -143,7 +143,7 @@ func (o *FilterOp) Execute(ctx *types.FuncContext, input *DataFrame) (*DataFrame filtered, err := filterArray(ctx.Pool(), dataChunk, filterChunks[chunkIdx]) if err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("filter_op: column %s: %v", colName, err)) + return nil, merr.WrapErrFunctionFailed(err, "filter_op: column %s", colName) } collector.Set(colName, chunkIdx, filtered) } @@ -175,14 +175,14 @@ func filterArray(pool memory.Allocator, data arrow.Array, mask *array.Boolean) ( // NewFilterOpFromRepr creates a FilterOp from an OperatorRepr. func NewFilterOpFromRepr(repr *OperatorRepr) (Operator, error) { if repr.Function == nil { - return nil, merr.WrapErrParameterInvalidMsg("filter_op: function is required") + return nil, merr.WrapErrParameterMissingMsg("filter_op: function is required") } fn, err := FunctionFromRepr(repr.Function) if err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("filter function: %v", err)) + return nil, merr.WrapErrParameterInvalidMsg("filter function: %v", err) } if len(repr.Inputs) == 0 { - return nil, merr.WrapErrParameterInvalidMsg("filter_op: inputs is required") + return nil, merr.WrapErrParameterMissingMsg("filter_op: inputs is required") } return NewFilterOp(fn, repr.Inputs) } diff --git a/internal/util/function/chain/operator_group_by.go b/internal/util/function/chain/operator_group_by.go index 5deb27aafb..5a8569e26d 100644 --- a/internal/util/function/chain/operator_group_by.go +++ b/internal/util/function/chain/operator_group_by.go @@ -172,11 +172,11 @@ func (o *GroupByOp) Execute(ctx *types.FuncContext, input *DataFrame) (*DataFram // Validate columns exist groupCol := input.Column(o.groupByField) if groupCol == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("group_by_op: column %q not found", o.groupByField)) + return nil, merr.WrapErrServiceInternalMsg("group_by_op: column %q not found", o.groupByField) } scoreCol := input.Column(types.ScoreFieldName) if scoreCol == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("group_by_op: column %q not found", types.ScoreFieldName)) + return nil, merr.WrapErrServiceInternalMsg("group_by_op: column %q not found", types.ScoreFieldName) } numChunks := input.NumChunks() @@ -218,7 +218,7 @@ func (o *GroupByOp) Execute(ctx *types.FuncContext, input *DataFrame) (*DataFram dataChunk := col.Chunk(chunkIdx) reordered, err := dispatchPickByIndices(ctx.Pool(), dataChunk, result.indices) if err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("group_by_op: reorder column %s: %v", colName, err)) + return nil, merr.WrapErrServiceInternalMsg("group_by_op: reorder column %s: %v", colName, err) } collector.Set(colName, chunkIdx, reordered) } @@ -259,13 +259,13 @@ func (o *GroupByOp) processChunk(ctx *types.FuncContext, input *DataFrame, chunk scoreCol := input.Column(types.ScoreFieldName) idCol := input.Column(types.IDFieldName) if idCol == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("group_by_op: column %q not found", types.IDFieldName)) + return nil, merr.WrapErrServiceInternalMsg("group_by_op: column %q not found", types.IDFieldName) } groupChunk := groupCol.Chunk(chunkIdx) scoreChunk, ok := scoreCol.Chunk(chunkIdx).(*array.Float32) if !ok { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("group_by_op: score column chunk %d is not Float32", chunkIdx)) + return nil, merr.WrapErrServiceInternalMsg("group_by_op: score column chunk %d is not Float32", chunkIdx) } idChunk := idCol.Chunk(chunkIdx) chunkLen := groupChunk.Len() @@ -457,12 +457,12 @@ func (o *GroupByOp) computeGroupScore(g *group) { func NewGroupByOpFromRepr(repr *OperatorRepr) (Operator, error) { field, ok := repr.Params["field"].(string) if !ok || field == "" { - return nil, merr.WrapErrParameterInvalidMsg("group_by_op: field is required") + return nil, merr.WrapErrParameterMissingMsg("group_by_op: field is required") } groupSize, err := getInt64Param(repr.Params, "group_size") if err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("group_by_op: %v", err)) + return nil, merr.Wrap(err, "group_by_op") } if groupSize <= 0 { return nil, merr.WrapErrParameterInvalidMsg("group_by_op: group_size must be positive") @@ -470,7 +470,7 @@ func NewGroupByOpFromRepr(repr *OperatorRepr) (Operator, error) { limit, err := getInt64Param(repr.Params, "limit") if err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("group_by_op: %v", err)) + return nil, merr.Wrap(err, "group_by_op") } if limit <= 0 { return nil, merr.WrapErrParameterInvalidMsg("group_by_op: limit must be positive") @@ -480,7 +480,7 @@ func NewGroupByOpFromRepr(repr *OperatorRepr) (Operator, error) { if _, ok := repr.Params["offset"]; ok { offset, err = getInt64Param(repr.Params, "offset") if err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("group_by_op: %v", err)) + return nil, merr.Wrap(err, "group_by_op") } if offset < 0 { return nil, merr.WrapErrParameterInvalidMsg("group_by_op: offset must be non-negative") @@ -491,7 +491,7 @@ func NewGroupByOpFromRepr(repr *OperatorRepr) (Operator, error) { if scorerStr, ok := repr.Params["scorer"].(string); ok { scorer = GroupScorer(scorerStr) if err := ValidateGroupScorer(scorer); err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("group_by_op: %v", err)) + return nil, merr.Wrap(err, "group_by_op") } } @@ -502,7 +502,7 @@ func NewGroupByOpFromRepr(repr *OperatorRepr) (Operator, error) { func getInt64Param(params map[string]interface{}, key string) (int64, error) { val, ok := params[key] if !ok { - return 0, merr.WrapErrParameterInvalidMsg("%s is required", key) + return 0, merr.WrapErrParameterMissingMsg("%s is required", key) } switch v := val.(type) { case int64: diff --git a/internal/util/function/chain/operator_limit.go b/internal/util/function/chain/operator_limit.go index cb2d9f406c..2115d38014 100644 --- a/internal/util/function/chain/operator_limit.go +++ b/internal/util/function/chain/operator_limit.go @@ -76,7 +76,7 @@ func (o *LimitOp) Execute(ctx *types.FuncContext, input *DataFrame) (*DataFrame, dataChunk := col.Chunk(chunkIdx) sliced, err := sliceArray(dataChunk, int(start), int(end)) if err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("limit_op: column %s: %v", colName, err)) + return nil, merr.WrapErrServiceInternalMsg("limit_op: column %s: %v", colName, err) } collector.Set(colName, chunkIdx, sliced) } @@ -90,7 +90,7 @@ func (o *LimitOp) Execute(ctx *types.FuncContext, input *DataFrame) (*DataFrame, for _, colName := range colNames { if err := builder.AddColumnFromChunks(colName, collector.Consume(colName)); err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("limit_op: %v", err)) + return nil, merr.WrapErrServiceInternalMsg("limit_op: %v", err) } builder.CopyFieldMetadata(input, colName) } @@ -109,7 +109,7 @@ func (o *LimitOp) String() string { func NewLimitOpFromRepr(repr *OperatorRepr) (Operator, error) { limitVal, ok := repr.Params["limit"] if !ok { - return nil, merr.WrapErrParameterInvalidMsg("limit_op: limit is required") + return nil, merr.WrapErrParameterMissingMsg("limit_op: limit is required") } var limit int64 switch v := limitVal.(type) { diff --git a/internal/util/function/chain/operator_map.go b/internal/util/function/chain/operator_map.go index dbbe0d2a54..258a553949 100644 --- a/internal/util/function/chain/operator_map.go +++ b/internal/util/function/chain/operator_map.go @@ -46,8 +46,8 @@ func NewMapOp(function types.FunctionExpr, inputCols, outputCols []string) (*Map // Skip validation if OutputDataTypes() returns nil (dynamic output types) outputTypes := function.OutputDataTypes() if outputTypes != nil && len(outputCols) != len(outputTypes) { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("map_op: output columns count (%d) must match function output types count (%d)", - len(outputCols), len(outputTypes))) + return nil, merr.WrapErrServiceInternalMsg("map_op: output columns count (%d) must match function output types count (%d)", + len(outputCols), len(outputTypes)) } return &MapOp{ @@ -90,8 +90,8 @@ func (o *MapOp) Execute(ctx *types.FuncContext, input *DataFrame) (*DataFrame, e out.Release() } } - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("map_op: function returned %d outputs, expected %d", - len(outputs), len(o.outputs))) + return nil, merr.WrapErrServiceInternalMsg("map_op: function returned %d outputs, expected %d", + len(outputs), len(o.outputs)) } // 4. Create builder @@ -124,7 +124,7 @@ func (o *MapOp) Execute(ctx *types.FuncContext, input *DataFrame) (*DataFrame, e // 6. Add all output columns at once if err := builder.AddColumns(o.outputs, outputs); err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("map_op: %v", err)) + return nil, merr.WrapErrServiceInternalMsg("map_op: %v", err) } return builder.Build(), nil @@ -144,7 +144,7 @@ func NewMapOpFromRepr(repr *OperatorRepr) (Operator, error) { } fn, err := FunctionFromRepr(repr.Function) if err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("map function: %v", err)) + return nil, merr.WrapErrParameterInvalidMsg("map function: %v", err) } if len(repr.Inputs) == 0 { return nil, merr.WrapErrParameterInvalidMsg("map operator requires inputs") diff --git a/internal/util/function/chain/operator_merge.go b/internal/util/function/chain/operator_merge.go index 7b8dbcf7da..5f6724e951 100644 --- a/internal/util/function/chain/operator_merge.go +++ b/internal/util/function/chain/operator_merge.go @@ -192,19 +192,19 @@ func (op *MergeOp) ExecuteMulti(ctx *types.FuncContext, inputs []*DataFrame) (*D numChunks := inputs[0].NumChunks() for i, df := range inputs { if df.NumChunks() != numChunks { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("merge_op: input[%d] has %d chunks, expected %d", i, df.NumChunks(), numChunks)) + return nil, merr.WrapErrFunctionFailedMsg("merge_op: input[%d] has %d chunks, expected %d", i, df.NumChunks(), numChunks) } } // Validate scoreNormFuncs count matches inputs count (when present) if len(op.scoreNormFuncs) > 0 && len(op.scoreNormFuncs) != len(inputs) { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("merge_op: scoreNormFuncs count %d != inputs count %d", len(op.scoreNormFuncs), len(inputs))) + return nil, merr.WrapErrServiceInternalMsg("merge_op: scoreNormFuncs count %d != inputs count %d", len(op.scoreNormFuncs), len(inputs)) } // Validate weights for weighted strategy if op.strategy == MergeStrategyWeighted { if len(op.weights) != len(inputs) { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("merge_op: weights count %d != inputs count %d", len(op.weights), len(inputs))) + return nil, merr.WrapErrServiceInternalMsg("merge_op: weights count %d != inputs count %d", len(op.weights), len(inputs)) } } @@ -221,7 +221,7 @@ func (op *MergeOp) ExecuteMulti(ctx *types.FuncContext, inputs []*DataFrame) (*D case MergeStrategyAvg: return op.mergeScoreCombine(ctx, inputs, avgMergeFunc) default: - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("merge_op: unsupported strategy %s", op.strategy)) + return nil, merr.WrapErrServiceInternalMsg("merge_op: unsupported strategy %s", op.strategy) } } @@ -324,7 +324,7 @@ func (op *MergeOp) collectRRFScores(inputs []*DataFrame, chunkIdx int) (map[any] for inputIdx, df := range inputs { idCol := df.Column(types.IDFieldName) if idCol == nil { - return nil, nil, merr.WrapErrServiceInternal(fmt.Sprintf("merge_op: input[%d] missing %s column", inputIdx, types.IDFieldName)) + return nil, nil, merr.WrapErrFunctionFailedMsg("merge_op: input[%d] missing %s column", inputIdx, types.IDFieldName) } idChunk := idCol.Chunk(chunkIdx) @@ -363,13 +363,13 @@ func (op *MergeOp) collectWeightedScores(inputs []*DataFrame, chunkIdx int) (map idCol := df.Column(types.IDFieldName) scoreCol := df.Column(types.ScoreFieldName) if idCol == nil || scoreCol == nil { - return nil, nil, merr.WrapErrServiceInternal(fmt.Sprintf("merge_op: input[%d] missing ID or score column", inputIdx)) + return nil, nil, merr.WrapErrFunctionFailedMsg("merge_op: input[%d] missing ID or score column", inputIdx) } idChunk := idCol.Chunk(chunkIdx) scoreChunk, ok := scoreCol.Chunk(chunkIdx).(*array.Float32) if !ok { - return nil, nil, merr.WrapErrServiceInternal(fmt.Sprintf("merge_op: input[%d] score column chunk %d is not Float32", inputIdx, chunkIdx)) + return nil, nil, merr.WrapErrFunctionFailedMsg("merge_op: input[%d] score column chunk %d is not Float32", inputIdx, chunkIdx) } weight := float32(op.weights[inputIdx]) @@ -449,13 +449,13 @@ func (op *MergeOp) collectCombinedScores(inputs []*DataFrame, chunkIdx int, merg idCol := df.Column(types.IDFieldName) scoreCol := df.Column(types.ScoreFieldName) if idCol == nil || scoreCol == nil { - return nil, nil, nil, merr.WrapErrServiceInternal(fmt.Sprintf("merge_op: input[%d] missing ID or score column", inputIdx)) + return nil, nil, nil, merr.WrapErrFunctionFailedMsg("merge_op: input[%d] missing ID or score column", inputIdx) } idChunk := idCol.Chunk(chunkIdx) scoreChunk, ok := scoreCol.Chunk(chunkIdx).(*array.Float32) if !ok { - return nil, nil, nil, merr.WrapErrServiceInternal(fmt.Sprintf("merge_op: input[%d] score column chunk %d is not Float32", inputIdx, chunkIdx)) + return nil, nil, nil, merr.WrapErrFunctionFailedMsg("merge_op: input[%d] score column chunk %d is not Float32", inputIdx, chunkIdx) } normFunc := op.scoreNormFunc(inputIdx) @@ -727,7 +727,7 @@ func (op *MergeOp) buildResultArrays(ctx *types.FuncContext, ids []any, scores [ case string: return op.buildStringResults(ctx, ids, scores) default: - return nil, nil, merr.WrapErrServiceInternal(fmt.Sprintf("merge_op: unsupported ID type %T", ids[0])) + return nil, nil, merr.WrapErrFunctionFailedMsg("merge_op: unsupported ID type %T", ids[0]) } } @@ -740,7 +740,7 @@ func (op *MergeOp) buildInt64Results(ctx *types.FuncContext, ids []any, scores [ for i, id := range ids { v, ok := id.(int64) if !ok { - return nil, nil, merr.WrapErrServiceInternal(fmt.Sprintf("merge_op: expected int64 ID at index %d, got %T", i, id)) + return nil, nil, merr.WrapErrFunctionFailedMsg("merge_op: expected int64 ID at index %d, got %T", i, id) } idBuilder.Append(v) scoreBuilder.Append(scores[i]) @@ -758,7 +758,7 @@ func (op *MergeOp) buildStringResults(ctx *types.FuncContext, ids []any, scores for i, id := range ids { v, ok := id.(string) if !ok { - return nil, nil, merr.WrapErrServiceInternal(fmt.Sprintf("merge_op: expected string ID at index %d, got %T", i, id)) + return nil, nil, merr.WrapErrFunctionFailedMsg("merge_op: expected string ID at index %d, got %T", i, id) } idBuilder.Append(v) scoreBuilder.Append(scores[i]) @@ -817,7 +817,7 @@ func (op *MergeOp) buildFieldArray(ctx *types.FuncContext, colName string, locs return buildEmptyArray(ctx.Pool(), col.DataType()) } } - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("merge_op: cannot determine type for column %s", colName)) + return nil, merr.WrapErrServiceInternalMsg("merge_op: cannot determine type for column %s", colName) } // Find the data type from first input that has this column @@ -830,7 +830,7 @@ func (op *MergeOp) buildFieldArray(ctx *types.FuncContext, colName string, locs } if dataType == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("merge_op: column %s not found in any input", colName)) + return nil, merr.WrapErrServiceInternalMsg("merge_op: column %s not found in any input", colName) } return buildArrayFromLocations(ctx.Pool(), colName, locs, inputs, dataType, chunkIdx) @@ -872,7 +872,7 @@ func buildEmptyArray(pool memory.Allocator, dt arrow.DataType) (arrow.Array, err defer b.Release() return b.NewArray(), nil default: - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("unsupported type: %s", dt.Name())) + return nil, merr.WrapErrServiceInternalMsg("unsupported type: %s", dt.Name()) } } @@ -896,7 +896,7 @@ func buildArrayFromLocations(pool memory.Allocator, colName string, locs []idLoc case arrow.STRING: return buildTypedArrayFromLocations[string](pool, colName, locs, inputs, array.NewStringBuilder(pool), chunkIdx) default: - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("unsupported type: %s", dt.Name())) + return nil, merr.WrapErrServiceInternalMsg("unsupported type: %s", dt.Name()) } } diff --git a/internal/util/function/chain/operator_registry.go b/internal/util/function/chain/operator_registry.go index bd1b9bdac4..b526776569 100644 --- a/internal/util/function/chain/operator_registry.go +++ b/internal/util/function/chain/operator_registry.go @@ -41,17 +41,17 @@ var ( // Returns an error if an operator with the same type is already registered. func RegisterOperator(opType string, factory OperatorFactory) error { if opType == "" { - return merr.WrapErrParameterInvalidMsg("operator type cannot be empty") + return merr.WrapErrParameterMissingMsg("operator type cannot be empty") } if factory == nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("operator factory cannot be nil for %q", opType)) + return merr.WrapErrServiceInternalMsg("operator factory cannot be nil for %q", opType) } operatorRegistryMu.Lock() defer operatorRegistryMu.Unlock() if _, exists := operatorRegistry[opType]; exists { - return merr.WrapErrServiceInternal(fmt.Sprintf("operator %q already registered", opType)) + return merr.WrapErrServiceInternalMsg("operator %q already registered", opType) } operatorRegistry[opType] = factory return nil diff --git a/internal/util/function/chain/operator_select.go b/internal/util/function/chain/operator_select.go index d485ff32b5..4e9bfc700d 100644 --- a/internal/util/function/chain/operator_select.go +++ b/internal/util/function/chain/operator_select.go @@ -63,7 +63,7 @@ func (o *SelectOp) Execute(ctx *types.FuncContext, input *DataFrame) (*DataFrame for _, colName := range o.inputs { col := input.Column(colName) if col == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("select_op: column %q not found", colName)) + return nil, merr.WrapErrServiceInternalMsg("select_op: column %q not found", colName) } // Copy chunks with retain @@ -89,7 +89,7 @@ func (o *SelectOp) String() string { func NewSelectOpFromRepr(repr *OperatorRepr) (Operator, error) { columnsInterface, ok := repr.Params["columns"] if !ok { - return nil, merr.WrapErrParameterInvalidMsg("select_op: columns is required") + return nil, merr.WrapErrParameterMissingMsg("select_op: columns is required") } columns, ok := columnsInterface.([]interface{}) if !ok { @@ -100,7 +100,7 @@ func NewSelectOpFromRepr(repr *OperatorRepr) (Operator, error) { return nil, merr.WrapErrParameterInvalidMsg("select_op: columns must be a list") } if len(columns) == 0 { - return nil, merr.WrapErrParameterInvalidMsg("select_op: columns is required") + return nil, merr.WrapErrParameterMissingMsg("select_op: columns is required") } colsStr := make([]string, len(columns)) for i, col := range columns { diff --git a/internal/util/function/chain/operator_sort.go b/internal/util/function/chain/operator_sort.go index adbfd7f3c8..ab26b27a98 100644 --- a/internal/util/function/chain/operator_sort.go +++ b/internal/util/function/chain/operator_sort.go @@ -80,12 +80,12 @@ func (o *SortOp) Execute(ctx *types.FuncContext, input *DataFrame) (*DataFrame, column := o.Column() sortCol := input.Column(column) if sortCol == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("sort_op: column %q not found", column)) + return nil, merr.WrapErrServiceInternalMsg("sort_op: column %q not found", column) } // Validate sort column type is comparable if !isComparableType(sortCol.DataType()) { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("sort_op: column %s has non-comparable type %s", column, sortCol.DataType().Name())) + return nil, merr.WrapErrServiceInternalMsg("sort_op: column %s has non-comparable type %s", column, sortCol.DataType().Name()) } // Resolve optional tie-break column @@ -164,7 +164,7 @@ func (o *SortOp) Execute(ctx *types.FuncContext, input *DataFrame) (*DataFrame, dataChunk := col.Chunk(chunkIdx) reordered, err := reorderArray(ctx.Pool(), dataChunk, indices) if err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("sort_op: column %s: %v", colName, err)) + return nil, merr.WrapErrServiceInternalMsg("sort_op: column %s: %v", colName, err) } collector.Set(colName, chunkIdx, reordered) } @@ -263,7 +263,7 @@ func reorderArray(pool memory.Allocator, data arrow.Array, indices []int) (arrow func NewSortOpFromRepr(repr *OperatorRepr) (Operator, error) { column, ok := repr.Params["column"].(string) if !ok || column == "" { - return nil, merr.WrapErrParameterInvalidMsg("sort_op: column is required") + return nil, merr.WrapErrParameterMissingMsg("sort_op: column is required") } desc := false if descVal, ok := repr.Params["desc"]; ok { diff --git a/internal/util/function/chain/repr.go b/internal/util/function/chain/repr.go index e107000ec0..aba8ae9eb0 100644 --- a/internal/util/function/chain/repr.go +++ b/internal/util/function/chain/repr.go @@ -20,7 +20,6 @@ package chain import ( "encoding/json" - "fmt" "github.com/apache/arrow/go/v17/arrow/memory" @@ -135,7 +134,7 @@ func chainJSONToRepr(json *ChainJSON) (*ChainRepr, error) { for i, opJSON := range json.Operators { opRepr, err := operatorJSONToRepr(&opJSON) if err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("operator[%d]: %v", i, err)) + return nil, merr.WrapErrServiceInternalMsg("operator[%d]: %v", i, err) } repr.Operators = append(repr.Operators, *opRepr) } @@ -172,7 +171,7 @@ func funcChainFromRepr(repr *ChainRepr, alloc memory.Allocator) (*FuncChain, err } if repr.Stage == "" { - return nil, merr.WrapErrParameterInvalidMsg("stage is required") + return nil, merr.WrapErrParameterMissingMsg("stage is required") } chain := NewFuncChainWithAllocator(alloc) @@ -184,7 +183,7 @@ func funcChainFromRepr(repr *ChainRepr, alloc memory.Allocator) (*FuncChain, err for i, opRepr := range repr.Operators { op, err := operatorFromRepr(&opRepr) if err != nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("operator[%d]: %v", i, err)) + return nil, merr.WrapErrServiceInternalMsg("operator[%d]: %v", i, err) } chain.Add(op) } @@ -209,7 +208,7 @@ func operatorFromRepr(repr *OperatorRepr) (Operator, error) { // FunctionFromRepr creates a FunctionExpr from a FunctionRepr. func FunctionFromRepr(repr *FunctionRepr) (types.FunctionExpr, error) { if repr.Name == "" { - return nil, merr.WrapErrParameterInvalidMsg("function name is required") + return nil, merr.WrapErrParameterMissingMsg("function name is required") } return types.CreateFunction(repr.Name, repr.Params) diff --git a/internal/util/function/chain/rerank_builder.go b/internal/util/function/chain/rerank_builder.go index 66f89413ca..0e9fc8d4ea 100644 --- a/internal/util/function/chain/rerank_builder.go +++ b/internal/util/function/chain/rerank_builder.go @@ -270,7 +270,7 @@ func buildRerankChainInternal( ) (*FuncChain, error) { rerankerName := rerank.GetRerankName(funcSchema) if rerankerName == "" { - return nil, merr.WrapErrParameterInvalidMsg("rerank_builder: reranker name not specified") + return nil, merr.WrapErrParameterMissingMsg("rerank_builder: reranker name not specified") } if alloc == nil { @@ -582,13 +582,13 @@ func parseDecayParams(funcSchema *schemapb.FunctionSchema) (MergeStrategy, bool, } if !functionSet { - return "", false, nil, merr.WrapErrParameterInvalidMsg("decay function not specified") + return "", false, nil, merr.WrapErrParameterMissingMsg("decay function not specified") } if !originSet { - return "", false, nil, merr.WrapErrParameterInvalidMsg("decay origin not specified") + return "", false, nil, merr.WrapErrParameterMissingMsg("decay origin not specified") } if !scaleSet { - return "", false, nil, merr.WrapErrParameterInvalidMsg("decay scale not specified") + return "", false, nil, merr.WrapErrParameterMissingMsg("decay scale not specified") } // Convert score_mode to MergeStrategy @@ -664,7 +664,7 @@ func parseModelQueries(funcSchema *schemapb.FunctionSchema) ([]string, error) { return nil, merr.WrapErrParameterInvalidMsg("rerank_builder: parse rerank params [queries] failed: %v", err) } if len(queries) == 0 { - return nil, merr.WrapErrParameterInvalidMsg("rerank_builder: rerank queries must not be empty") + return nil, merr.WrapErrParameterMissingMsg("rerank_builder: rerank queries must not be empty") } return queries, nil } diff --git a/internal/util/function/chain/types/registry.go b/internal/util/function/chain/types/registry.go index 5e6272a6ce..d9325ebd58 100644 --- a/internal/util/function/chain/types/registry.go +++ b/internal/util/function/chain/types/registry.go @@ -46,17 +46,17 @@ func NewFunctionRegistry() *FunctionRegistry { // Returns an error if a function with the same name is already registered. func (r *FunctionRegistry) Register(name string, factory FunctionFactory) error { if name == "" { - return merr.WrapErrParameterInvalidMsg("function name cannot be empty") + return merr.WrapErrParameterMissingMsg("function name cannot be empty") } if factory == nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("function factory cannot be nil for %q", name)) + return merr.WrapErrServiceInternalMsg("function factory cannot be nil for %q", name) } r.mu.Lock() defer r.mu.Unlock() if _, exists := r.factories[name]; exists { - return merr.WrapErrServiceInternal(fmt.Sprintf("function %q already registered", name)) + return merr.WrapErrServiceInternalMsg("function %q already registered", name) } r.factories[name] = factory return nil diff --git a/internal/util/function/embedding/ali_embedding_provider.go b/internal/util/function/embedding/ali_embedding_provider.go index 8a18ea3f27..351662f617 100644 --- a/internal/util/function/embedding/ali_embedding_provider.go +++ b/internal/util/function/embedding/ali_embedding_provider.go @@ -20,13 +20,13 @@ package embedding import ( "context" - "fmt" "strings" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/util/credentials" "github.com/milvus-io/milvus/internal/util/function/models" "github.com/milvus-io/milvus/internal/util/function/models/ali" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -45,7 +45,7 @@ type AliEmbeddingProvider struct { func createAliEmbeddingClient(apiKey string, url string) (*ali.AliDashScopeEmbedding, error) { if apiKey == "" { - return nil, fmt.Errorf("missing credentials config or configure the %s environment variable in the Milvus service", models.DashscopeAKEnvStr) + return nil, merr.WrapErrParameterInvalidMsg("missing credentials config or configure the %s environment variable in the Milvus service", models.DashscopeAKEnvStr) } if url == "" { @@ -131,11 +131,11 @@ func (provider *AliEmbeddingProvider) CallEmbedding(ctx context.Context, texts [ return nil, err } if end-i != len(resp.Output.Embeddings) { - return nil, fmt.Errorf("get embedding failed. The number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(resp.Output.Embeddings)) + return nil, merr.WrapErrFunctionFailedMsg("get embedding failed. The number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(resp.Output.Embeddings)) } for _, item := range resp.Output.Embeddings { if len(item.Embedding) != int(provider.fieldDim) { - return nil, fmt.Errorf("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", + return nil, merr.WrapErrFunctionFailedMsg("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", provider.fieldDim, len(item.Embedding)) } data = append(data, item.Embedding) diff --git a/internal/util/function/embedding/bedrock_embedding_provider.go b/internal/util/function/embedding/bedrock_embedding_provider.go index bfa1e96c8a..722bd98567 100644 --- a/internal/util/function/embedding/bedrock_embedding_provider.go +++ b/internal/util/function/embedding/bedrock_embedding_provider.go @@ -21,7 +21,6 @@ package embedding import ( "context" "encoding/json" - "fmt" "os" "strings" @@ -29,12 +28,12 @@ import ( "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/bedrockruntime" - "github.com/cockroachdb/errors" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" milvusCredentials "github.com/milvus-io/milvus/internal/util/credentials" "github.com/milvus-io/milvus/internal/util/function/models" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -57,13 +56,13 @@ type BedrockEmbeddingProvider struct { func createBedRockEmbeddingClient(awsAccessKeyId string, awsSecretAccessKey string, region string) (*bedrockruntime.Client, error) { if awsAccessKeyId == "" { - return nil, fmt.Errorf("missing credentials config or configure the %s environment variable in the Milvus service", models.BedrockAccessKeyId) + return nil, merr.WrapErrParameterInvalidMsg("missing credentials config or configure the %s environment variable in the Milvus service", models.BedrockAccessKeyId) } if awsSecretAccessKey == "" { - return nil, fmt.Errorf("missing credentials config or configure the %s environment variable in the Milvus service", models.BedrockSAKEnvStr) + return nil, merr.WrapErrParameterInvalidMsg("missing credentials config or configure the %s environment variable in the Milvus service", models.BedrockSAKEnvStr) } if region == "" { - return nil, errors.New("missing AWS Service region. Please pass `region` param") + return nil, merr.WrapErrParameterInvalidMsg("missing AWS Service region. Please pass `region` param") } cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(region), @@ -140,7 +139,7 @@ func NewBedrockEmbeddingProvider(fieldSchema *schemapb.FieldSchema, functionSche case "true": normalize = true default: - return nil, fmt.Errorf("illegal [%s:%s] param", models.NormalizeParamKey, param.Value) + return nil, merr.WrapErrParameterInvalidMsg("illegal [%s:%s] param", models.NormalizeParamKey, param.Value) } default: } @@ -214,7 +213,7 @@ func (provider *BedrockEmbeddingProvider) CallEmbedding(ctx context.Context, tex return nil, err } if len(resp.Embedding) != int(provider.fieldDim) { - return nil, fmt.Errorf("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", + return nil, merr.WrapErrFunctionFailedMsg("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", provider.fieldDim, len(resp.Embedding)) } data = append(data, resp.Embedding) diff --git a/internal/util/function/embedding/cohere_embedding_provider.go b/internal/util/function/embedding/cohere_embedding_provider.go index c17c20914f..3db142c785 100644 --- a/internal/util/function/embedding/cohere_embedding_provider.go +++ b/internal/util/function/embedding/cohere_embedding_provider.go @@ -20,13 +20,13 @@ package embedding import ( "context" - "fmt" "strings" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/util/credentials" "github.com/milvus-io/milvus/internal/util/function/models" "github.com/milvus-io/milvus/internal/util/function/models/cohere" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -69,7 +69,7 @@ func NewCohereEmbeddingProvider(fieldSchema *schemapb.FieldSchema, functionSchem } case models.TruncateParamKey: if param.Value != "NONE" && param.Value != "START" && param.Value != "END" { - return nil, fmt.Errorf("illegal parameters, %s only supports [NONE, START, END]", models.TruncateParamKey) + return nil, merr.WrapErrParameterInvalidMsg("illegal parameters, %s only supports [NONE, START, END]", models.TruncateParamKey) } truncate = param.Value default: @@ -87,7 +87,7 @@ func NewCohereEmbeddingProvider(fieldSchema *schemapb.FieldSchema, functionSchem embdType := models.GetEmbdType(fieldSchema.DataType) if embdType == models.UnsupportEmbd { - return nil, fmt.Errorf("unsupport output type: %s", fieldSchema.DataType) + return nil, merr.WrapErrParameterInvalidMsg("unsupport output type: %s", fieldSchema.DataType) } outputType := func() string { @@ -149,22 +149,22 @@ func (provider *CohereEmbeddingProvider) CallEmbedding(ctx context.Context, text } if provider.embdType == models.Float32Embd { if end-i != len(resp.Embeddings.Float) { - return nil, fmt.Errorf("get embedding failed. The number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(resp.Embeddings.Float)) + return nil, merr.WrapErrFunctionFailedMsg("get embedding failed. The number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(resp.Embeddings.Float)) } for _, item := range resp.Embeddings.Float { if len(item) != int(provider.fieldDim) { - return nil, fmt.Errorf("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", + return nil, merr.WrapErrFunctionFailedMsg("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", provider.fieldDim, len(item)) } } embRet.Append(resp.Embeddings.Float) } else { if end-i != len(resp.Embeddings.Int8) { - return nil, fmt.Errorf("get embedding failed. The number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(resp.Embeddings.Int8)) + return nil, merr.WrapErrFunctionFailedMsg("get embedding failed. The number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(resp.Embeddings.Int8)) } for _, item := range resp.Embeddings.Int8 { if len(item) != int(provider.fieldDim) { - return nil, fmt.Errorf("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", + return nil, merr.WrapErrFunctionFailedMsg("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", provider.fieldDim, len(item)) } } diff --git a/internal/util/function/embedding/function_base.go b/internal/util/function/embedding/function_base.go index 234155d4ef..7038e17b58 100644 --- a/internal/util/function/embedding/function_base.go +++ b/internal/util/function/embedding/function_base.go @@ -19,10 +19,10 @@ package embedding import ( - "fmt" "strings" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type FunctionBase struct { @@ -43,7 +43,7 @@ func getProvider(functionSchema *schemapb.FunctionSchema) (string, error) { default: } } - return "", fmt.Errorf("the text embedding service provider parameter:[%s] was not found", Provider) + return "", merr.WrapErrParameterInvalidMsg("the text embedding service provider parameter:[%s] was not found", Provider) } func NewFunctionBase(coll *schemapb.CollectionSchema, fSchema *schemapb.FunctionSchema) (*FunctionBase, error) { @@ -59,7 +59,7 @@ func NewFunctionBase(coll *schemapb.CollectionSchema, fSchema *schemapb.Function } if len(base.outputFields) != len(fSchema.GetOutputFieldNames()) { - return &base, fmt.Errorf("the collection [%s]'s information is wrong, function [%s]'s outputs does not match the schema", + return &base, merr.WrapErrParameterInvalidMsg("the collection [%s]'s information is wrong, function [%s]'s outputs does not match the schema", coll.Name, fSchema.Name) } diff --git a/internal/util/function/embedding/function_executor.go b/internal/util/function/embedding/function_executor.go index cb5e1246fb..1406d1f8b3 100644 --- a/internal/util/function/embedding/function_executor.go +++ b/internal/util/function/embedding/function_executor.go @@ -20,11 +20,9 @@ package embedding import ( "context" - "fmt" "strconv" "sync" - "github.com/cockroachdb/errors" "google.golang.org/protobuf/proto" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" @@ -72,7 +70,7 @@ func createFunction(coll *schemapb.CollectionSchema, schema *schemapb.FunctionSc case schemapb.FunctionType_MinHash: return nil, nil default: - return nil, fmt.Errorf("unknown functionRunner type %s", schema.GetType().String()) + return nil, merr.WrapErrParameterInvalidMsg("unknown functionRunner type %s", schema.GetType().String()) } } @@ -96,7 +94,7 @@ func validateFunction(schema *schemapb.CollectionSchema, fSchema *schemapb.Funct } if err := f.Check(context.Background()); err != nil { - return fmt.Errorf("check function [%s:%s] failed, the err is: %v", fSchema.Name, fSchema.GetType().String(), err) + return merr.Wrapf(err, "check function [%s:%s] failed", fSchema.Name, fSchema.GetType().String()) } return nil } @@ -119,7 +117,7 @@ func ValidateFunctions(schema *schemapb.CollectionSchema, needValidateFunctionNa } } - return fmt.Errorf("function [%s] not found in schema", needValidateFunctionName) + return merr.WrapErrParameterInvalidMsg("function [%s] not found in schema", needValidateFunctionName) } func NewFunctionExecutor(schema *schemapb.CollectionSchema, functions []*schemapb.FunctionSchema, extraInfo *models.ModelExtraInfo) (*FunctionExecutor, error) { @@ -151,7 +149,7 @@ func (executor *FunctionExecutor) processSingleFunction(ctx context.Context, run } } if len(inputs) != len(runner.GetSchema().InputFieldIds) { - return nil, errors.New("input field not found") + return nil, merr.WrapErrParameterInvalidMsg("input field not found") } tr := timerecord.NewTimeRecorder("function ProcessInsert") @@ -169,7 +167,7 @@ func (executor *FunctionExecutor) ProcessInsert(ctx context.Context, msg *msgstr numRows := msg.NumRows for _, runner := range executor.runners { if numRows > uint64(runner.MaxBatch()) { - return fmt.Errorf("numRows [%d] > function [%s]'s max batch [%d]", numRows, runner.GetSchema().Name, runner.MaxBatch()) + return merr.WrapErrParameterInvalidMsg("numRows [%d] > function [%s]'s max batch [%d]", numRows, runner.GetSchema().Name, runner.MaxBatch()) } } @@ -198,7 +196,7 @@ func (executor *FunctionExecutor) ProcessInsert(ctx context.Context, msg *msgstr errs = append(errs, err) } if len(errs) > 0 { - return fmt.Errorf("%v", errs) + return merr.WrapErrFunctionFailedMsg("function execution failed: %v", errs) } for output := range outputs { @@ -230,10 +228,10 @@ func (executor *FunctionExecutor) processSingleSearch(ctx context.Context, runne func (executor *FunctionExecutor) prcessSearch(ctx context.Context, req *internalpb.SearchRequest) error { runner, exist := executor.runners[req.FieldId] if !exist { - return fmt.Errorf("can not found function in field %d", req.FieldId) + return merr.WrapErrParameterInvalidMsg("can not found function in field %d", req.FieldId) } if req.Nq > int64(runner.MaxBatch()) { - return fmt.Errorf("nq [%d] > function [%s]'s max batch [%d]", req.Nq, runner.GetSchema().Name, runner.MaxBatch()) + return merr.WrapErrParameterInvalidMsg("nq [%d] > function [%s]'s max batch [%d]", req.Nq, runner.GetSchema().Name, runner.MaxBatch()) } if newHolder, err := executor.processSingleSearch(ctx, runner, req.GetPlaceholderGroup()); err != nil { return err @@ -250,7 +248,7 @@ func (executor *FunctionExecutor) prcessAdvanceSearch(ctx context.Context, req * for idx, sub := range req.GetSubReqs() { if runner, exist := executor.runners[sub.FieldId]; exist { if sub.Nq > int64(runner.MaxBatch()) { - return fmt.Errorf("nq [%d] > function [%s]'s max batch [%d]", sub.Nq, runner.GetSchema().Name, runner.MaxBatch()) + return merr.WrapErrParameterInvalidMsg("nq [%d] > function [%s]'s max batch [%d]", sub.Nq, runner.GetSchema().Name, runner.MaxBatch()) } wg.Add(1) go func(runner Runner, idx int64, placeholderGroup []byte) { @@ -290,7 +288,7 @@ func (executor *FunctionExecutor) processSingleBulkInsert(ctx context.Context, r for idx, id := range runner.GetSchema().InputFieldIds { field, exist := data.Data[id] if !exist { - return nil, fmt.Errorf("can not find input field: [%s]", runner.GetSchema().GetInputFieldNames()[idx]) + return nil, merr.WrapErrParameterInvalidMsg("can not find input field: [%s]", runner.GetSchema().GetInputFieldNames()[idx]) } inputs = append(inputs, field) } diff --git a/internal/util/function/embedding/gemini_embedding_provider.go b/internal/util/function/embedding/gemini_embedding_provider.go index 9671f20d97..e174f19b0f 100644 --- a/internal/util/function/embedding/gemini_embedding_provider.go +++ b/internal/util/function/embedding/gemini_embedding_provider.go @@ -27,6 +27,7 @@ import ( "github.com/milvus-io/milvus/internal/util/credentials" "github.com/milvus-io/milvus/internal/util/function/models" "github.com/milvus-io/milvus/internal/util/function/models/gemini" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -51,7 +52,7 @@ func NewGeminiEmbeddingProvider(fieldSchema *schemapb.FieldSchema, functionSchem } if fieldSchema.DataType != schemapb.DataType_FloatVector { - return nil, fmt.Errorf("Gemini embedding only supports FloatVector field, got %s", schemapb.DataType_name[int32(fieldSchema.DataType)]) //nolint:staticcheck // starts with proper noun + return nil, merr.WrapErrParameterInvalidMsg("Gemini embedding only supports FloatVector field, got %s", schemapb.DataType_name[int32(fieldSchema.DataType)]) //nolint:staticcheck // starts with proper noun } apiKey, url, err := models.ParseAKAndURL(credentials, functionSchema.Params, params, models.GeminiAKEnvStr, extraInfo) @@ -79,7 +80,7 @@ func NewGeminiEmbeddingProvider(fieldSchema *schemapb.FieldSchema, functionSchem } if modelName == "" { - return nil, fmt.Errorf("model_name is required for Gemini embedding provider") + return nil, merr.WrapErrParameterMissingMsg("model_name is required for Gemini embedding provider") } modelName = strings.TrimPrefix(modelName, "models/") @@ -139,12 +140,12 @@ func (provider *GeminiEmbeddingProvider) CallEmbedding(ctx context.Context, text return nil, err } if end-i != len(resp.Embeddings) { - return nil, fmt.Errorf("get embedding failed, the number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(resp.Embeddings)) + return nil, merr.WrapErrFunctionFailedMsg("get embedding failed, the number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(resp.Embeddings)) } for _, item := range resp.Embeddings { if len(item.Values) != int(provider.fieldDim) { - return nil, fmt.Errorf("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", + return nil, merr.WrapErrFunctionFailedMsg("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", provider.fieldDim, len(item.Values)) } embRet.Append(item.Values) diff --git a/internal/util/function/embedding/openai_embedding_provider.go b/internal/util/function/embedding/openai_embedding_provider.go index 41bf219930..ff5c297f5b 100644 --- a/internal/util/function/embedding/openai_embedding_provider.go +++ b/internal/util/function/embedding/openai_embedding_provider.go @@ -28,6 +28,7 @@ import ( "github.com/milvus-io/milvus/internal/util/credentials" "github.com/milvus-io/milvus/internal/util/function/models" "github.com/milvus-io/milvus/internal/util/function/models/openai" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -46,7 +47,7 @@ type OpenAIEmbeddingProvider struct { func createOpenAIEmbeddingClient(apiKey string, url string) (*openai.OpenAIEmbeddingClient, error) { if apiKey == "" { - return nil, fmt.Errorf("missing credentials config or configure the %s environment variable in the Milvus service", models.OpenaiAKEnvStr) + return nil, merr.WrapErrParameterInvalidMsg("missing credentials config or configure the %s environment variable in the Milvus service", models.OpenaiAKEnvStr) } if url == "" { @@ -59,7 +60,7 @@ func createOpenAIEmbeddingClient(apiKey string, url string) (*openai.OpenAIEmbed func createAzureOpenAIEmbeddingClient(apiKey string, url string, resourceName string) (*openai.AzureOpenAIEmbeddingClient, error) { if apiKey == "" { - return nil, fmt.Errorf("missing credentials config or configure the %s environment variable in the Milvus service", models.AzureOpenaiAKEnvStr) + return nil, merr.WrapErrParameterInvalidMsg("missing credentials config or configure the %s environment variable in the Milvus service", models.AzureOpenaiAKEnvStr) } if url == "" { @@ -71,7 +72,7 @@ func createAzureOpenAIEmbeddingClient(apiKey string, url string, resourceName st } } if url == "" { - return nil, fmt.Errorf("must configure the %s environment variable in the Milvus service", models.AzureOpenaiResourceName) + return nil, merr.WrapErrParameterInvalidMsg("must configure the %s environment variable in the Milvus service", models.AzureOpenaiResourceName) } c := openai.NewAzureOpenAIEmbeddingClient(apiKey, url) return c, nil @@ -164,11 +165,11 @@ func (provider *OpenAIEmbeddingProvider) CallEmbedding(ctx context.Context, text return nil, err } if end-i != len(resp.Data) { - return nil, fmt.Errorf("get embedding failed, the number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(resp.Data)) + return nil, merr.WrapErrFunctionFailedMsg("get embedding failed, the number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(resp.Data)) } for _, item := range resp.Data { if len(item.Embedding) != int(provider.fieldDim) { - return nil, fmt.Errorf("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", + return nil, merr.WrapErrFunctionFailedMsg("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", provider.fieldDim, len(item.Embedding)) } data = append(data, item.Embedding) diff --git a/internal/util/function/embedding/runner.go b/internal/util/function/embedding/runner.go index d751f95327..3359e4883f 100644 --- a/internal/util/function/embedding/runner.go +++ b/internal/util/function/embedding/runner.go @@ -16,15 +16,14 @@ package embedding import ( "context" - "fmt" - "github.com/cockroachdb/errors" "github.com/samber/lo" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/storage" "github.com/milvus-io/milvus/internal/util/function" "github.com/milvus-io/milvus/internal/util/function/models" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -120,7 +119,7 @@ func RunTextEmbedding( return err } if err := exec.ProcessBulkInsert(ctx, data); err != nil { - return fmt.Errorf("text embedding: %w", err) + return merr.Wrap(err, "text embedding") } return nil } @@ -135,7 +134,7 @@ func runOne( ) error { runner, err := function.NewFunctionRunner(schema, fn) if err != nil { - return fmt.Errorf("%s runner: %w", fn.GetType(), err) + return merr.Wrapf(err, "%s runner", fn.GetType()) } if runner == nil { return nil @@ -148,7 +147,7 @@ func runOne( } outputs, err := runner.BatchRun(inputs...) if err != nil { - return fmt.Errorf("%s execution: %w", fn.GetType(), err) + return merr.Wrapf(err, "%s execution", fn.GetType()) } return assignFn(schema, fn, runner, outputs, data) } @@ -161,7 +160,7 @@ func assignBM25Output( data *storage.InsertData, ) error { if len(outputs) != len(fn.GetOutputFieldIds()) { - return fmt.Errorf("BM25 runner output count mismatch: got %d, expected %d", + return merr.WrapErrServiceInternalMsg("BM25 runner output count mismatch: got %d, expected %d", len(outputs), len(fn.GetOutputFieldIds())) } for i, outID := range fn.GetOutputFieldIds() { @@ -170,31 +169,31 @@ func assignBM25Output( case schemapb.DataType_FloatVector: fd, ok := outputs[i].(*storage.FloatVectorFieldData) if !ok { - return fmt.Errorf("BM25 output %d: want *FloatVectorFieldData, got %T", outID, outputs[i]) + return merr.WrapErrServiceInternalMsg("BM25 output %d: want *FloatVectorFieldData, got %T", outID, outputs[i]) } data.Data[outID] = fd case schemapb.DataType_BFloat16Vector: fd, ok := outputs[i].(*storage.BFloat16VectorFieldData) if !ok { - return fmt.Errorf("BM25 output %d: want *BFloat16VectorFieldData, got %T", outID, outputs[i]) + return merr.WrapErrServiceInternalMsg("BM25 output %d: want *BFloat16VectorFieldData, got %T", outID, outputs[i]) } data.Data[outID] = fd case schemapb.DataType_Float16Vector: fd, ok := outputs[i].(*storage.Float16VectorFieldData) if !ok { - return fmt.Errorf("BM25 output %d: want *Float16VectorFieldData, got %T", outID, outputs[i]) + return merr.WrapErrServiceInternalMsg("BM25 output %d: want *Float16VectorFieldData, got %T", outID, outputs[i]) } data.Data[outID] = fd case schemapb.DataType_BinaryVector: fd, ok := outputs[i].(*storage.BinaryVectorFieldData) if !ok { - return fmt.Errorf("BM25 output %d: want *BinaryVectorFieldData, got %T", outID, outputs[i]) + return merr.WrapErrServiceInternalMsg("BM25 output %d: want *BinaryVectorFieldData, got %T", outID, outputs[i]) } data.Data[outID] = fd case schemapb.DataType_SparseFloatVector: sparse, ok := outputs[i].(*schemapb.SparseFloatArray) if !ok { - return fmt.Errorf("BM25 output %d: want *SparseFloatArray, got %T", outID, outputs[i]) + return merr.WrapErrServiceInternalMsg("BM25 output %d: want *SparseFloatArray, got %T", outID, outputs[i]) } data.Data[outID] = &storage.SparseFloatVectorFieldData{ SparseFloatArray: schemapb.SparseFloatArray{ @@ -202,7 +201,7 @@ func assignBM25Output( }, } default: - return fmt.Errorf("unsupported BM25 output type %s for field %d", + return merr.WrapErrParameterInvalidMsg("unsupported BM25 output type %s for field %d", outField.GetDataType(), outID) } } @@ -217,23 +216,23 @@ func assignMinHashOutput( data *storage.InsertData, ) error { if len(outputs) == 0 { - return errors.New("MinHash runner returned empty output") + return merr.WrapErrServiceInternalMsg("MinHash runner returned empty output") } fd, ok := outputs[0].(*schemapb.FieldData) if !ok { - return fmt.Errorf("MinHash output 0: want *FieldData, got %T", outputs[0]) + return merr.WrapErrServiceInternalMsg("MinHash output 0: want *FieldData, got %T", outputs[0]) } vec := fd.GetVectors() if vec == nil { - return errors.New("MinHash output is not a vector field") + return merr.WrapErrServiceInternalMsg("MinHash output is not a vector field") } binVec := vec.GetBinaryVector() if binVec == nil { - return errors.New("MinHash output is not a binary vector") + return merr.WrapErrServiceInternalMsg("MinHash output is not a binary vector") } outFields := runner.GetOutputFields() if len(outFields) == 0 { - return errors.New("MinHash runner has no output fields") + return merr.WrapErrServiceInternalMsg("MinHash runner has no output fields") } data.Data[outFields[0].GetFieldID()] = &storage.BinaryVectorFieldData{ Data: binVec, Dim: int(vec.GetDim()), diff --git a/internal/util/function/embedding/siliconflow_embedding_provider.go b/internal/util/function/embedding/siliconflow_embedding_provider.go index 81dedd90ad..295051c681 100644 --- a/internal/util/function/embedding/siliconflow_embedding_provider.go +++ b/internal/util/function/embedding/siliconflow_embedding_provider.go @@ -20,13 +20,13 @@ package embedding import ( "context" - "fmt" "strings" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/util/credentials" "github.com/milvus-io/milvus/internal/util/function/models" "github.com/milvus-io/milvus/internal/util/function/models/siliconflow" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -111,11 +111,11 @@ func (provider *SiliconflowEmbeddingProvider) CallEmbedding(ctx context.Context, return nil, err } if end-i != len(resp.Data) { - return nil, fmt.Errorf("get embedding failed, the number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(resp.Data)) + return nil, merr.WrapErrFunctionFailedMsg("get embedding failed, the number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(resp.Data)) } for _, item := range resp.Data { if len(item.Embedding) != int(provider.fieldDim) { - return nil, fmt.Errorf("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", + return nil, merr.WrapErrFunctionFailedMsg("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", provider.fieldDim, len(item.Embedding)) } data = append(data, item.Embedding) diff --git a/internal/util/function/embedding/tei_embedding_provider.go b/internal/util/function/embedding/tei_embedding_provider.go index 5d080183c1..4f2a8585f0 100644 --- a/internal/util/function/embedding/tei_embedding_provider.go +++ b/internal/util/function/embedding/tei_embedding_provider.go @@ -20,7 +20,6 @@ package embedding import ( "context" - "fmt" "strconv" "strings" @@ -28,6 +27,7 @@ import ( "github.com/milvus-io/milvus/internal/util/credentials" "github.com/milvus-io/milvus/internal/util/function/models" "github.com/milvus-io/milvus/internal/util/function/models/tei" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -68,15 +68,15 @@ func NewTEIEmbeddingProvider(fieldSchema *schemapb.FieldSchema, functionSchema * searchPrompt = param.Value case models.MaxClientBatchSizeParamKey: if maxBatch, err = strconv.Atoi(param.Value); err != nil { - return nil, fmt.Errorf("[%s param's value: %s] is not a valid number", models.MaxClientBatchSizeParamKey, param.Value) + return nil, merr.WrapErrParameterInvalidMsg("[%s param's value: %s] is not a valid number", models.MaxClientBatchSizeParamKey, param.Value) } case models.TruncationDirectionParamKey: if truncationDirection = param.Value; truncationDirection != "Left" && truncationDirection != "Right" { - return nil, fmt.Errorf("[%s param's value: %s] is not invalid, only supports [Left/Right]", models.TruncationDirectionParamKey, param.Value) + return nil, merr.WrapErrParameterInvalidMsg("[%s param's value: %s] is not invalid, only supports [Left/Right]", models.TruncationDirectionParamKey, param.Value) } case models.TruncateParamKey: if truncate, err = strconv.ParseBool(param.Value); err != nil { - return nil, fmt.Errorf("[%s param's value: %s] is invalid, only supports: [true/false]", models.TruncateParamKey, param.Value) + return nil, merr.WrapErrParameterInvalidMsg("[%s param's value: %s] is invalid, only supports: [true/false]", models.TruncateParamKey, param.Value) } default: } @@ -134,11 +134,11 @@ func (provider *TeiEmbeddingProvider) CallEmbedding(ctx context.Context, texts [ return nil, err } if end-i != len(*resp) { - return nil, fmt.Errorf("get embedding failed, the number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(*resp)) + return nil, merr.WrapErrFunctionFailedMsg("get embedding failed, the number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(*resp)) } for _, item := range *resp { if len(item) != int(provider.fieldDim) { - return nil, fmt.Errorf("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", + return nil, merr.WrapErrFunctionFailedMsg("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", provider.fieldDim, len(item)) } data = append(data, item) diff --git a/internal/util/function/embedding/text_embedding_function.go b/internal/util/function/embedding/text_embedding_function.go index 2f5dd98848..b9f8b67ef4 100644 --- a/internal/util/function/embedding/text_embedding_function.go +++ b/internal/util/function/embedding/text_embedding_function.go @@ -20,17 +20,15 @@ package embedding import ( "context" - "fmt" "reflect" - "github.com/cockroachdb/errors" - "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/internal/storage" "github.com/milvus-io/milvus/internal/util/credentials" "github.com/milvus-io/milvus/internal/util/function/models" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -64,18 +62,18 @@ func hasEmptyString(texts []string) bool { func TextEmbeddingOutputsCheck(fields []*schemapb.FieldSchema) error { if len(fields) != 1 || (fields[0].DataType != schemapb.DataType_FloatVector && fields[0].DataType != schemapb.DataType_Int8Vector) { - return errors.New("TextEmbedding function output field must be a FloatVector or Int8Vector field") //nolint:staticcheck // starts with proper noun + return merr.WrapErrParameterInvalidMsg("TextEmbedding function output field must be a FloatVector or Int8Vector field") //nolint:staticcheck // starts with proper noun } return nil } func TextEmbeddingInputsCheck(name string, fields []*schemapb.FieldSchema) error { if len(fields) != 1 || (fields[0].DataType != schemapb.DataType_VarChar && fields[0].DataType != schemapb.DataType_Text) { - return errors.New("TextEmbedding function input field must be a VARCHAR/TEXT field") //nolint:staticcheck // starts with proper noun + return merr.WrapErrParameterInvalidMsg("TextEmbedding function input field must be a VARCHAR/TEXT field") //nolint:staticcheck // starts with proper noun } if fields[0].Nullable { - return fmt.Errorf("function input field cannot be nullable: function %s, field %s", name, fields[0].GetName()) + return merr.WrapErrParameterInvalidMsg("function input field cannot be nullable: function %s, field %s", name, fields[0].GetName()) } return nil } @@ -99,7 +97,7 @@ func isValidInputDataType(dataType schemapb.DataType) bool { func NewTextEmbeddingFunction(coll *schemapb.CollectionSchema, functionSchema *schemapb.FunctionSchema, extraInfo *models.ModelExtraInfo) (*TextEmbeddingFunction, error) { if len(functionSchema.GetOutputFieldNames()) != 1 { - return nil, fmt.Errorf("text function should only have one output field, but now is %d", len(functionSchema.GetOutputFieldNames())) + return nil, merr.WrapErrParameterInvalidMsg("text function should only have one output field, but now is %d", len(functionSchema.GetOutputFieldNames())) } base, err := NewFunctionBase(coll, functionSchema) @@ -114,7 +112,7 @@ func NewTextEmbeddingFunction(coll *schemapb.CollectionSchema, functionSchema *s var newProviderErr error conf := paramtable.Get().FunctionCfg.GetTextEmbeddingProviderConfig(base.provider) if !models.IsEnable(conf) { - return nil, fmt.Errorf("text embedding model provider [%s] is disabled", base.provider) + return nil, merr.WrapErrParameterInvalidMsg("text embedding model provider [%s] is disabled", base.provider) } credentials := credentials.NewCredentials(paramtable.Get().CredentialCfg.GetCredentials()) batchFactor := paramtable.Get().FunctionCfg.GetBatchFactor() @@ -146,7 +144,7 @@ func NewTextEmbeddingFunction(coll *schemapb.CollectionSchema, functionSchema *s case geminiProvider: embP, newProviderErr = NewGeminiEmbeddingProvider(base.outputFields[0], functionSchema, conf, credentials, extraInfo) default: - return nil, fmt.Errorf("unsupported text embedding service provider: [%s] , list of supported [%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s]", base.provider, openAIProvider, azureOpenAIProvider, aliDashScopeProvider, bedrockProvider, vertexAIProvider, voyageAIProvider, cohereProvider, siliconflowProvider, teiProvider, ycProvider, zillizProvider, geminiProvider) + return nil, merr.WrapErrParameterInvalidMsg("unsupported text embedding service provider: [%s] , list of supported [%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s]", base.provider, openAIProvider, azureOpenAIProvider, aliDashScopeProvider, bedrockProvider, vertexAIProvider, voyageAIProvider, cohereProvider, siliconflowProvider, teiProvider, ycProvider, zillizProvider, geminiProvider) } if newProviderErr != nil { @@ -168,18 +166,18 @@ func (runner *TextEmbeddingFunction) Check(ctx context.Context) error { case [][]float32: dim = len(embds[0]) if runner.GetOutputFields()[0].DataType != schemapb.DataType_FloatVector { - return fmt.Errorf("embedding model output and field type mismatch, model output is %s, field type is %s", schemapb.DataType_name[int32(schemapb.DataType_FloatVector)], schemapb.DataType_name[int32(runner.GetOutputFields()[0].DataType)]) + return merr.WrapErrParameterInvalidMsg("embedding model output and field type mismatch, model output is %s, field type is %s", schemapb.DataType_name[int32(schemapb.DataType_FloatVector)], schemapb.DataType_name[int32(runner.GetOutputFields()[0].DataType)]) } case [][]int8: dim = len(embds[0]) if runner.GetOutputFields()[0].DataType != schemapb.DataType_Int8Vector { - return fmt.Errorf("embedding model output and field type mismatch, model output is %s, field type is %s", schemapb.DataType_name[int32(schemapb.DataType_Int8Vector)], schemapb.DataType_name[int32(runner.GetOutputFields()[0].DataType)]) + return merr.WrapErrParameterInvalidMsg("embedding model output and field type mismatch, model output is %s, field type is %s", schemapb.DataType_name[int32(schemapb.DataType_Int8Vector)], schemapb.DataType_name[int32(runner.GetOutputFields()[0].DataType)]) } default: - return fmt.Errorf("unsupported embedding type: %s", reflect.TypeOf(embds).String()) + return merr.WrapErrParameterInvalidMsg("unsupported embedding type: %s", reflect.TypeOf(embds).String()) } if dim != int(runner.embProvider.FieldDim()) { - return fmt.Errorf("the dim set in the schema is inconsistent with the dim of the model, dim in schema is %d, dim of model is %d", runner.embProvider.FieldDim(), dim) + return merr.WrapErrParameterInvalidMsg("the dim set in the schema is inconsistent with the dim of the model, dim in schema is %d, dim of model is %d", runner.embProvider.FieldDim(), dim) } return nil } @@ -249,25 +247,25 @@ func (runner *TextEmbeddingFunction) packToFieldData(embds any) ([]*schemapb.Fie func (runner *TextEmbeddingFunction) ProcessInsert(ctx context.Context, inputs []*schemapb.FieldData) ([]*schemapb.FieldData, error) { if len(inputs) != 1 { - return nil, fmt.Errorf("text embedding function only receives one input field, but got [%d]", len(inputs)) + return nil, merr.WrapErrParameterInvalidMsg("text embedding function only receives one input field, but got [%d]", len(inputs)) } if !isValidInputDataType(inputs[0].Type) { - return nil, fmt.Errorf("text embedding only supports varchar or text field as input field, but got %s", schemapb.DataType_name[int32(inputs[0].Type)]) + return nil, merr.WrapErrParameterInvalidMsg("text embedding only supports varchar or text field as input field, but got %s", schemapb.DataType_name[int32(inputs[0].Type)]) } texts := inputs[0].GetScalars().GetStringData().GetData() if texts == nil { - return nil, errors.New("input texts is empty") + return nil, merr.WrapErrParameterInvalidMsg("input texts is empty") } // make sure all texts are not empty if hasEmptyString(texts) { - return nil, errors.New("there is an empty string in the input data, TextEmbedding function does not support empty text") + return nil, merr.WrapErrParameterInvalidMsg("there is an empty string in the input data, TextEmbedding function does not support empty text") } numRows := len(texts) if numRows > runner.MaxBatch() { - return nil, fmt.Errorf("embedding supports up to [%d] pieces of data at a time, got [%d]", runner.MaxBatch(), numRows) + return nil, merr.WrapErrParameterInvalidMsg("embedding supports up to [%d] pieces of data at a time, got [%d]", runner.MaxBatch(), numRows) } embds, err := runner.embProvider.CallEmbedding(ctx, texts, models.InsertMode) @@ -282,11 +280,11 @@ func (runner *TextEmbeddingFunction) ProcessSearch(ctx context.Context, placehol texts := funcutil.GetVarCharFromPlaceholder(placeholderGroup.Placeholders[0]) // Already checked externally numRows := len(texts) if numRows > runner.MaxBatch() { - return nil, fmt.Errorf("embedding supports up to [%d] pieces of data at a time, got [%d]", runner.MaxBatch(), numRows) + return nil, merr.WrapErrParameterInvalidMsg("embedding supports up to [%d] pieces of data at a time, got [%d]", runner.MaxBatch(), numRows) } // make sure all texts are not empty if hasEmptyString(texts) { - return nil, errors.New("there is an empty string in the queries, TextEmbedding function does not support empty text") + return nil, merr.WrapErrParameterInvalidMsg("there is an empty string in the queries, TextEmbedding function does not support empty text") } embds, err := runner.embProvider.CallEmbedding(ctx, texts, models.SearchMode) if err != nil { @@ -297,27 +295,27 @@ func (runner *TextEmbeddingFunction) ProcessSearch(ctx context.Context, placehol } else if runner.GetOutputFields()[0].DataType == schemapb.DataType_Int8Vector { return funcutil.Int8VectorsToPlaceholderGroup(embds.([][]int8)), nil } - return nil, fmt.Errorf("text embedding function doesn't support % vector", schemapb.DataType_name[int32(runner.GetOutputFields()[0].DataType)]) + return nil, merr.WrapErrParameterInvalidMsg("text embedding function doesn't support % vector", schemapb.DataType_name[int32(runner.GetOutputFields()[0].DataType)]) } func (runner *TextEmbeddingFunction) ProcessBulkInsert(ctx context.Context, inputs []storage.FieldData) (map[storage.FieldID]storage.FieldData, error) { if len(inputs) != 1 { - return nil, fmt.Errorf("TextEmbedding function only receives one input, bug got [%d]", len(inputs)) //nolint:staticcheck // starts with proper noun + return nil, merr.WrapErrParameterInvalidMsg("TextEmbedding function only receives one input, bug got [%d]", len(inputs)) //nolint:staticcheck // starts with proper noun } if !isValidInputDataType(inputs[0].GetDataType()) { - return nil, fmt.Errorf("TextEmbedding function only supports varchar or text field as input field, but got %s", schemapb.DataType_name[int32(inputs[0].GetDataType())]) //nolint:staticcheck // starts with proper noun + return nil, merr.WrapErrParameterInvalidMsg("TextEmbedding function only supports varchar or text field as input field, but got %s", schemapb.DataType_name[int32(inputs[0].GetDataType())]) //nolint:staticcheck // starts with proper noun } texts, ok := inputs[0].GetDataRows().([]string) if !ok { - return nil, errors.New("input texts is empty") + return nil, merr.WrapErrParameterInvalidMsg("input texts is empty") } // make sure all texts are not empty // In storage.FieldData, null is also stored as an empty string if hasEmptyString(texts) { - return nil, errors.New("there is an empty string in the input data, TextEmbedding function does not support empty text") + return nil, merr.WrapErrParameterInvalidMsg("there is an empty string in the input data, TextEmbedding function does not support empty text") } embds, err := runner.embProvider.CallEmbedding(ctx, texts, models.InsertMode) if err != nil { @@ -352,5 +350,5 @@ func (runner *TextEmbeddingFunction) ProcessBulkInsert(ctx context.Context, inpu runner.outputFields[0].FieldID: field, }, nil } - return nil, errors.New("unknown embedding type") + return nil, merr.WrapErrFunctionFailedMsg("unknown embedding type") } diff --git a/internal/util/function/embedding/vertexai_embedding_provider.go b/internal/util/function/embedding/vertexai_embedding_provider.go index 8eb49e9cb8..b5ca3fb8d9 100644 --- a/internal/util/function/embedding/vertexai_embedding_provider.go +++ b/internal/util/function/embedding/vertexai_embedding_provider.go @@ -25,13 +25,12 @@ import ( "strings" "sync" - "github.com/cockroachdb/errors" - "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/internal/util/credentials" "github.com/milvus-io/milvus/internal/util/function/models" "github.com/milvus-io/milvus/internal/util/function/models/vertexai" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -49,7 +48,7 @@ func getVertexAIJsonKey() ([]byte, error) { jsonKeyPath := os.Getenv(models.VertexServiceAccountJSONEnv) if jsonKeyPath == "" { - return nil, errors.New("VetexAI credentials file path is empty") + return nil, merr.WrapErrParameterInvalidMsg("VetexAI credentials file path is empty") } if vtxKey.filePath == jsonKeyPath { return vtxKey.jsonKey, nil @@ -57,7 +56,7 @@ func getVertexAIJsonKey() ([]byte, error) { jsonKey, err := os.ReadFile(jsonKeyPath) //nolint:gosec // path is from trusted environment variable if err != nil { - return nil, fmt.Errorf("Vertexai: read credentials file failed, %v", err) //nolint:staticcheck // starts with proper noun + return nil, merr.Wrap(err, "Vertexai: read credentials file failed") //nolint:staticcheck // starts with proper noun } vtxKey.jsonKey = jsonKey @@ -287,11 +286,11 @@ func (provider *VertexAIEmbeddingProvider) callVertexAIEmbedding(texts []string, return nil, err } if end-i != len(resp.Predictions) { - return nil, fmt.Errorf("get embedding failed, the number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(resp.Predictions)) + return nil, merr.WrapErrFunctionFailedMsg("get embedding failed, the number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(resp.Predictions)) } for _, item := range resp.Predictions { if len(item.Embeddings.Values) != int(provider.fieldDim) { - return nil, fmt.Errorf("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", + return nil, merr.WrapErrFunctionFailedMsg("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", provider.fieldDim, len(item.Embeddings.Values)) } data = append(data, item.Embeddings.Values) @@ -311,7 +310,7 @@ func (provider *VertexAIEmbeddingProvider) callGeminiEmbedding(texts []string, m return nil, err } if len(resp.Embedding.Values) != int(provider.fieldDim) { - return nil, fmt.Errorf("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", + return nil, merr.WrapErrFunctionFailedMsg("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", provider.fieldDim, len(resp.Embedding.Values)) } data = append(data, resp.Embedding.Values) diff --git a/internal/util/function/embedding/voyageai_embedding_provider.go b/internal/util/function/embedding/voyageai_embedding_provider.go index 7dd8e820db..a4a15c224b 100644 --- a/internal/util/function/embedding/voyageai_embedding_provider.go +++ b/internal/util/function/embedding/voyageai_embedding_provider.go @@ -20,7 +20,6 @@ package embedding import ( "context" - "fmt" "strconv" "strings" @@ -28,6 +27,7 @@ import ( "github.com/milvus-io/milvus/internal/util/credentials" "github.com/milvus-io/milvus/internal/util/function/models" "github.com/milvus-io/milvus/internal/util/function/models/voyageai" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -72,7 +72,7 @@ func NewVoyageAIEmbeddingProvider(fieldSchema *schemapb.FieldSchema, functionSch } case models.TruncationParamKey: if truncate, err = strconv.ParseBool(param.Value); err != nil { - return nil, fmt.Errorf("[%s param's value: %s] is invalid, only supports: [true/false]", models.TruncationParamKey, param.Value) + return nil, merr.WrapErrParameterInvalidMsg("[%s param's value: %s] is invalid, only supports: [true/false]", models.TruncationParamKey, param.Value) } default: } @@ -89,7 +89,7 @@ func NewVoyageAIEmbeddingProvider(fieldSchema *schemapb.FieldSchema, functionSch embdType := models.GetEmbdType(fieldSchema.DataType) if embdType == models.UnsupportEmbd { - return nil, fmt.Errorf("unsupported output type: %s", fieldSchema.DataType) + return nil, merr.WrapErrParameterInvalidMsg("unsupported output type: %s", fieldSchema.DataType) } outputType := func() string { @@ -145,12 +145,12 @@ func (provider *VoyageAIEmbeddingProvider) CallEmbedding(ctx context.Context, te if provider.embdType == models.Float32Embd { resp := r.(*voyageai.EmbeddingResponse[float32]) if end-i != len(resp.Data) { - return nil, fmt.Errorf("get embedding failed, the number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(resp.Data)) + return nil, merr.WrapErrFunctionFailedMsg("get embedding failed, the number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(resp.Data)) } for _, item := range resp.Data { if len(item.Embedding) != int(provider.fieldDim) { - return nil, fmt.Errorf("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", + return nil, merr.WrapErrFunctionFailedMsg("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", provider.fieldDim, len(item.Embedding)) } embRet.Append(item.Embedding) @@ -158,12 +158,12 @@ func (provider *VoyageAIEmbeddingProvider) CallEmbedding(ctx context.Context, te } else { resp := r.(*voyageai.EmbeddingResponse[int8]) if end-i != len(resp.Data) { - return nil, fmt.Errorf("get embedding failed, the number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(resp.Data)) + return nil, merr.WrapErrFunctionFailedMsg("get embedding failed, the number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(resp.Data)) } for _, item := range resp.Data { if len(item.Embedding) != int(provider.fieldDim) { - return nil, fmt.Errorf("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", + return nil, merr.WrapErrFunctionFailedMsg("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", provider.fieldDim, len(item.Embedding)) } embRet.Append(item.Embedding) diff --git a/internal/util/function/embedding/yc_embedding_provider.go b/internal/util/function/embedding/yc_embedding_provider.go index 5b059f35b7..a57291b11a 100644 --- a/internal/util/function/embedding/yc_embedding_provider.go +++ b/internal/util/function/embedding/yc_embedding_provider.go @@ -26,6 +26,7 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/util/credentials" "github.com/milvus-io/milvus/internal/util/function/models" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -75,7 +76,7 @@ func NewYCEmbeddingProvider(fieldSchema *schemapb.FieldSchema, functionSchema *s } } if modelName == "" { - return nil, fmt.Errorf("yc embedding model name is required") + return nil, merr.WrapErrParameterMissingMsg("yc embedding model name is required") } apiKey, url, err := models.ParseAKAndURL(credentials, functionSchema.Params, params, models.YandexCloudAKEnvStr, extraInfo) @@ -83,7 +84,7 @@ func NewYCEmbeddingProvider(fieldSchema *schemapb.FieldSchema, functionSchema *s return nil, err } if apiKey == "" { - return nil, fmt.Errorf("missing credentials config or configure the %s environment variable in the Milvus service", models.YandexCloudAKEnvStr) + return nil, merr.WrapErrParameterInvalidMsg("missing credentials config or configure the %s environment variable in the Milvus service", models.YandexCloudAKEnvStr) } if url == "" { url = defaultYCTextEmbeddingURL @@ -142,11 +143,11 @@ func (provider *YCEmbeddingProvider) CallEmbedding(ctx context.Context, texts [] embeddings := extractYCEmbeddings(resp) if end-i != len(embeddings) { - return nil, fmt.Errorf("get embedding failed, the number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(embeddings)) + return nil, merr.WrapErrFunctionFailedMsg("get embedding failed, the number of texts and embeddings does not match text:[%d], embedding:[%d]", end-i, len(embeddings)) } for _, emb := range embeddings { if len(emb) != int(provider.fieldDim) { - return nil, fmt.Errorf("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", + return nil, merr.WrapErrFunctionFailedMsg("the required embedding dim is [%d], but the embedding obtained from the model is [%d]", provider.fieldDim, len(emb)) } data = append(data, emb) diff --git a/internal/util/function/function.go b/internal/util/function/function.go index e3c819aa50..66a549b137 100644 --- a/internal/util/function/function.go +++ b/internal/util/function/function.go @@ -19,9 +19,8 @@ package function import ( - "fmt" - "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type FunctionRunner interface { @@ -43,6 +42,6 @@ func NewFunctionRunner(coll *schemapb.CollectionSchema, schema *schemapb.Functio case schemapb.FunctionType_TextEmbedding: return nil, nil default: - return nil, fmt.Errorf("unknown functionRunner type %s", schema.GetType().String()) + return nil, merr.WrapErrParameterInvalidMsg("unknown functionRunner type %s", schema.GetType().String()) } } diff --git a/internal/util/function/highlight/semantic_highlight.go b/internal/util/function/highlight/semantic_highlight.go index 9e3e1ec97f..e564cd2304 100644 --- a/internal/util/function/highlight/semantic_highlight.go +++ b/internal/util/function/highlight/semantic_highlight.go @@ -21,11 +21,11 @@ package highlight import ( "context" "encoding/json" - "fmt" "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/internal/util/function/models" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type semanticHighlightProvider interface { @@ -62,21 +62,21 @@ func NewSemanticHighlight(collSchema *schemapb.CollectionSchema, params []*commo switch param.Key { case queryKeyName: if err := json.Unmarshal([]byte(param.Value), &queries); err != nil { - return nil, fmt.Errorf("parse queries failed, err: %v", err) + return nil, merr.Wrap(err, "parse queries failed") } case inputFieldKeyName: if err := json.Unmarshal([]byte(param.Value), &inputFields); err != nil { - return nil, fmt.Errorf("parse input_field failed, err: %v", err) + return nil, merr.Wrap(err, "parse input_field failed") } } } if len(queries) == 0 { - return nil, fmt.Errorf("queries is required") + return nil, merr.WrapErrParameterMissingMsg("queries is required") } if len(inputFields) == 0 { - return nil, fmt.Errorf("input_field is required") + return nil, merr.WrapErrParameterMissingMsg("input_field is required") } fieldIDMap := make(map[string]*schemapb.FieldSchema) @@ -97,13 +97,13 @@ func NewSemanticHighlight(collSchema *schemapb.CollectionSchema, params []*commo if ok { // schema field found if field.DataType != schemapb.DataType_VarChar && field.DataType != schemapb.DataType_Text { - return nil, fmt.Errorf("input_field %s is not a VarChar or Text field", fieldName) + return nil, merr.WrapErrParameterInvalidMsg("input_field %s is not a VarChar or Text field", fieldName) } fieldIDs = append(fieldIDs, field.FieldID) } else { // field not in schema, check if dynamic field is enabled if !collSchema.EnableDynamicField { - return nil, fmt.Errorf("input_field %s not found in schema", fieldName) + return nil, merr.WrapErrParameterInvalidMsg("input_field %s not found in schema", fieldName) } // Non-schema fields are dynamic fields dynamicFieldNames = append(dynamicFieldNames, fieldName) @@ -171,7 +171,7 @@ func (highlight *SemanticHighlight) processOneQuery(ctx context.Context, query s return nil, nil, err } if len(highlights) != len(documents) || len(scores) != len(documents) { - return nil, nil, fmt.Errorf("highlights size must equal to documents size, but got highlights size [%d], scores size [%d], documents size [%d]", len(highlights), len(scores), len(documents)) + return nil, nil, merr.WrapErrFunctionFailedMsg("highlights size must equal to documents size, but got highlights size [%d], scores size [%d], documents size [%d]", len(highlights), len(scores), len(documents)) } return highlights, scores, nil @@ -180,7 +180,7 @@ func (highlight *SemanticHighlight) processOneQuery(ctx context.Context, query s func (highlight *SemanticHighlight) Process(ctx context.Context, topks []int64, documents []string) ([][]string, [][]float32, error) { nq := len(topks) if len(highlight.queries) != nq { - return nil, nil, fmt.Errorf("nq must equal to queries size, but got nq [%d], queries size [%d], queries: [%v]", nq, len(highlight.queries), highlight.queries) + return nil, nil, merr.WrapErrParameterInvalidMsg("nq must equal to queries size, but got nq [%d], queries size [%d], queries: [%v]", nq, len(highlight.queries), highlight.queries) } if len(documents) == 0 { return [][]string{}, [][]float32{}, nil diff --git a/internal/util/function/manager.go b/internal/util/function/manager.go index 28d3fb1562..677291dd33 100644 --- a/internal/util/function/manager.go +++ b/internal/util/function/manager.go @@ -20,6 +20,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/util/bm25" "github.com/milvus-io/milvus/pkg/v3/util/conc" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -476,7 +477,7 @@ func (e *functionRunnerCollectionEntry) ensureVersion( schema *schemapb.CollectionSchema, ) ([]*functionRunnerEntry, error) { if schema == nil { - return nil, errors.New("collection schema is nil") + return nil, merr.WrapErrFunctionFailedMsg("collection schema is nil") } schemaVersion := schema.GetVersion() @@ -579,7 +580,7 @@ func (e *functionRunnerCollectionEntry) ensureVersion( _, outputOK := outputFieldIDsBySignature[signature] if _, functionOK := functionsBySignature[signature]; !functionOK || !outputOK { e.mu.Unlock() - return nil, fmt.Errorf("function runner signature %s not found in schema version %d", signature, schemaVersion) + return nil, merr.WrapErrFunctionFailedMsg("function runner signature %s not found in schema version %d", signature, schemaVersion) } if e.runners[signature] == nil { e.runners[signature] = newFunctionRunnerEntry( @@ -675,7 +676,7 @@ func (e *functionRunnerCollectionEntry) Materialize( body *msgpb.InsertRequest, ) (bool, error) { if schema == nil { - return false, errors.New("collection schema is nil") + return false, merr.WrapErrFunctionFailedMsg("collection schema is nil") } changed, ok, err := e.TryMaterialize(schema.GetVersion(), body) if err != nil { @@ -710,7 +711,7 @@ func (e *functionRunnerCollectionEntry) TryMaterialize( body *msgpb.InsertRequest, ) (bool, bool, error) { if body == nil { - return false, false, errors.New("insert request is nil") + return false, false, merr.WrapErrFunctionFailedMsg("insert request is nil") } runnerEntries, outputFieldIDs, ok := e.getVersionRunnerEntries(schemaVersion) @@ -763,7 +764,7 @@ func (e *functionRunnerCollectionEntry) RunWithAnalyzer( err := runnerEntry.run(ctx, func(runner FunctionRunner) error { analyzer, ok := runner.(Analyzer) if !ok { - return errors.New("function runner cannot serve analyzer requests") + return merr.WrapErrFunctionFailedMsg("function runner cannot serve analyzer requests") } return run(analyzer) }) @@ -820,14 +821,14 @@ func (m *functionRunnerManager) Materialize( body *msgpb.InsertRequest, ) (bool, error) { if schema == nil { - return false, errors.New("collection schema is nil") + return false, merr.WrapErrFunctionFailedMsg("collection schema is nil") } if !HasEmbeddingFunctions(schema) { return false, nil } entry := m.getEntry(collectionID) if entry == nil { - return false, fmt.Errorf("function runners for collection %d are not allocated", collectionID) + return false, merr.WrapErrFunctionFailedMsg("function runners for collection %d are not allocated", collectionID) } return entry.Materialize(ctx, schema, body) } @@ -894,10 +895,10 @@ func functionRunnerErrorResult(err error) <-chan error { func BuildEmbeddingRunner(schema *schemapb.CollectionSchema, fn *schemapb.FunctionSchema) (FunctionRunner, error) { if schema == nil { - return nil, errors.New("collection schema is nil") + return nil, merr.WrapErrFunctionFailedMsg("collection schema is nil") } if fn == nil { - return nil, errors.New("function schema is nil") + return nil, merr.WrapErrFunctionFailedMsg("function schema is nil") } if !IsEmbeddingFunctionType(fn.GetType()) { return nil, nil @@ -910,7 +911,7 @@ func BuildEmbeddingRunner(schema *schemapb.CollectionSchema, fn *schemapb.Functi func BuildEmbeddingRunners(schema *schemapb.CollectionSchema) ([]FunctionRunner, error) { if schema == nil { - return nil, errors.New("collection schema is nil") + return nil, merr.WrapErrFunctionFailedMsg("collection schema is nil") } if !HasEmbeddingFunctions(schema) { return nil, nil @@ -933,7 +934,7 @@ func BuildEmbeddingRunners(schema *schemapb.CollectionSchema) ([]FunctionRunner, func EmbeddingOutputFieldIDs(schema *schemapb.CollectionSchema) ([]int64, error) { if schema == nil { - return nil, errors.New("collection schema is nil") + return nil, merr.WrapErrFunctionFailedMsg("collection schema is nil") } if !HasEmbeddingFunctions(schema) { return nil, nil @@ -953,7 +954,7 @@ func EmbeddingOutputFieldIDs(schema *schemapb.CollectionSchema) ([]int64, error) func EmbeddingFunctionSignature(schema *schemapb.CollectionSchema) (string, error) { if schema == nil { - return "", errors.New("collection schema is nil") + return "", merr.WrapErrFunctionFailedMsg("collection schema is nil") } hasher := sha256.New() @@ -1005,7 +1006,7 @@ func functionOutputFieldIDs(schema *schemapb.CollectionSchema, fn *schemapb.Func outputFieldIDs := append([]int64(nil), outputIDs...) for _, outputFieldID := range outputFieldIDs { if typeutil.GetField(schema, outputFieldID) == nil { - return nil, fmt.Errorf("function %s output field %d not found", fn.GetName(), outputFieldID) + return nil, merr.WrapErrFunctionFailedMsg("function %s output field %d not found", fn.GetName(), outputFieldID) } } return outputFieldIDs, nil @@ -1013,14 +1014,14 @@ func functionOutputFieldIDs(schema *schemapb.CollectionSchema, fn *schemapb.Func outputNames := fn.GetOutputFieldNames() if len(outputNames) == 0 { - return nil, fmt.Errorf("function %s output fields not found", fn.GetName()) + return nil, merr.WrapErrFunctionFailedMsg("function %s output fields not found", fn.GetName()) } outputFieldIDs := make([]int64, 0, len(outputNames)) for _, outputName := range outputNames { field := typeutil.GetFieldByName(schema, outputName) if field == nil { - return nil, fmt.Errorf("function %s output field %s not found", fn.GetName(), outputName) + return nil, merr.WrapErrFunctionFailedMsg("function %s output field %s not found", fn.GetName(), outputName) } outputFieldIDs = append(outputFieldIDs, field.GetFieldID()) } @@ -1032,7 +1033,7 @@ func functionInputFieldIDs(schema *schemapb.CollectionSchema, fn *schemapb.Funct inputFieldIDs := append([]int64(nil), inputIDs...) for _, inputFieldID := range inputFieldIDs { if typeutil.GetField(schema, inputFieldID) == nil { - return nil, fmt.Errorf("function %s input field %d not found", fn.GetName(), inputFieldID) + return nil, merr.WrapErrFunctionFailedMsg("function %s input field %d not found", fn.GetName(), inputFieldID) } } return inputFieldIDs, nil @@ -1040,14 +1041,14 @@ func functionInputFieldIDs(schema *schemapb.CollectionSchema, fn *schemapb.Funct inputNames := fn.GetInputFieldNames() if len(inputNames) == 0 { - return nil, fmt.Errorf("function %s input fields not found", fn.GetName()) + return nil, merr.WrapErrFunctionFailedMsg("function %s input fields not found", fn.GetName()) } inputFieldIDs := make([]int64, 0, len(inputNames)) for _, inputName := range inputNames { field := typeutil.GetFieldByName(schema, inputName) if field == nil { - return nil, fmt.Errorf("function %s input field %s not found", fn.GetName(), inputName) + return nil, merr.WrapErrFunctionFailedMsg("function %s input field %s not found", fn.GetName(), inputName) } inputFieldIDs = append(inputFieldIDs, field.GetFieldID()) } @@ -1066,28 +1067,28 @@ func embeddingFunctionSignature(schema *schemapb.CollectionSchema, fn *schemapb. for _, fieldID := range fn.GetInputFieldIds() { field := typeutil.GetField(schema, fieldID) if field == nil { - return "", fmt.Errorf("function %s input field %d not found", fn.GetName(), fieldID) + return "", merr.WrapErrFunctionFailedMsg("function %s input field %d not found", fn.GetName(), fieldID) } writeFieldSignature(hasher, "input", field) } for _, fieldName := range fn.GetInputFieldNames() { field := typeutil.GetFieldByName(schema, fieldName) if field == nil { - return "", fmt.Errorf("function %s input field %s not found", fn.GetName(), fieldName) + return "", merr.WrapErrFunctionFailedMsg("function %s input field %s not found", fn.GetName(), fieldName) } writeFieldSignature(hasher, "input_name", field) } for _, fieldID := range fn.GetOutputFieldIds() { field := typeutil.GetField(schema, fieldID) if field == nil { - return "", fmt.Errorf("function %s output field %d not found", fn.GetName(), fieldID) + return "", merr.WrapErrFunctionFailedMsg("function %s output field %d not found", fn.GetName(), fieldID) } writeFieldSignature(hasher, "output", field) } for _, fieldName := range fn.GetOutputFieldNames() { field := typeutil.GetFieldByName(schema, fieldName) if field == nil { - return "", fmt.Errorf("function %s output field %s not found", fn.GetName(), fieldName) + return "", merr.WrapErrFunctionFailedMsg("function %s output field %s not found", fn.GetName(), fieldName) } writeFieldSignature(hasher, "output_name", field) } @@ -1192,14 +1193,14 @@ func RunWithAnalyzer( func FillFunctionFields(runners []FunctionRunner, body *msgpb.InsertRequest) (bool, error) { if body == nil { - return false, errors.New("insert request is nil") + return false, merr.WrapErrFunctionFailedMsg("insert request is nil") } changed := false for _, runner := range runners { outputFields := runner.GetOutputFields() if len(outputFields) != 1 { - return false, fmt.Errorf("function should have exactly one output field, got %d", len(outputFields)) + return false, merr.WrapErrFunctionFailedMsg("function should have exactly one output field, got %d", len(outputFields)) } outputField := outputFields[0] if HasFieldData(body.GetFieldsData(), outputField.GetFieldID()) { @@ -1239,12 +1240,12 @@ func RunFunction(runner FunctionRunner, body *msgpb.InsertRequest) (*schemapb.Fi return nil, err } if len(output) == 0 { - return nil, errors.New("function runner returned empty output") + return nil, merr.WrapErrFunctionFailedMsg("function runner returned empty output") } outputFields := runner.GetOutputFields() if len(outputFields) != 1 { - return nil, fmt.Errorf("function should have exactly one output field, got %d", len(outputFields)) + return nil, merr.WrapErrFunctionFailedMsg("function should have exactly one output field, got %d", len(outputFields)) } outputField := outputFields[0] @@ -1252,20 +1253,20 @@ func RunFunction(runner FunctionRunner, body *msgpb.InsertRequest) (*schemapb.Fi case schemapb.FunctionType_BM25: sparseArray, ok := output[0].(*schemapb.SparseFloatArray) if !ok { - return nil, errors.New("BM25 runner returned non sparse-float-vector output") + return nil, merr.WrapErrFunctionFailedMsg("BM25 runner returned non sparse-float-vector output") } return bm25.BuildSparseFieldData(outputField, sparseArray), nil case schemapb.FunctionType_MinHash: fieldData, ok := output[0].(*schemapb.FieldData) if !ok { - return nil, errors.New("MinHash runner returned non field-data output") + return nil, merr.WrapErrFunctionFailedMsg("MinHash runner returned non field-data output") } fieldData.Type = outputField.GetDataType() fieldData.FieldName = outputField.GetName() fieldData.FieldId = outputField.GetFieldID() return fieldData, nil default: - return nil, fmt.Errorf("unsupported embedding function type %s", runner.GetSchema().GetType().String()) + return nil, merr.WrapErrFunctionFailedMsg("unsupported embedding function type %s", runner.GetSchema().GetType().String()) } } @@ -1312,11 +1313,11 @@ func getStringFieldData(fieldsData []*schemapb.FieldData, fieldIDs ...int64) ([] for _, fieldID := range fieldIDs { fieldData := GetFieldData(fieldsData, fieldID) if fieldData == nil { - return nil, fmt.Errorf("field %d not found", fieldID) + return nil, merr.WrapErrFunctionFailedMsg("field %d not found", fieldID) } stringData := fieldData.GetScalars().GetStringData() if stringData == nil { - return nil, fmt.Errorf("field %d is not string data", fieldID) + return nil, merr.WrapErrFunctionFailedMsg("field %d is not string data", fieldID) } result = append(result, stringData.GetData()) } diff --git a/internal/util/function/minhash_function.go b/internal/util/function/minhash_function.go index 5301c3e0b9..aa1b3b553f 100644 --- a/internal/util/function/minhash_function.go +++ b/internal/util/function/minhash_function.go @@ -12,18 +12,16 @@ import "C" import ( "encoding/binary" - "fmt" "strconv" "strings" "sync" "unsafe" - "github.com/cockroachdb/errors" - "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/internal/util/analyzer" "github.com/milvus-io/milvus/internal/util/analyzer/canalyzer" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // MinHashFunctionRunner @@ -76,10 +74,10 @@ func NewMinHashFunctionRunner( funSchema *schemapb.FunctionSchema, ) (FunctionRunner, error) { if len(funSchema.GetOutputFieldIds()) != 1 { - return nil, fmt.Errorf("minhash function should only have one output field, but now %d", len(funSchema.GetOutputFieldIds())) + return nil, merr.WrapErrParameterInvalidMsg("minhash function should only have one output field, but now %d", len(funSchema.GetOutputFieldIds())) } if len(funSchema.GetInputFieldIds()) != 1 { - return nil, fmt.Errorf("minhash function should only have one input field, but now %d", len(funSchema.GetInputFieldIds())) + return nil, merr.WrapErrParameterInvalidMsg("minhash function should only have one input field, but now %d", len(funSchema.GetInputFieldIds())) } var inputField, outputField *schemapb.FieldSchema for _, field := range collSchema.GetFields() { @@ -92,10 +90,10 @@ func NewMinHashFunctionRunner( } } if outputField == nil { - return nil, errors.New("no output field") + return nil, merr.WrapErrParameterInvalidMsg("no output field") } if inputField == nil { - return nil, errors.New("no input field") + return nil, merr.WrapErrParameterInvalidMsg("no input field") } params := getAnalyzerParams(inputField) @@ -116,19 +114,19 @@ func NewMinHashFunctionRunner( case NumHashesKey: val, err := strconv.ParseInt(param.GetValue(), 10, 64) if err != nil { - return nil, fmt.Errorf("param num_hashes:%s is not a number", param.GetValue()) + return nil, merr.WrapErrParameterInvalidMsg("param num_hashes:%s is not a number", param.GetValue()) } if val <= 0 { - return nil, fmt.Errorf("param num_hashes:%d must be positive", val) + return nil, merr.WrapErrParameterInvalidMsg("param num_hashes:%d must be positive", val) } numHashes = int(val) case ShingleSizeKey: val, err := strconv.ParseInt(param.GetValue(), 10, 64) if err != nil { - return nil, fmt.Errorf("param shingle_size:%s is not a number", param.GetValue()) + return nil, merr.WrapErrParameterInvalidMsg("param shingle_size:%s is not a number", param.GetValue()) } if val <= 0 { - return nil, fmt.Errorf("param shingle_size:%d must be positive", val) + return nil, merr.WrapErrParameterInvalidMsg("param shingle_size:%d must be positive", val) } shingleSize = int(val) case HashFuncKey: @@ -138,7 +136,7 @@ func NewMinHashFunctionRunner( case "sha1": hashFunc = HashFuncSHA1 default: - return nil, fmt.Errorf("unknown hash function: %s", param.GetValue()) + return nil, merr.WrapErrParameterInvalidMsg("unknown hash function: %s", param.GetValue()) } case TokenLevelKey: switch strings.ToLower(param.GetValue()) { @@ -147,12 +145,12 @@ func NewMinHashFunctionRunner( case "word": useCharToken = false default: - return nil, fmt.Errorf("unknown token_level: %s (expected 'char' or 'word')", param.GetValue()) + return nil, merr.WrapErrParameterInvalidMsg("unknown token_level: %s (expected 'char' or 'word')", param.GetValue()) } case SeedKey: val, err := strconv.ParseInt(param.GetValue(), 10, 64) if err != nil { - return nil, fmt.Errorf("param seed:%s is not a number", param.GetValue()) + return nil, merr.WrapErrParameterInvalidMsg("param seed:%s is not a number", param.GetValue()) } seed = int(val) } @@ -171,7 +169,7 @@ func NewMinHashFunctionRunner( } } if outputDim <= 0 || outputDim%32 != 0 { - return nil, fmt.Errorf("minhash function output field '%s' dim not found or invalid(dim > 0, dim %% 32 == 0)", outputField.GetName()) + return nil, merr.WrapErrParameterInvalidMsg("minhash function output field '%s' dim not found or invalid(dim > 0, dim %% 32 == 0)", outputField.GetName()) } numHashes = int(outputDim / 32) funSchema.Params = append(funSchema.Params, &commonpb.KeyValuePair{ @@ -203,10 +201,10 @@ func ValidateMinHashFunction(collSchema *schemapb.CollectionSchema, funSchema *s // check input field count if len(funSchema.GetInputFieldNames()) != 1 { - return fmt.Errorf("minhash function should only have one input field, but now %d", len(funSchema.GetInputFieldNames())) + return merr.WrapErrParameterInvalidMsg("minhash function should only have one input field, but now %d", len(funSchema.GetInputFieldNames())) } if len(funSchema.GetOutputFieldNames()) != 1 { - return fmt.Errorf("minhash function should only have one output field, but now %d", len(funSchema.GetOutputFieldNames())) + return merr.WrapErrParameterInvalidMsg("minhash function should only have one output field, but now %d", len(funSchema.GetOutputFieldNames())) } // Find fields by name (since FieldIDs may not be assigned yet during validation) @@ -223,14 +221,14 @@ func ValidateMinHashFunction(collSchema *schemapb.CollectionSchema, funSchema *s } if inputField == nil { - return fmt.Errorf("minhash function input field '%s' not found", inputFieldName) + return merr.WrapErrParameterInvalidMsg("minhash function input field '%s' not found", inputFieldName) } if outputField == nil { - return fmt.Errorf("minhash function output field '%s' not found", outputFieldName) + return merr.WrapErrParameterInvalidMsg("minhash function output field '%s' not found", outputFieldName) } if inputField.GetDataType() != schemapb.DataType_VarChar && inputField.GetDataType() != schemapb.DataType_String { - return fmt.Errorf("minhash function input field '%s' is not string type, is %s", + return merr.WrapErrParameterInvalidMsg("minhash function input field '%s' is not string type, is %s", inputFieldName, inputField.GetDataType()) } // check function params @@ -240,45 +238,45 @@ func ValidateMinHashFunction(collSchema *schemapb.CollectionSchema, funSchema *s case NumHashesKey: val, err := strconv.ParseInt(param.GetValue(), 10, 64) if err != nil { - return fmt.Errorf("param num_hashes:%s is not a number", param.GetValue()) + return merr.WrapErrParameterInvalidMsg("param num_hashes:%s is not a number", param.GetValue()) } numHashes = int(val) if numHashes <= 0 { - return fmt.Errorf("param num_hashes:%d must be positive", numHashes) + return merr.WrapErrParameterInvalidMsg("param num_hashes:%d must be positive", numHashes) } case ShingleSizeKey: val, err := strconv.ParseInt(param.GetValue(), 10, 64) if err != nil { - return fmt.Errorf("param shingle_size:%s is not a number", param.GetValue()) + return merr.WrapErrParameterInvalidMsg("param shingle_size:%s is not a number", param.GetValue()) } if val <= 0 { - return fmt.Errorf("param shingle_size:%d must be positive", val) + return merr.WrapErrParameterInvalidMsg("param shingle_size:%d must be positive", val) } case HashFuncKey: switch strings.ToLower(param.GetValue()) { case "xxhash", "xxhash64", "sha1": // valid hash function default: - return fmt.Errorf("unknown hash function: %s (expected 'xxhash64' or 'sha1')", param.GetValue()) + return merr.WrapErrParameterInvalidMsg("unknown hash function: %s (expected 'xxhash64' or 'sha1')", param.GetValue()) } case TokenLevelKey: switch strings.ToLower(param.GetValue()) { case "char", "character", "word": // valid token level default: - return fmt.Errorf("unknown token_level: %s (expected 'char' or 'word')", param.GetValue()) + return merr.WrapErrParameterInvalidMsg("unknown token_level: %s (expected 'char' or 'word')", param.GetValue()) } case SeedKey: _, err := strconv.ParseInt(param.GetValue(), 10, 64) if err != nil { - return fmt.Errorf("param seed:%s is not a number", param.GetValue()) + return merr.WrapErrParameterInvalidMsg("param seed:%s is not a number", param.GetValue()) } } } // check numHashes with output field var outputDim int64 = -1 if outputField.GetDataType() != schemapb.DataType_BinaryVector { - return fmt.Errorf("minhash function output field '%s' is not binary vector type", outputFieldName) + return merr.WrapErrParameterInvalidMsg("minhash function output field '%s' is not binary vector type", outputFieldName) } for _, param := range outputField.GetTypeParams() { if param.GetKey() == "dim" { @@ -293,11 +291,11 @@ func ValidateMinHashFunction(collSchema *schemapb.CollectionSchema, funSchema *s if numHashes > 0 { expectedDim := int64(numHashes * 32) // binary vector, each hash is 4 bytes (32 bits), but stored as 8 bits in binary vector if outputDim != expectedDim { - return fmt.Errorf("minhash function output field '%s' dim %d does not match expected dim %d (numHashes %d * one minhash signature size of 32bit)", outputFieldName, outputDim, expectedDim, numHashes) + return merr.WrapErrParameterInvalidMsg("minhash function output field '%s' dim %d does not match expected dim %d (numHashes %d * one minhash signature size of 32bit)", outputFieldName, outputDim, expectedDim, numHashes) } } else { if outputDim%32 != 0 { - return fmt.Errorf("minhash function output field '%s' dim %d is not multiple of 32 (one minhash signature size)", outputFieldName, outputDim) + return merr.WrapErrParameterInvalidMsg("minhash function output field '%s' dim %d is not multiple of 32 (one minhash signature size)", outputFieldName, outputDim) } } // else no numHashes specified, skip output field validation @@ -345,16 +343,16 @@ func (m *MinHashFunctionRunner) BatchRun(inputs ...any) ([]any, error) { defer m.mu.RUnlock() if m.closed { - return nil, errors.New("MinHash function closed") + return nil, merr.WrapErrServiceInternalMsg("MinHash function closed") } if len(inputs) > 1 { - return nil, errors.New("MinHash function received more than one input column") + return nil, merr.WrapErrParameterInvalidMsg("MinHash function received more than one input column") } text, ok := inputs[0].([]string) if !ok { - return nil, errors.New("MinHash function input not string list") + return nil, merr.WrapErrParameterInvalidMsg("MinHash function input not string list") } rowNum := len(text) diff --git a/internal/util/function/models/ali/ali_dashscope_client.go b/internal/util/function/models/ali/ali_dashscope_client.go index 233682c3ef..9b4d09f2de 100644 --- a/internal/util/function/models/ali/ali_dashscope_client.go +++ b/internal/util/function/models/ali/ali_dashscope_client.go @@ -20,9 +20,8 @@ import ( "fmt" "sort" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus/internal/util/function/models" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type Input struct { @@ -92,11 +91,11 @@ func NewAliDashScopeEmbeddingClient(apiKey string, url string) *AliDashScopeEmbe func (c *AliDashScopeEmbedding) Check() error { if c.apiKey == "" { - return errors.New("api key is empty") + return merr.WrapErrParameterInvalidMsg("api key is empty") } if c.url == "" { - return errors.New("url is empty") + return merr.WrapErrParameterInvalidMsg("url is empty") } return nil } diff --git a/internal/util/function/models/cohere/cohere_client.go b/internal/util/function/models/cohere/cohere_client.go index 311d75bb2f..7ce7e88b08 100644 --- a/internal/util/function/models/cohere/cohere_client.go +++ b/internal/util/function/models/cohere/cohere_client.go @@ -21,6 +21,7 @@ import ( "sort" "github.com/milvus-io/milvus/internal/util/function/models" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type CohereClient struct { @@ -29,7 +30,7 @@ type CohereClient struct { func NewCohereClient(apiKey string) (*CohereClient, error) { if apiKey == "" { - return nil, fmt.Errorf("missing credentials config or configure the %s environment variable in the Milvus service", models.CohereAIAKEnvStr) + return nil, merr.WrapErrParameterInvalidMsg("missing credentials config or configure the %s environment variable in the Milvus service", models.CohereAIAKEnvStr) } return &CohereClient{ apiKey: apiKey, diff --git a/internal/util/function/models/common.go b/internal/util/function/models/common.go index 27faf246ce..72b590e24b 100644 --- a/internal/util/function/models/common.go +++ b/internal/util/function/models/common.go @@ -34,6 +34,7 @@ import ( "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/internal/util/credentials" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type TextEmbeddingMode int @@ -211,11 +212,11 @@ func ParseAKAndURL(credentials *credentials.Credentials, params []*commonpb.KeyV func ParseAndCheckFieldDim(dimStr string, fieldDim int64, fieldName string) (int64, error) { dim, err := strconv.ParseInt(dimStr, 10, 64) if err != nil { - return 0, fmt.Errorf("dimension [%s] provided in Function params is not a valid int", dimStr) + return 0, merr.WrapErrParameterInvalidMsg("dimension [%s] provided in Function params is not a valid int", dimStr) } if dim != 0 && dim != fieldDim { - return 0, fmt.Errorf("function output field:[%s]'s dimension [%d] does not match the dimension [%d] provided in Function params", fieldName, fieldDim, dim) + return 0, merr.WrapErrParameterInvalidMsg("function output field:[%s]'s dimension [%d] does not match the dimension [%d] provided in Function params", fieldName, fieldDim, dim) } return dim, nil } @@ -267,10 +268,10 @@ func NewBaseURL(endpoint string) (*url.URL, error) { return nil, err } if base.Scheme != "http" && base.Scheme != "https" { - return nil, fmt.Errorf("endpoint: [%s] is not a valid http/https link", endpoint) + return nil, merr.WrapErrParameterInvalidMsg("endpoint: [%s] is not a valid http/https link", endpoint) } if base.Host == "" { - return nil, fmt.Errorf("endpoint: [%s] is not a valid http/https link", endpoint) + return nil, merr.WrapErrParameterInvalidMsg("endpoint: [%s] is not a valid http/https link", endpoint) } return base, nil } @@ -303,7 +304,7 @@ func PostRequest[T Response](req any, url string, headers map[string]string, tim var res T err = json.Unmarshal(body, &res) if err != nil { - return nil, fmt.Errorf("call service failed, unmarshal response failed, errs:[%v]", err) + return nil, merr.Wrap(err, "call service failed, unmarshal response failed") } return &res, err } @@ -311,17 +312,25 @@ func PostRequest[T Response](req any, url string, headers map[string]string, tim func send(req *http.Request) ([]byte, error) { resp, err := http.DefaultClient.Do(req) //nolint:gosec // URL is constructed from configured model endpoints, not user input if err != nil { - return nil, fmt.Errorf("call service failed, errs:[%v]", err) + // transport failure (connection refused / timeout / DNS) is transient + return nil, merr.WrapErrServiceUnavailable(err.Error(), "call service failed") } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { - return nil, fmt.Errorf("call service failed, read response failed, errs:[%v]", err) + // interrupted while reading the response body is transient + return nil, merr.WrapErrServiceUnavailable(err.Error(), "call service failed, read response failed") } if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("call service failed, errs:[%s, %s]", resp.Status, body) + msg := fmt.Sprintf("call service failed, errs:[%s, %s]", resp.Status, body) + // 429 / 5xx are transient and retryable; other 4xx are caused by the + // request/config (bad key, bad input) and are permanent. + if resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode >= http.StatusInternalServerError { + return nil, merr.WrapErrServiceUnavailable(msg) + } + return nil, merr.WrapErrFunctionFailedMsg("%s", msg) } return body, nil } diff --git a/internal/util/function/models/gemini/gemini_client.go b/internal/util/function/models/gemini/gemini_client.go index 32afed9883..34b3d2dd12 100644 --- a/internal/util/function/models/gemini/gemini_client.go +++ b/internal/util/function/models/gemini/gemini_client.go @@ -17,10 +17,10 @@ package gemini import ( - "fmt" "strings" "github.com/milvus-io/milvus/internal/util/function/models" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type GeminiClient struct { @@ -29,7 +29,7 @@ type GeminiClient struct { func NewGeminiClient(apiKey string) (*GeminiClient, error) { if apiKey == "" { - return nil, fmt.Errorf("missing credentials config or configure the %s environment variable in the Milvus service", models.GeminiAKEnvStr) + return nil, merr.WrapErrParameterInvalidMsg("missing credentials config or configure the %s environment variable in the Milvus service", models.GeminiAKEnvStr) } return &GeminiClient{ apiKey: apiKey, diff --git a/internal/util/function/models/openai/openai_client.go b/internal/util/function/models/openai/openai_client.go index 124ecb91dd..31d76cfbe6 100644 --- a/internal/util/function/models/openai/openai_client.go +++ b/internal/util/function/models/openai/openai_client.go @@ -21,9 +21,8 @@ import ( "net/url" "sort" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus/internal/util/function/models" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type EmbeddingRequest struct { @@ -99,11 +98,11 @@ type openAIBase struct { func (c *openAIBase) Check() error { if c.apiKey == "" { - return errors.New("api key is empty") + return merr.WrapErrParameterInvalidMsg("api key is empty") } if c.url == "" { - return errors.New("url is empty") + return merr.WrapErrParameterInvalidMsg("url is empty") } return nil } diff --git a/internal/util/function/models/siliconflow/siliconflow_client.go b/internal/util/function/models/siliconflow/siliconflow_client.go index a937a9aa75..02cd92d8e1 100644 --- a/internal/util/function/models/siliconflow/siliconflow_client.go +++ b/internal/util/function/models/siliconflow/siliconflow_client.go @@ -21,6 +21,7 @@ import ( "sort" "github.com/milvus-io/milvus/internal/util/function/models" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type SiliconflowClient struct { @@ -29,7 +30,7 @@ type SiliconflowClient struct { func NewSiliconflowClient(apiKey string) (*SiliconflowClient, error) { if apiKey == "" { - return nil, fmt.Errorf("missing credentials conifg or configure the %s environment variable in the Milvus service", models.SiliconflowAKEnvStr) + return nil, merr.WrapErrParameterInvalidMsg("missing credentials conifg or configure the %s environment variable in the Milvus service", models.SiliconflowAKEnvStr) } return &SiliconflowClient{ diff --git a/internal/util/function/models/vertexai/vertexai_client.go b/internal/util/function/models/vertexai/vertexai_client.go index 94e3160c2c..7e2469c7f0 100644 --- a/internal/util/function/models/vertexai/vertexai_client.go +++ b/internal/util/function/models/vertexai/vertexai_client.go @@ -20,10 +20,10 @@ import ( "context" "fmt" - "github.com/cockroachdb/errors" "golang.org/x/oauth2/google" "github.com/milvus-io/milvus/internal/util/function/models" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type Instance struct { @@ -111,13 +111,13 @@ func NewVertexAIEmbedding(url string, jsonKey []byte, scopes string, token strin func (c *VertexAIEmbedding) Check() error { if c.url == "" { - return errors.New("VertexAI embedding url is empty") + return merr.WrapErrParameterInvalidMsg("VertexAI embedding url is empty") } if len(c.jsonKey) == 0 { - return errors.New("jsonKey is empty") + return merr.WrapErrParameterInvalidMsg("jsonKey is empty") } if c.scopes == "" { - return errors.New("Scopes param is empty") + return merr.WrapErrParameterInvalidMsg("Scopes param is empty") } return nil } @@ -126,11 +126,11 @@ func (c *VertexAIEmbedding) getAccessToken() (string, error) { ctx := context.Background() creds, err := google.CredentialsFromJSON(ctx, c.jsonKey, c.scopes) if err != nil { - return "", fmt.Errorf("failed to find credentials: %v", err) + return "", merr.Wrap(err, "failed to find credentials") } token, err := creds.TokenSource.Token() if err != nil { - return "", fmt.Errorf("failed to get token: %v", err) + return "", merr.Wrap(err, "failed to get token") } return token.AccessToken, nil } diff --git a/internal/util/function/models/vllm/vllm_client.go b/internal/util/function/models/vllm/vllm_client.go index ff43502c96..131a4181e6 100644 --- a/internal/util/function/models/vllm/vllm_client.go +++ b/internal/util/function/models/vllm/vllm_client.go @@ -23,6 +23,7 @@ import ( "sort" "github.com/milvus-io/milvus/internal/util/function/models" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) func NewBaseURL(endpoint string) (*url.URL, error) { @@ -31,10 +32,10 @@ func NewBaseURL(endpoint string) (*url.URL, error) { return nil, err } if base.Scheme != "http" && base.Scheme != "https" { - return nil, fmt.Errorf("endpoint: [%s] is not a valid http/https link", endpoint) + return nil, merr.WrapErrParameterInvalidMsg("endpoint: [%s] is not a valid http/https link", endpoint) } if base.Host == "" { - return nil, fmt.Errorf("endpoint: [%s] is not a valid http/https link", endpoint) + return nil, merr.WrapErrParameterInvalidMsg("endpoint: [%s] is not a valid http/https link", endpoint) } return base, nil } diff --git a/internal/util/function/models/voyageai/voyageai_client.go b/internal/util/function/models/voyageai/voyageai_client.go index 12be125ffa..1c0849f7d2 100644 --- a/internal/util/function/models/voyageai/voyageai_client.go +++ b/internal/util/function/models/voyageai/voyageai_client.go @@ -21,9 +21,8 @@ import ( "maps" "sort" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus/internal/util/function/models" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type VoyageAIClient struct { @@ -32,7 +31,7 @@ type VoyageAIClient struct { func NewVoyageAIClient(apiKey string) (*VoyageAIClient, error) { if apiKey == "" { - return nil, fmt.Errorf("missing credentials config or configure the %s environment variable in the Milvus service", models.VoyageAIAKEnvStr) + return nil, merr.WrapErrParameterInvalidMsg("missing credentials config or configure the %s environment variable in the Milvus service", models.VoyageAIAKEnvStr) } return &VoyageAIClient{ apiKey: apiKey, @@ -114,18 +113,18 @@ func newVoyageAIEmbedding(apiKey string, url string) *voyageAIEmbedding { func (c *voyageAIEmbedding) Check() error { if c.apiKey == "" { - return errors.New("api key is empty") + return merr.WrapErrParameterInvalidMsg("api key is empty") } if c.url == "" { - return errors.New("url is empty") + return merr.WrapErrParameterInvalidMsg("url is empty") } return nil } func (c *voyageAIEmbedding) embedding(modelName string, texts []string, dim int, textType string, outputType string, truncation bool, headers map[string]string, timeoutSec int64) (any, error) { if outputType != "float" && outputType != "int8" { - return nil, fmt.Errorf("Voyageai: unsupported output type: [%s], only support float and int8", outputType) //nolint:staticcheck // starts with proper noun + return nil, merr.WrapErrParameterInvalidMsg("Voyageai: unsupported output type: [%s], only support float and int8", outputType) //nolint:staticcheck // starts with proper noun } r := EmbeddingRequest{ Model: modelName, diff --git a/internal/util/function/models/zilliz/zilliz_client.go b/internal/util/function/models/zilliz/zilliz_client.go index 38afae0c51..34bd35a59e 100644 --- a/internal/util/function/models/zilliz/zilliz_client.go +++ b/internal/util/function/models/zilliz/zilliz_client.go @@ -20,7 +20,6 @@ import ( "bytes" "context" "encoding/binary" - "fmt" "strconv" "sync" "time" @@ -36,6 +35,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/modelservicepb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type clientConfig struct { @@ -71,14 +71,14 @@ type clientManager struct { func loadConfig(config map[string]string) (*clientConfig, error) { endpoint := config["endpoint"] if endpoint == "" { - return nil, fmt.Errorf("zilliz client config error, lost endpoint config") + return nil, merr.WrapErrParameterInvalidMsg("zilliz client config error, lost endpoint config") } enableTLSStr := config["enableTLS"] enableTLS := false if enableTLSStr != "" { var err error if enableTLS, err = strconv.ParseBool(enableTLSStr); err != nil { - return nil, fmt.Errorf("zilliz client config err: enableTLS:%s is not bool, err:%w", enableTLSStr, err) + return nil, merr.Wrapf(err, "zilliz client config err: enableTLS:%s is not bool", enableTLSStr) } } if enableTLS { @@ -192,7 +192,8 @@ func NewZilliClient(modelDeploymentID string, clusterID string, dbName string, i } conn, err := mgr.GetConn(clientConf) if err != nil { - return nil, fmt.Errorf("connect model serving failed, err:%w", err) + // failing to connect to the model serving backend is transient + return nil, merr.WrapErrServiceUnavailable(err.Error(), "connect model serving failed") } return &ZillizClient{ modelDeploymentID: modelDeploymentID, @@ -280,7 +281,7 @@ func (c *ZillizClient) Highlight(ctx context.Context, query string, texts []stri } if len(sentences) != len(retScores) { - return nil, nil, fmt.Errorf("sentences length %d does not match scores length %d", len(sentences), len(retScores)) + return nil, nil, merr.WrapErrFunctionFailedMsg("sentences length %d does not match scores length %d", len(sentences), len(retScores)) } highlights = append(highlights, sentences) scores = append(scores, retScores) diff --git a/internal/util/function/multi_analyzer_bm25_function.go b/internal/util/function/multi_analyzer_bm25_function.go index 17ad156c54..e925cc7f6a 100644 --- a/internal/util/function/multi_analyzer_bm25_function.go +++ b/internal/util/function/multi_analyzer_bm25_function.go @@ -20,7 +20,6 @@ package function import ( "encoding/json" - "fmt" "sync" "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb" @@ -74,12 +73,12 @@ func NewMultiAnalyzerBM25FunctionRunner(coll *schemapb.CollectionSchema, schema mfield, ok := m["by_field"] if !ok { - return nil, fmt.Errorf("bm25 function with multi analyzer must have by_field param in multi_analyzer_params") + return nil, merr.WrapErrParameterInvalidMsg("bm25 function with multi analyzer must have by_field param in multi_analyzer_params") } err = json.Unmarshal(mfield, &mFileName) if err != nil { - return nil, fmt.Errorf("bm25 function with multi analyzer by_field must be string but now: %s", mfield) + return nil, merr.WrapErrParameterInvalidMsg("bm25 function with multi analyzer by_field must be string but now: %s", mfield) } for _, field := range coll.GetFields() { @@ -89,33 +88,33 @@ func NewMultiAnalyzerBM25FunctionRunner(coll *schemapb.CollectionSchema, schema } if len(runner.inputFields) != 2 { - return nil, fmt.Errorf("bm25 function with multi analyzer must have two input fields") + return nil, merr.WrapErrParameterInvalidMsg("bm25 function with multi analyzer must have two input fields") } if value, ok := m["alias"]; ok { mapping := map[string]string{} err = json.Unmarshal(value, &mapping) if err != nil { - return nil, fmt.Errorf("bm25 function with multi analyzer mapping must be string map but now: %s", value) + return nil, merr.WrapErrParameterInvalidMsg("bm25 function with multi analyzer mapping must be string map but now: %s", value) } runner.alias = mapping } analyzers, ok := m["analyzers"] if !ok { - return nil, fmt.Errorf("bm25 function with multi analyzer must have analyzers param in multi_analyzer_params") + return nil, merr.WrapErrParameterInvalidMsg("bm25 function with multi analyzer must have analyzers param in multi_analyzer_params") } var analyzersParam map[string]json.RawMessage err = json.Unmarshal(analyzers, &analyzersParam) if err != nil { - return nil, fmt.Errorf("bm25 function unmarshal multi_analyzer_params analyzers failed : %s", err.Error()) + return nil, merr.Wrap(err, "bm25 function unmarshal multi_analyzer_params analyzers failed") } for name, param := range analyzersParam { analyzer, err := analyzer.NewAnalyzer(string(param), "") if err != nil { - return nil, fmt.Errorf("bm25 function create analyzer %s failed with error: %s", name, err.Error()) + return nil, merr.Wrapf(err, "bm25 function create analyzer %s failed", name) } runner.analyzers[name] = analyzer } @@ -187,25 +186,25 @@ func (v *MultiAnalyzerBM25FunctionRunner) BatchRun(inputs ...any) ([]any, error) defer v.mu.RUnlock() if v.closed { - return nil, fmt.Errorf("analyzer receview request after function closed") + return nil, merr.WrapErrServiceInternalMsg("analyzer receview request after function closed") } if len(inputs) != 2 { - return nil, fmt.Errorf("BM25 function with multi analyzer must received two input column") + return nil, merr.WrapErrParameterInvalidMsg("BM25 function with multi analyzer must received two input column") } text, ok := inputs[0].([]string) if !ok { - return nil, fmt.Errorf("BM25 function with multi analyzer text input must be string list") + return nil, merr.WrapErrParameterInvalidMsg("BM25 function with multi analyzer text input must be string list") } analyzer, ok := inputs[1].([]string) if !ok { - return nil, fmt.Errorf("BM25 function with multi analyzer input analyzer name must be string list") + return nil, merr.WrapErrParameterInvalidMsg("BM25 function with multi analyzer input analyzer name must be string list") } if len(text) != len(analyzer) { - return nil, fmt.Errorf("BM25 function with multi analyzer input text and analyzer name must have same length") + return nil, merr.WrapErrParameterInvalidMsg("BM25 function with multi analyzer input text and analyzer name must have same length") } rowNum := len(text) @@ -285,25 +284,25 @@ func (v *MultiAnalyzerBM25FunctionRunner) BatchAnalyze(withDetail bool, withHash defer v.mu.RUnlock() if v.closed { - return nil, fmt.Errorf("analyzer receview request after function closed") + return nil, merr.WrapErrServiceInternalMsg("analyzer receview request after function closed") } if len(inputs) != 2 { - return nil, fmt.Errorf("multi analyzer must received two input column(text, analyzer_name)") + return nil, merr.WrapErrParameterInvalidMsg("multi analyzer must received two input column(text, analyzer_name)") } text, ok := inputs[0].([]string) if !ok { - return nil, fmt.Errorf("multi analyzer text input must be string list") + return nil, merr.WrapErrParameterInvalidMsg("multi analyzer text input must be string list") } analyzer, ok := inputs[1].([]string) if !ok { - return nil, fmt.Errorf("multi analyzer input analyzer name must be string list") + return nil, merr.WrapErrParameterInvalidMsg("multi analyzer input analyzer name must be string list") } if len(text) != len(analyzer) { - return nil, fmt.Errorf("multi analyzer input text and analyzer name must have same length") + return nil, merr.WrapErrParameterInvalidMsg("multi analyzer input text and analyzer name must have same length") } rowNum := len(text) diff --git a/internal/util/function/rerank/ali_rerank_provider.go b/internal/util/function/rerank/ali_rerank_provider.go index 1a6e515440..980866a9bf 100644 --- a/internal/util/function/rerank/ali_rerank_provider.go +++ b/internal/util/function/rerank/ali_rerank_provider.go @@ -20,13 +20,13 @@ package rerank import ( "context" - "fmt" "strings" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus/internal/util/credentials" "github.com/milvus-io/milvus/internal/util/function/models" "github.com/milvus-io/milvus/internal/util/function/models/ali" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type aliProvider struct { @@ -61,7 +61,7 @@ func newAliProvider(params []*commonpb.KeyValuePair, conf map[string]string, cre } } if modelName == "" { - return nil, fmt.Errorf("ali rerank model name is required") + return nil, merr.WrapErrParameterMissingMsg("ali rerank model name is required") } provider := aliProvider{ baseProvider: baseProvider{batchSize: maxBatch}, diff --git a/internal/util/function/rerank/cohere_rerank_provider.go b/internal/util/function/rerank/cohere_rerank_provider.go index 1dcd1e61bd..4f5f80c055 100644 --- a/internal/util/function/rerank/cohere_rerank_provider.go +++ b/internal/util/function/rerank/cohere_rerank_provider.go @@ -20,7 +20,6 @@ package rerank import ( "context" - "fmt" "strconv" "strings" @@ -28,6 +27,7 @@ import ( "github.com/milvus-io/milvus/internal/util/credentials" "github.com/milvus-io/milvus/internal/util/function/models" "github.com/milvus-io/milvus/internal/util/function/models/cohere" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type cohereProvider struct { @@ -65,7 +65,7 @@ func newCohereProvider(params []*commonpb.KeyValuePair, conf map[string]string, case models.MaxTKsPerDocParamKey: maxTokensPerDoc, err := strconv.Atoi(param.Value) if err != nil { - return nil, fmt.Errorf("[%s param's value: %s] is not a valid number", models.MaxTKsPerDocParamKey, param.Value) + return nil, merr.WrapErrParameterInvalidMsg("[%s param's value: %s] is not a valid number", models.MaxTKsPerDocParamKey, param.Value) } else { modelParams[models.MaxTKsPerDocParamKey] = maxTokensPerDoc } @@ -73,7 +73,7 @@ func newCohereProvider(params []*commonpb.KeyValuePair, conf map[string]string, } } if modelName == "" { - return nil, fmt.Errorf("cohere rerank model name is required") + return nil, merr.WrapErrParameterMissingMsg("cohere rerank model name is required") } provider := cohereProvider{ baseProvider: baseProvider{batchSize: maxBatch}, diff --git a/internal/util/function/rerank/model_function.go b/internal/util/function/rerank/model_function.go index 0e8aa87a56..0074cdf75b 100644 --- a/internal/util/function/rerank/model_function.go +++ b/internal/util/function/rerank/model_function.go @@ -20,13 +20,13 @@ package rerank import ( "context" - "fmt" "strconv" "strings" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus/internal/util/credentials" "github.com/milvus-io/milvus/internal/util/function/models" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -43,10 +43,10 @@ const ( func parseMaxBatch(maxBatch string) (int, error) { if batch, err := strconv.Atoi(maxBatch); err != nil { - return -1, fmt.Errorf("[%s param's value: %s] is not a valid number", models.MaxClientBatchSizeParamKey, maxBatch) + return -1, merr.WrapErrParameterInvalidMsg("[%s param's value: %s] is not a valid number", models.MaxClientBatchSizeParamKey, maxBatch) } else { if batch <= 0 { - return -1, fmt.Errorf("[%s param's value: %s] must be greater than 0", models.MaxClientBatchSizeParamKey, maxBatch) + return -1, merr.WrapErrParameterInvalidMsg("[%s param's value: %s] must be greater than 0", models.MaxClientBatchSizeParamKey, maxBatch) } return batch, nil } @@ -73,7 +73,7 @@ func NewModelProvider(params []*commonpb.KeyValuePair, extraInfo *models.ModelEx provider := strings.ToLower(param.Value) conf := paramtable.Get().FunctionCfg.GetRerankModelProviders(provider) if !models.IsEnable(conf) { - return nil, fmt.Errorf("rerank provider: [%s] is disabled", provider) + return nil, merr.WrapErrParameterInvalidMsg("rerank provider: [%s] is disabled", provider) } credentials := credentials.NewCredentials(paramtable.Get().CredentialCfg.GetCredentials()) switch provider { @@ -93,9 +93,9 @@ func NewModelProvider(params []*commonpb.KeyValuePair, extraInfo *models.ModelEx conf := paramtable.Get().FunctionCfg.ZillizProviders.GetValue() return newZillizProvider(params, conf, extraInfo) default: - return nil, fmt.Errorf("unknown rerank model provider:%s", param.Value) + return nil, merr.WrapErrParameterInvalidMsg("unknown rerank model provider:%s", param.Value) } } } - return nil, fmt.Errorf("lost rerank params:%s ", providerParamName) + return nil, merr.WrapErrParameterInvalidMsg("lost rerank params:%s ", providerParamName) } diff --git a/internal/util/function/rerank/siliconflow_rerank_provider.go b/internal/util/function/rerank/siliconflow_rerank_provider.go index 843dacce06..4e42d3d1a3 100644 --- a/internal/util/function/rerank/siliconflow_rerank_provider.go +++ b/internal/util/function/rerank/siliconflow_rerank_provider.go @@ -20,7 +20,6 @@ package rerank import ( "context" - "fmt" "strconv" "strings" @@ -28,6 +27,7 @@ import ( "github.com/milvus-io/milvus/internal/util/credentials" "github.com/milvus-io/milvus/internal/util/function/models" "github.com/milvus-io/milvus/internal/util/function/models/siliconflow" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type siliconflowProvider struct { @@ -67,20 +67,20 @@ func newSiliconflowProvider(params []*commonpb.KeyValuePair, conf map[string]str case models.MaxChunksPerDocParamKey: maxChunksPerDoc, err := strconv.Atoi(param.Value) if err != nil { - return nil, fmt.Errorf("[%s param's value: %s] is not a valid number", models.MaxChunksPerDocParamKey, param.Value) + return nil, merr.WrapErrParameterInvalidMsg("[%s param's value: %s] is not a valid number", models.MaxChunksPerDocParamKey, param.Value) } rankParams[models.MaxChunksPerDocParamKey] = maxChunksPerDoc case models.OverlapTokensParamKey: overlapTokens, err := strconv.Atoi(param.Value) if err != nil { - return nil, fmt.Errorf("[%s param's value: %s] is not a valid number", models.OverlapTokensParamKey, param.Value) + return nil, merr.WrapErrParameterInvalidMsg("[%s param's value: %s] is not a valid number", models.OverlapTokensParamKey, param.Value) } rankParams[models.OverlapTokensParamKey] = overlapTokens default: } } if modelName == "" { - return nil, fmt.Errorf("siliconflow rerank model name is required") + return nil, merr.WrapErrParameterMissingMsg("siliconflow rerank model name is required") } provider := siliconflowProvider{ diff --git a/internal/util/function/rerank/tei_rerank_provider.go b/internal/util/function/rerank/tei_rerank_provider.go index 3eda5bf9f1..18028beacf 100644 --- a/internal/util/function/rerank/tei_rerank_provider.go +++ b/internal/util/function/rerank/tei_rerank_provider.go @@ -20,7 +20,6 @@ package rerank import ( "context" - "fmt" "strconv" "strings" @@ -28,6 +27,7 @@ import ( "github.com/milvus-io/milvus/internal/util/credentials" "github.com/milvus-io/milvus/internal/util/function/models" "github.com/milvus-io/milvus/internal/util/function/models/tei" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type teiProvider struct { @@ -57,13 +57,13 @@ func newTeiProvider(params []*commonpb.KeyValuePair, conf map[string]string, cre } case models.TruncateParamKey: if teiTrun, err := strconv.ParseBool(param.Value); err != nil { - return nil, fmt.Errorf("Rerank params error, %s: %s is not bool type", models.TruncateParamKey, param.Value) + return nil, merr.WrapErrParameterInvalidMsg("Rerank params error, %s: %s is not bool type", models.TruncateParamKey, param.Value) } else { truncateParams[models.TruncateParamKey] = teiTrun } case models.TruncationDirectionParamKey: if truncationDirection := param.Value; truncationDirection != "Left" && truncationDirection != "Right" { - return nil, fmt.Errorf("[%s param's value: %s] is not invalid, only supports [Left/Right]", models.TruncationDirectionParamKey, param.Value) + return nil, merr.WrapErrParameterInvalidMsg("[%s param's value: %s] is not invalid, only supports [Left/Right]", models.TruncationDirectionParamKey, param.Value) } else { truncateParams[models.TruncationDirectionParamKey] = truncationDirection } diff --git a/internal/util/function/rerank/vllm_rerank_provider.go b/internal/util/function/rerank/vllm_rerank_provider.go index cfda1b31ed..a0e9621f58 100644 --- a/internal/util/function/rerank/vllm_rerank_provider.go +++ b/internal/util/function/rerank/vllm_rerank_provider.go @@ -20,7 +20,6 @@ package rerank import ( "context" - "fmt" "strconv" "strings" @@ -28,6 +27,7 @@ import ( "github.com/milvus-io/milvus/internal/util/credentials" "github.com/milvus-io/milvus/internal/util/function/models" "github.com/milvus-io/milvus/internal/util/function/models/vllm" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type vllmProvider struct { @@ -52,7 +52,7 @@ func newVllmProvider(params []*commonpb.KeyValuePair, conf map[string]string, cr } case models.VllmTruncateParamName: if vllmTrun, err := strconv.ParseInt(param.Value, 10, 64); err != nil { - return nil, fmt.Errorf("Rerank params error, %s: %s is not a number", models.VllmTruncateParamName, param.Value) + return nil, merr.WrapErrParameterInvalidMsg("Rerank params error, %s: %s is not a number", models.VllmTruncateParamName, param.Value) } else { truncateParams[models.VllmTruncateParamName] = vllmTrun } @@ -60,7 +60,7 @@ func newVllmProvider(params []*commonpb.KeyValuePair, conf map[string]string, cr } } if endpoint == "" { - return nil, fmt.Errorf("rerank function lost params endpoint") + return nil, merr.WrapErrParameterInvalidMsg("rerank function lost params endpoint") } apiKey, _, err := models.ParseAKAndURL(credentials, params, conf, "", &models.ModelExtraInfo{}) diff --git a/internal/util/function/rerank/voyageai_rerank_provider.go b/internal/util/function/rerank/voyageai_rerank_provider.go index 7ebb26065c..cda583eb20 100644 --- a/internal/util/function/rerank/voyageai_rerank_provider.go +++ b/internal/util/function/rerank/voyageai_rerank_provider.go @@ -20,7 +20,6 @@ package rerank import ( "context" - "fmt" "strconv" "strings" @@ -28,6 +27,7 @@ import ( "github.com/milvus-io/milvus/internal/util/credentials" "github.com/milvus-io/milvus/internal/util/function/models" "github.com/milvus-io/milvus/internal/util/function/models/voyageai" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type voyageaiProvider struct { @@ -63,7 +63,7 @@ func newVoyageaiProvider(params []*commonpb.KeyValuePair, conf map[string]string } case models.TruncationParamKey: if truncate, err := strconv.ParseBool(param.Value); err != nil { - return nil, fmt.Errorf("[%s param's value: %s] is invalid, only supports: [true/false]", models.TruncationParamKey, param.Value) + return nil, merr.WrapErrParameterInvalidMsg("[%s param's value: %s] is invalid, only supports: [true/false]", models.TruncationParamKey, param.Value) } else { modelParams[models.TruncationParamKey] = truncate } @@ -71,7 +71,7 @@ func newVoyageaiProvider(params []*commonpb.KeyValuePair, conf map[string]string } } if modelName == "" { - return nil, fmt.Errorf("voyageai rerank model name is required") + return nil, merr.WrapErrParameterMissingMsg("voyageai rerank model name is required") } provider := voyageaiProvider{ baseProvider: baseProvider{batchSize: maxBatch}, diff --git a/internal/util/funcutil/count_util.go b/internal/util/funcutil/count_util.go index 984aa86642..f9ce377185 100644 --- a/internal/util/funcutil/count_util.go +++ b/internal/util/funcutil/count_util.go @@ -1,18 +1,17 @@ package funcutil import ( - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/pkg/v3/proto/internalpb" "github.com/milvus-io/milvus/pkg/v3/proto/segcorepb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) func CntOfInternalResult(res *internalpb.RetrieveResults) (int64, error) { if len(res.GetFieldsData()) != 1 { - return 0, errors.New("internal count result should only have one column") + return 0, merr.WrapErrDataIntegrityMsg("internal count result should only have one column") } f := res.GetFieldsData()[0] @@ -21,7 +20,7 @@ func CntOfInternalResult(res *internalpb.RetrieveResults) (int64, error) { func CntOfSegCoreResult(res *segcorepb.RetrieveResults) (int64, error) { if len(res.GetFieldsData()) != 1 { - return 0, errors.New("segcore count result should only have one column") + return 0, merr.WrapErrDataIntegrityMsg("segcore count result should only have one column") } f := res.GetFieldsData()[0] @@ -31,14 +30,14 @@ func CntOfSegCoreResult(res *segcorepb.RetrieveResults) (int64, error) { func CntOfFieldData(f *schemapb.FieldData) (int64, error) { scalars := f.GetScalars() if scalars == nil { - return 0, errors.New("count result should be scalar") + return 0, merr.WrapErrDataIntegrityMsg("count result should be scalar") } data := scalars.GetLongData() if data == nil { - return 0, errors.New("count result should be int64 data") + return 0, merr.WrapErrDataIntegrityMsg("count result should be int64 data") } if len(data.GetData()) != 1 { - return 0, errors.New("count result shoud only have one row") + return 0, merr.WrapErrDataIntegrityMsg("count result shoud only have one row") } return data.GetData()[0], nil } @@ -85,7 +84,7 @@ func WrapCntToQueryResults(cnt int64) *milvuspb.QueryResults { func CntOfQueryResults(res *milvuspb.QueryResults) (int64, error) { if len(res.GetFieldsData()) != 1 { - return 0, errors.New("milvus count result should only have one column") + return 0, merr.WrapErrDataIntegrityMsg("milvus count result should only have one column") } f := res.GetFieldsData()[0] diff --git a/internal/util/hookutil/cipher.go b/internal/util/hookutil/cipher.go index a2c24149f0..94f8ebebf2 100644 --- a/internal/util/hookutil/cipher.go +++ b/internal/util/hookutil/cipher.go @@ -36,6 +36,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/indexcgopb" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -43,7 +44,7 @@ var ( Cipher atomic.Value initCipherOnce sync.Once cipherReloadMutex sync.RWMutex - ErrCipherPluginMissing = errors.New("cipher plugin is missing") + errCipherPluginMissing = errors.New("cipher plugin is missing") ) type BackupInterface interface { @@ -198,7 +199,7 @@ func TidyDBCipherProperties(ezID int64, dbProperties []*commonpb.KeyValuePair) ( case common.EncryptionEnabledKey: value := strings.ToLower(property.Value) if value != "true" && value != "false" { - return nil, fmt.Errorf("invalid value for %s: %q, must be \"true\" or \"false\"", + return nil, merr.WrapErrParameterInvalidMsg("invalid value for %s: %q, must be \"true\" or \"false\"", common.EncryptionEnabledKey, property.Value) } defaultEncrypt = value == "true" @@ -213,7 +214,7 @@ func TidyDBCipherProperties(ezID int64, dbProperties []*commonpb.KeyValuePair) ( cipher := GetCipher() // If cipher plugin is missing but encryption is requested, return error if cipher == nil && defaultEncrypt { - return nil, ErrCipherPluginMissing + return nil, errCipherPluginMissing } // No plugin or not encrypted @@ -222,7 +223,7 @@ func TidyDBCipherProperties(ezID int64, dbProperties []*commonpb.KeyValuePair) ( } if defaultRootKey == "" { - return nil, fmt.Errorf("encryption enabled but no key provided and no default key configured") + return nil, merr.WrapErrParameterInvalidMsg("encryption enabled but no key provided and no default key configured") } result := removeCipherProperties(dbProperties) @@ -377,7 +378,7 @@ func GetCPluginContextByEzID(ezID int64) (*indexcgopb.StoragePluginContext, erro } key := GetCipher().GetUnsafeKey(ezID, 0) if len(key) == 0 { - return nil, errors.Newf("cannot get ez key for ezID=%d", ezID) + return nil, merr.WrapErrParameterInvalidMsg("cannot get ez key for ezID=%d", ezID) } return &indexcgopb.StoragePluginContext{ EncryptionZoneId: ezID, @@ -388,12 +389,12 @@ func GetCPluginContextByEzID(ezID int64) (*indexcgopb.StoragePluginContext, erro func BackupEZKFromDBProperties(dbProperties []*commonpb.KeyValuePair) (string, error) { if !IsDBEncrypted(dbProperties) { - return "", fmt.Errorf("not an encryption zone") + return "", merr.WrapErrParameterInvalidMsg("not an encryption zone") } ezID, hasEzID := ParseEzIDFromProperties(dbProperties) if !hasEzID { - return "", fmt.Errorf("encryption enabled but no ezID found") + return "", merr.WrapErrServiceInternalMsg("encryption enabled but no ezID found") } return BackupEZ(ezID) @@ -433,7 +434,7 @@ func initCipher() error { initConfigs := buildCipherInitConfig() if err = cipherVal.Init(initConfigs); err != nil { - return fmt.Errorf("fail to init configs for the cipher plugin, error: %s", err.Error()) + return merr.Wrap(err, "fail to init configs for the cipher plugin") } registerCallback() diff --git a/internal/util/hookutil/cipher_test.go b/internal/util/hookutil/cipher_test.go index 992b5b21a1..a13df1f010 100644 --- a/internal/util/hookutil/cipher_test.go +++ b/internal/util/hookutil/cipher_test.go @@ -157,7 +157,7 @@ func (s *CipherSuite) TestTidyDBCipherPropertiesError() { } _, err := TidyDBCipherProperties(1, dbProperties) s.Error(err) - s.Equal(ErrCipherPluginMissing, err) + s.Equal(errCipherPluginMissing, err) } func (s *CipherSuite) TestTidyDBCipherPropertiesInvalidValue() { @@ -497,7 +497,7 @@ func (s *CipherSuite) TestBackupEZ() { storeCipher(nil) _, err := BackupEZ(123) s.Error(err) - s.Equal(ErrCipherPluginMissing, err) + s.Equal(errCipherPluginMissing, err) InitTestCipher() _, err = BackupEZ(123) @@ -513,7 +513,7 @@ func (s *CipherSuite) TestBackupEZKFromDBProperties() { } _, err := BackupEZKFromDBProperties(props) s.Error(err) - s.Equal(ErrCipherPluginMissing, err) + s.Equal(errCipherPluginMissing, err) props = []*commonpb.KeyValuePair{ {Key: common.EncryptionRootKeyKey, Value: "abc-key"}, diff --git a/internal/util/hookutil/default.go b/internal/util/hookutil/default.go index 3a05dff195..3026c4284d 100644 --- a/internal/util/hookutil/default.go +++ b/internal/util/hookutil/default.go @@ -21,9 +21,8 @@ package hookutil import ( "context" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/hook" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type DefaultHook struct{} @@ -31,7 +30,7 @@ type DefaultHook struct{} var _ hook.Hook = (*DefaultHook)(nil) func (d DefaultHook) VerifyAPIKey(key string) (string, error) { - return "", errors.New("default hook, can't verify api key") + return "", merr.WrapErrParameterInvalidMsg("default hook, can't verify api key") } func (d DefaultHook) Init(params map[string]string) error { diff --git a/internal/util/hookutil/ez.go b/internal/util/hookutil/ez.go index 828e2945f1..2d9061e8a7 100644 --- a/internal/util/hookutil/ez.go +++ b/internal/util/hookutil/ez.go @@ -23,10 +23,9 @@ import ( "strconv" "strings" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus/pkg/v3/common" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) const ( @@ -46,12 +45,12 @@ func encodeEZContext(ezID int64, key []byte) string { func decodeEZContext(encoded string) (ezID int64, key string, err error) { parts := strings.Split(encoded, ":") if len(parts) != 2 { - return 0, "", fmt.Errorf("invalid EZ context format: %s", encoded) + return 0, "", merr.WrapErrParameterInvalidMsg("invalid EZ context format: %s", encoded) } ezID, err = strconv.ParseInt(parts[0], 10, 64) if err != nil { - return 0, "", fmt.Errorf("invalid ezID in context: %w", err) + return 0, "", merr.Wrap(err, "invalid ezID in context") } return ezID, parts[1], nil @@ -114,12 +113,12 @@ func RemoveEZ(ezID int64) error { func BackupEZ(ezID int64) (string, error) { cipher := GetCipher() if cipher == nil { - return "", ErrCipherPluginMissing + return "", errCipherPluginMissing } backupProvider, ok := cipher.(BackupInterface) if !ok { - return "", fmt.Errorf("cipher plugin does not support backup operation") + return "", merr.WrapErrServiceInternalMsg("cipher plugin does not support backup operation") } return backupProvider.Backup(ezID) @@ -133,7 +132,7 @@ func GetPluginContext(ezID int64, collectionID int64) ([]*commonpb.KeyValuePair, key := cipher.GetUnsafeKey(ezID, collectionID) if len(key) == 0 { - return nil, errors.Newf("cannot get ez key for ezID=%d, collectionID=%d", ezID, collectionID) + return nil, merr.WrapErrParameterInvalidMsg("cannot get ez key for ezID=%d, collectionID=%d", ezID, collectionID) } return []*commonpb.KeyValuePair{{ @@ -152,7 +151,7 @@ func ImportEZ(importEzk string) ([]*commonpb.KeyValuePair, error) { CipherConfigImportEZ: importEzk, } if err := cipher.Init(config); err != nil { - return nil, fmt.Errorf("failed to import EZK: %w", err) + return nil, merr.Wrap(err, "failed to import EZK") } ezID, err := GetEzIDByImportEzk(importEzk) @@ -165,7 +164,7 @@ func ImportEZ(importEzk string) ([]*commonpb.KeyValuePair, error) { func GetEzIDByImportEzk(importEzk string) (int64, error) { ezkBytes, err := base64.StdEncoding.DecodeString(importEzk) if err != nil { - return 0, fmt.Errorf("failed to decode EZK: %w", err) + return 0, merr.Wrap(err, "failed to decode EZK") } type EZKData struct { @@ -173,7 +172,7 @@ func GetEzIDByImportEzk(importEzk string) (int64, error) { } var ezkData EZKData if err := json.Unmarshal(ezkBytes, &ezkData); err != nil { - return 0, fmt.Errorf("failed to unmarshal EZK: %w", err) + return 0, merr.Wrap(err, "failed to unmarshal EZK") } return ezkData.EzID, nil } diff --git a/internal/util/hookutil/hook.go b/internal/util/hookutil/hook.go index 4f7a050d61..b53c2035d7 100644 --- a/internal/util/hookutil/hook.go +++ b/internal/util/hookutil/hook.go @@ -28,6 +28,7 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v3/hook" "github.com/milvus-io/milvus/pkg/v3/config" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -81,7 +82,7 @@ func initHook() error { } if err = hookVal.Init(paramtable.GetHookParams().SoConfig.GetValue()); err != nil { - return fmt.Errorf("fail to init configs for the hook, error: %s", err.Error()) + return merr.Wrap(err, "fail to init configs for the hook") } storeHook((hookVal)) paramtable.GetHookParams().WatchHookWithPrefix("watch_hook", "", func(event *config.Event) { diff --git a/internal/util/hookutil/plugin.go b/internal/util/hookutil/plugin.go index e14787a824..19154ad458 100644 --- a/internal/util/hookutil/plugin.go +++ b/internal/util/hookutil/plugin.go @@ -1,13 +1,13 @@ package hookutil import ( - "fmt" "plugin" "sync" "go.uber.org/zap" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) var pluginMutex sync.Mutex @@ -18,7 +18,7 @@ var pluginMutex sync.Mutex func LoadPlugin[T any](path string, symbol string) (T, error) { var zero T if path == "" { - return zero, fmt.Errorf("empty plugin path for symbol %q", symbol) + return zero, merr.WrapErrParameterInvalidMsg("empty plugin path for symbol %q", symbol) } log.Info("loading plugin", zap.String("path", path), zap.String("symbol", symbol)) @@ -28,17 +28,17 @@ func LoadPlugin[T any](path string, symbol string) (T, error) { p, err := plugin.Open(path) if err != nil { - return zero, fmt.Errorf("fail to open plugin %s: %w", path, err) + return zero, merr.Wrapf(err, "fail to open plugin %s", path) } sym, err := p.Lookup(symbol) if err != nil { - return zero, fmt.Errorf("fail to find symbol %q in plugin %s: %w", symbol, path, err) + return zero, merr.Wrapf(err, "fail to find symbol %q in plugin %s", symbol, path) } val, ok := sym.(T) if !ok { - return zero, fmt.Errorf("symbol %q in plugin %s does not implement expected interface", symbol, path) + return zero, merr.WrapErrServiceInternalMsg("symbol %q in plugin %s does not implement expected interface", symbol, path) } return val, nil diff --git a/internal/util/idalloc/basic_allocator.go b/internal/util/idalloc/basic_allocator.go index cecd5ff365..533ce05ce2 100644 --- a/internal/util/idalloc/basic_allocator.go +++ b/internal/util/idalloc/basic_allocator.go @@ -2,7 +2,6 @@ package idalloc import ( "context" - "fmt" "time" "github.com/cockroachdb/errors" @@ -11,6 +10,7 @@ import ( "github.com/milvus-io/milvus/internal/types" "github.com/milvus-io/milvus/pkg/v3/proto/rootcoordpb" "github.com/milvus-io/milvus/pkg/v3/util/commonpbutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/syncutil" ) @@ -87,18 +87,18 @@ func (ta *tsoAllocator) batchAllocate(ctx context.Context, count uint32) (uint64 mixc, err := ta.mix.GetWithContext(ctx) if err != nil { - return 0, 0, fmt.Errorf("get root coordinator client timeout: %w", err) + return 0, 0, merr.Wrap(err, "get root coordinator client timeout") } resp, err := mixc.AllocTimestamp(ctx, req) if err != nil { - return 0, 0, fmt.Errorf("syncTimestamp Failed:%w", err) + return 0, 0, merr.Wrap(err, "syncTimestamp Failed") } if resp.GetStatus().GetErrorCode() != commonpb.ErrorCode_Success { - return 0, 0, fmt.Errorf("syncTimeStamp Failed:%s", resp.GetStatus().GetReason()) + return 0, 0, merr.Wrap(merr.Error(resp.GetStatus()), "syncTimeStamp Failed") } if resp == nil { - return 0, 0, errors.New("empty AllocTimestampResponse") + return 0, 0, merr.WrapErrServiceInternalMsg("empty AllocTimestampResponse") } return resp.GetTimestamp(), int(resp.GetCount()), nil } @@ -132,18 +132,18 @@ func (ta *idAllocator) batchAllocate(ctx context.Context, count uint32) (uint64, mix, err := ta.mix.GetWithContext(ctx) if err != nil { - return 0, 0, fmt.Errorf("get root coordinator client timeout: %w", err) + return 0, 0, merr.Wrap(err, "get root coordinator client timeout") } resp, err := mix.AllocID(ctx, req) if err != nil { - return 0, 0, fmt.Errorf("AllocID Failed:%w", err) + return 0, 0, merr.Wrap(err, "AllocID Failed") } if resp.GetStatus().GetErrorCode() != commonpb.ErrorCode_Success { - return 0, 0, fmt.Errorf("AllocID Failed:%s", resp.GetStatus().GetReason()) + return 0, 0, merr.Wrap(merr.Error(resp.GetStatus()), "AllocID Failed") } if resp == nil { - return 0, 0, errors.New("empty AllocID") + return 0, 0, merr.WrapErrServiceInternalMsg("empty AllocID") } if resp.GetID() < 0 { panic("get unexpected negative id") diff --git a/internal/util/importutilv2/binlog/l0_reader.go b/internal/util/importutilv2/binlog/l0_reader.go index c0406a4dab..1b14ec8430 100644 --- a/internal/util/importutilv2/binlog/l0_reader.go +++ b/internal/util/importutilv2/binlog/l0_reader.go @@ -175,7 +175,7 @@ func (r *l0Reader) Read() (*storage.DeleteData, error) { tempData, errv2 := readInternal(path, v2opts) if errv2 != nil { // return both the error from v1 and v2 - return nil, merr.WrapErrImportFailed(fmt.Sprintf("failed to read deltalogs from v1 and v2: %v, %v", errv1, errv2)) + return nil, merr.WrapErrImportSysFailedMsg("failed to read deltalogs from v1 and v2: %v, %v", errv1, errv2) } // Merge v2 results into deleteData for i := int64(0); i < tempData.RowCount; i++ { diff --git a/internal/util/importutilv2/binlog/reader.go b/internal/util/importutilv2/binlog/reader.go index 547136b35f..fedc4b7406 100644 --- a/internal/util/importutilv2/binlog/reader.go +++ b/internal/util/importutilv2/binlog/reader.go @@ -18,7 +18,6 @@ package binlog import ( "context" - "fmt" "io" "math" "strings" @@ -107,8 +106,8 @@ func (r *reader) init(paths []string, tsStart, tsEnd uint64) error { // the "paths" has one or two paths, the first is the binlog path of a segment // the other is optional, is the delta path of a segment if len(paths) > 2 { - return merr.WrapErrImportFailed(fmt.Sprintf("too many input paths for binlog import. "+ - "Valid paths length should be one or two, but got paths:%s", paths)) + return merr.WrapErrImportFailedMsg("too many input paths for binlog import. "+ + "Valid paths length should be one or two, but got paths:%s", paths) } insertLogs, err := listInsertLogs(r.ctx, r.cm, paths[0], r.retryAttempts) if err != nil { @@ -379,7 +378,7 @@ OUTER: row := insertData.GetRow(i) err = result.Append(row) if err != nil { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("failed to append row, err=%s", err.Error())) + return nil, merr.WrapErrImportFailedMsg("failed to append row, err=%s", err.Error()) } } return result, nil diff --git a/internal/util/importutilv2/binlog/util.go b/internal/util/importutilv2/binlog/util.go index 896ace7541..67b9266217 100644 --- a/internal/util/importutilv2/binlog/util.go +++ b/internal/util/importutilv2/binlog/util.go @@ -18,7 +18,6 @@ package binlog import ( "context" - "fmt" "path" "sort" "strconv" @@ -41,18 +40,18 @@ func readData(reader *storage.BinlogReader, et storage.EventTypeCode) ([]any, [] for { event, err := reader.NextEventReader() if err != nil { - return nil, nil, merr.WrapErrImportFailed(fmt.Sprintf("failed to iterate events reader, error: %v", err)) + return nil, nil, merr.WrapErrImportSysFailedMsg("failed to iterate events reader, error: %v", err) } if event == nil { break // end of the file } if event.TypeCode != et { - return nil, nil, merr.WrapErrImportFailed(fmt.Sprintf("wrong binlog type, expect:%s, actual:%s", - et.String(), event.TypeCode.String())) + return nil, nil, merr.WrapErrImportFailedMsg("wrong binlog type, expect:%s, actual:%s", + et.String(), event.TypeCode.String()) } rows, validDataRows, _, err := event.GetDataFromPayload() if err != nil { - return nil, nil, merr.WrapErrImportFailed(fmt.Sprintf("failed to read data, error: %v", err)) + return nil, nil, merr.WrapErrImportSysFailedMsg("failed to read data, error: %v", err) } rowsSet = append(rowsSet, rows) validDataRowsSet = append(validDataRowsSet, validDataRows) @@ -62,14 +61,16 @@ func readData(reader *storage.BinlogReader, et storage.EventTypeCode) ([]any, [] // read delete data only func newBinlogReader(ctx context.Context, cm storage.ChunkManager, path string) (*storage.BinlogReader, error) { - bytes, err := cm.Read(ctx, path) // TODO: dyh, checks if the error is a retryable error + bytes, err := cm.Read(ctx, path) if err != nil { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("failed to open binlog %s", path)) + // cm.Read returns a typed storage error (Io* family); keep its code and + // retriability instead of flattening it into a generic import failure. + return nil, merr.Wrapf(err, "failed to open binlog %s", path) } var reader *storage.BinlogReader reader, err = storage.NewBinlogReader(bytes) if err != nil { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("failed to create reader, binlog:%s, error:%v", path, err)) + return nil, merr.WrapErrImportSysFailedMsg("failed to create reader, binlog:%s, error:%v", path, err) } return reader, nil } @@ -87,7 +88,7 @@ func listInsertLogs(ctx context.Context, cm storage.ChunkManager, insertPrefix s fieldStrID := path.Base(fieldPath) fieldID, err := strconv.ParseInt(fieldStrID, 10, 64) if err != nil { - walkErr = merr.WrapErrImportFailed(fmt.Sprintf("failed to parse field id from log, error: %v", err)) + walkErr = merr.WrapErrImportSysFailedMsg("failed to parse field id from log, error: %v", err) return false } insertLogs[fieldID] = append(insertLogs[fieldID], insertLog.FilePath) @@ -141,8 +142,8 @@ func verify(schema *schemapb.CollectionSchema, storageVersion int64, insertLogs // check binlog file count, must be equal for all fields for fieldID, logs := range insertLogs { if len(logs) != len(insertLogs[common.RowIDField]) { - return nil, nil, merr.WrapErrImportFailed(fmt.Sprintf("misaligned binlog count, field%d:%d, field%d:%d", - fieldID, len(logs), common.RowIDField, len(insertLogs[common.RowIDField]))) + return nil, nil, merr.WrapErrImportFailedMsg("misaligned binlog count, field%d:%d, field%d:%d", + fieldID, len(logs), common.RowIDField, len(insertLogs[common.RowIDField])) } } @@ -152,7 +153,7 @@ func verify(schema *schemapb.CollectionSchema, storageVersion int64, insertLogs if typeutil.IsVectorType(field.GetDataType()) { if _, ok := insertLogs[field.GetFieldID()]; !ok { // vector field must be provided - return nil, nil, merr.WrapErrImportFailed(fmt.Sprintf("no binlog for field:%s", field.GetName())) + return nil, nil, merr.WrapErrImportFailedMsg("no binlog for field:%s", field.GetName()) } } } @@ -200,7 +201,7 @@ func verify(schema *schemapb.CollectionSchema, storageVersion int64, insertLogs // primary key is required // function output field is also required // the other field must be provided - return nil, nil, merr.WrapErrImportFailed(fmt.Sprintf("no binlog for field:%s", field.GetName())) + return nil, nil, merr.WrapErrImportFailedMsg("no binlog for field:%s", field.GetName()) } else { // these binlogs are intend to be imported validInsertLogs[id] = logs @@ -230,7 +231,7 @@ func verify(schema *schemapb.CollectionSchema, storageVersion int64, insertLogs continue } if len(missingFields) > 0 { - return nil, nil, merr.WrapErrImportFailed(fmt.Sprintf("no binlog for struct field:%s", missingFields[0])) + return nil, nil, merr.WrapErrImportFailedMsg("no binlog for struct field:%s", missingFields[0]) } for id, logs := range presentLogs { validInsertLogs[id] = logs diff --git a/internal/util/importutilv2/binlog/util_test.go b/internal/util/importutilv2/binlog/util_test.go index 23ca3c4aec..9a98bf9ee3 100644 --- a/internal/util/importutilv2/binlog/util_test.go +++ b/internal/util/importutilv2/binlog/util_test.go @@ -121,5 +121,5 @@ func TestListInsertLogs_ParseFieldIDError(t *testing.T) { _, err := listInsertLogs(ctx, cm, "prefix/", 3) assert.Error(t, err) - assert.True(t, errors.Is(err, merr.ErrImportFailed), "parse error must be wrapped as an import failure") + assert.True(t, errors.Is(err, merr.ErrImportSysFailed), "parse-field-id IO error must be wrapped as a server-side import failure") } diff --git a/internal/util/importutilv2/common/util.go b/internal/util/importutilv2/common/util.go index 6e4d9e407f..d903ffd5d4 100644 --- a/internal/util/importutilv2/common/util.go +++ b/internal/util/importutilv2/common/util.go @@ -25,19 +25,20 @@ import ( "github.com/milvus-io/milvus/internal/storage" "github.com/milvus-io/milvus/pkg/v3/common" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) func CheckVarcharLength(str string, maxLength int64, field *schemapb.FieldSchema) error { if (int64)(len(str)) > maxLength { - return fmt.Errorf("value length(%d) for field %s exceeds max_length(%d)", len(str), field.GetName(), maxLength) + return merr.WrapErrParameterInvalidMsg("value length(%d) for field %s exceeds max_length(%d)", len(str), field.GetName(), maxLength) } return nil } func CheckArrayCapacity(arrLength int, maxCapacity int64, field *schemapb.FieldSchema) error { if (int64)(arrLength) > maxCapacity { - return fmt.Errorf("array capacity(%d) for field %s exceeds max_capacity(%d)", arrLength, field.GetName(), maxCapacity) + return merr.WrapErrParameterInvalidMsg("array capacity(%d) for field %s exceeds max_capacity(%d)", arrLength, field.GetName(), maxCapacity) } return nil } @@ -48,7 +49,7 @@ func EstimateReadCountPerBatch(bufferSize int, schema *schemapb.CollectionSchema return 0, err } if sizePerRecord <= 0 || bufferSize <= 0 { - return 0, fmt.Errorf("invalid size, sizePerRecord=%d, bufferSize=%d", sizePerRecord, bufferSize) + return 0, merr.WrapErrParameterInvalidMsg("invalid size, sizePerRecord=%d, bufferSize=%d", sizePerRecord, bufferSize) } if 1000*sizePerRecord <= bufferSize { return 1000, nil @@ -94,7 +95,7 @@ func CheckValidUTF8(s string, field *schemapb.FieldSchema) error { if !typeutil.IsUTF8(s) { // Use safe string representation to avoid gRPC serialization errors safeValue := SafeStringForErrorWithLimit(s, 100) - return fmt.Errorf("field '%s' contains invalid UTF-8 data, value=%s", field.GetName(), safeValue) + return merr.WrapErrParameterInvalidMsg("field '%s' contains invalid UTF-8 data, value=%s", field.GetName(), safeValue) } return nil } diff --git a/internal/util/importutilv2/csv/reader.go b/internal/util/importutilv2/csv/reader.go index 96e3bd290d..90db89350f 100644 --- a/internal/util/importutilv2/csv/reader.go +++ b/internal/util/importutilv2/csv/reader.go @@ -19,7 +19,6 @@ package csv import ( "context" "encoding/csv" - "fmt" "io" "go.uber.org/atomic" @@ -52,7 +51,7 @@ type reader struct { func NewReader(ctx context.Context, cm storage.ChunkManager, schema *schemapb.CollectionSchema, path string, bufferSize int, sep rune, nullkey string) (*reader, error) { cmReader, err := cm.Reader(ctx, path) if err != nil { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("read csv file failed, path=%s, err=%s", path, err.Error())) + return nil, merr.Wrapf(err, "read csv file failed, path=%s", path) } retryableReader := common.NewRetryableReaderWithReopen(ctx, path, cmReader, common.NewChunkManagerReopenReaderFunc(cm), cm.Size) count, err := common.EstimateReadCountPerBatch(bufferSize, schema) @@ -66,7 +65,7 @@ func NewReader(ctx context.Context, cm storage.ChunkManager, schema *schemapb.Co header, err := csvReader.Read() log.Info("csv header parsed", zap.Strings("header", header)) if err != nil { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("failed to read csv header, error: %v", err)) + return nil, merr.WrapErrImportSysFailedMsg("failed to read csv header, error: %v", err) } rowParser, err := NewRowParser(schema, header, nullkey) @@ -100,11 +99,11 @@ func (r *reader) Read() (*storage.InsertData, error) { } row, err := r.parser.Parse(value) if err != nil { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("failed to parse row, error: %v", err)) + return nil, merr.WrapErrImportFailedMsg("failed to parse row, error: %v", err) } err = insertData.Append(row) if err != nil { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("failed to append row, error: %v", err)) + return nil, merr.WrapErrImportFailedMsg("failed to append row, error: %v", err) } cnt++ if cnt >= r.count { diff --git a/internal/util/importutilv2/csv/row_parser.go b/internal/util/importutilv2/csv/row_parser.go index 29d364126b..13647a5d26 100644 --- a/internal/util/importutilv2/csv/row_parser.go +++ b/internal/util/importutilv2/csv/row_parser.go @@ -99,9 +99,9 @@ func NewRowParser(schema *schemapb.CollectionSchema, header []string, nullkey st headerMap := make(map[string]bool) for _, name := range header { if structName, ok := structArraySubFields[name]; ok { - return nil, merr.WrapErrImportFailed(fmt.Sprintf( + return nil, merr.WrapErrImportFailedMsg( "struct field '%s' must be provided as a struct array; flat sub-field '%s' is not supported", - structName, name)) + structName, name) } headerMap[name] = true } @@ -179,14 +179,14 @@ func (r *rowParser) reconstructArrayForStructArray(structName string, subFieldsM dec := json.NewDecoder(strings.NewReader(raw)) dec.UseNumber() if err := dec.Decode(&decoded); err != nil { - return nil, false, merr.WrapErrImportFailed(fmt.Sprintf("invalid StructArray format in CSV, failed to parse JSON: %v", err)) + return nil, false, merr.WrapErrImportFailedMsg("invalid StructArray format in CSV, failed to parse JSON: %v", err) } if decoded == nil { return nil, true, nil } structs, ok := decoded.([]any) if !ok { - return nil, false, merr.WrapErrImportFailed(fmt.Sprintf("invalid StructArray format in CSV, expect array but got type %T", decoded)) + return nil, false, merr.WrapErrImportFailedMsg("invalid StructArray format in CSV, expect array but got type %T", decoded) } expectedFieldCount := len(subFieldsMap) @@ -194,7 +194,7 @@ func (r *rowParser) reconstructArrayForStructArray(structName string, subFieldsM for i, elem := range structs { dict, ok := elem.(map[string]any) if !ok { - return nil, false, merr.WrapErrImportFailed(fmt.Sprintf("invalid element in StructArray, expect map[string]any but got type %T", elem)) + return nil, false, merr.WrapErrImportFailedMsg("invalid element in StructArray, expect map[string]any but got type %T", elem) } if len(dict) != expectedFieldCount { return nil, false, merr.WrapErrImportFailed( @@ -247,7 +247,7 @@ func (r *rowParser) Parse(strArr []string) (Row, error) { if subFieldsMap, ok := r.structArrays[csvFieldName]; ok { structField, ok := r.name2StructField[csvFieldName] if !ok { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("struct field %s is not found in schema", csvFieldName)) + return nil, merr.WrapErrImportFailedMsg("struct field %s is not found in schema", csvFieldName) } if value == r.nullkey { if err := appendNullStruct(structField, csvFieldName); err != nil { @@ -268,7 +268,7 @@ func (r *rowParser) Parse(strArr []string) (Row, error) { for subKey, subValues := range flatStructs { field, ok := r.name2Field[subKey] if !ok { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("sub field %s of struct field %s is not found in schema", subKey, csvFieldName)) + return nil, merr.WrapErrImportFailedMsg("sub field %s of struct field %s is not found in schema", subKey, csvFieldName) } // TODO: how to get max capacity from a StructFieldSchema? data, err := r.parseStructEntity(field, subValues) @@ -317,7 +317,7 @@ func (r *rowParser) Parse(strArr []string) (Row, error) { } _, subField := r.structArraySubFields[fieldName] if _, ok := row[fieldID]; !ok && !subField { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("value of field '%s' is missed", fieldName)) + return nil, merr.WrapErrImportFailedMsg("value of field '%s' is missed", fieldName) } } @@ -353,7 +353,7 @@ func (r *rowParser) combineDynamicRow(dynamicValues map[string]string, row Row) // put the all dynamic fields into newDynamicValues for k, v := range mp { if _, ok = dynamicValues[k]; ok { - return merr.WrapErrImportFailed(fmt.Sprintf("duplicated key in dynamic field, key=%s", k)) + return merr.WrapErrImportFailedMsg("duplicated key in dynamic field, key=%s", k) } newDynamicValues[k] = v } @@ -627,7 +627,7 @@ func (r *rowParser) arrayToFieldData(arr []interface{}, field *schemapb.FieldSch } num, err := strconv.ParseInt(value.String(), 10, 32) if err != nil { - return nil, fmt.Errorf("failed to parse int32: %w", err) + return nil, merr.Wrap(err, "failed to parse int32") } values[i] = int32(num) } @@ -648,7 +648,7 @@ func (r *rowParser) arrayToFieldData(arr []interface{}, field *schemapb.FieldSch } num, err := strconv.ParseInt(value.String(), 10, 64) if err != nil { - return nil, fmt.Errorf("failed to parse int64: %w", err) + return nil, merr.Wrap(err, "failed to parse int64") } values[i] = num } @@ -668,12 +668,12 @@ func (r *rowParser) arrayToFieldData(arr []interface{}, field *schemapb.FieldSch } num, err := strconv.ParseFloat(value.String(), 32) if err != nil { - return nil, fmt.Errorf("failed to parse float32: %w", err) + return nil, merr.Wrap(err, "failed to parse float32") } values[i] = float32(num) } if err := typeutil.VerifyFloats32(values); err != nil { - return nil, fmt.Errorf("float32 verification failed: %w", err) + return nil, merr.Wrap(err, "float32 verification failed") } return &schemapb.ScalarField{ Data: &schemapb.ScalarField_FloatData{ @@ -691,12 +691,12 @@ func (r *rowParser) arrayToFieldData(arr []interface{}, field *schemapb.FieldSch } num, err := strconv.ParseFloat(value.String(), 64) if err != nil { - return nil, fmt.Errorf("failed to parse float64: %w", err) + return nil, merr.Wrap(err, "failed to parse float64") } values[i] = num } if err := typeutil.VerifyFloats64(values); err != nil { - return nil, fmt.Errorf("float64 verification failed: %w", err) + return nil, merr.Wrap(err, "float64 verification failed") } return &schemapb.ScalarField{ Data: &schemapb.ScalarField_DoubleData{ @@ -714,7 +714,7 @@ func (r *rowParser) arrayToFieldData(arr []interface{}, field *schemapb.FieldSch } num, err := strconv.ParseInt(value.String(), 10, 64) if err != nil { - return nil, fmt.Errorf("failed to parse timesamptz: %w", err) + return nil, merr.Wrap(err, "failed to parse timesamptz") } values[i] = num } @@ -778,7 +778,7 @@ func (r *rowParser) arrayOfVectorToFieldData(vectors []any, field *schemapb.Fiel } num, err := strconv.ParseFloat(value.String(), 32) if err != nil { - return nil, fmt.Errorf("failed to parse float: %w", err) + return nil, merr.Wrap(err, "failed to parse float") } vector[i] = float32(num) } @@ -800,9 +800,9 @@ func (r *rowParser) arrayOfVectorToFieldData(vectors []any, field *schemapb.Fiel case schemapb.DataType_Float16Vector, schemapb.DataType_BFloat16Vector, schemapb.DataType_BinaryVector, schemapb.DataType_Int8Vector, schemapb.DataType_SparseFloatVector: - return nil, merr.WrapErrImportFailed(fmt.Sprintf("not implemented element type for CSV: %s", elementType.String())) + return nil, merr.WrapErrImportFailedMsg("not implemented element type for CSV: %s", elementType.String()) default: - return nil, merr.WrapErrImportFailed(fmt.Sprintf("unsupported element type: %s", elementType.String())) + return nil, merr.WrapErrImportFailedMsg("unsupported element type: %s", elementType.String()) } } diff --git a/internal/util/importutilv2/json/reader.go b/internal/util/importutilv2/json/reader.go index 61022d8225..ee4467b214 100644 --- a/internal/util/importutilv2/json/reader.go +++ b/internal/util/importutilv2/json/reader.go @@ -19,7 +19,6 @@ package json import ( "context" "encoding/json" - "fmt" "io" "strings" @@ -58,7 +57,7 @@ type reader struct { func newReader(ctx context.Context, cm storage.ChunkManager, schema *schemapb.CollectionSchema, path string, bufferSize int, isLinesFormat bool) (*reader, error) { r, err := cm.Reader(ctx, path) if err != nil { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("read json file failed, path=%s, err=%s", path, err.Error())) + return nil, merr.Wrapf(err, "read json file failed, path=%s", path) } retryableReader := common.NewRetryableReaderWithReopen(ctx, path, r, common.NewChunkManagerReopenReaderFunc(cm), cm.Size) count, err := common.EstimateReadCountPerBatch(bufferSize, schema) @@ -98,6 +97,18 @@ func NewLinesReader(ctx context.Context, cm storage.ChunkManager, schema *schema return reader, err } +// wrapDecodeError attributes a JSON decode failure. The decoder reads from the +// storage-backed retryableReader, so an error that is already a typed merr is +// a propagated storage/IO failure — keep its code and retriability instead of +// blaming the user's file (ErrImportFailed is InputError); anything else is a +// genuinely malformed file. +func wrapDecodeError(err error, msg string) error { + if merr.IsMilvusError(err) { + return merr.Wrap(err, msg) + } + return merr.WrapErrImportFailedMsg("%s, error: %v", msg, err) +} + func (j *reader) Init() error { // Treat number value as a string instead of a float64. // By default, json lib treat all number values as float64, @@ -129,7 +140,7 @@ func (j *reader) Init() error { // ] t, err := j.dec.Token() if err != nil { - return merr.WrapErrImportFailed(fmt.Sprintf("init failed, failed to decode JSON, error: %v", err)) + return wrapDecodeError(err, "init failed, failed to decode JSON") } if t != json.Delim('{') && t != json.Delim('[') { return merr.WrapErrImportFailed("invalid JSON format, the content should be started with '{' or '['") @@ -176,19 +187,19 @@ func (j *reader) readNormalJSON(insertData *storage.InsertData) error { // read the key t, err := j.dec.Token() if err != nil { - return merr.WrapErrImportFailed(fmt.Sprintf("failed to decode the JSON file, error: %v", err)) + return wrapDecodeError(err, "failed to decode the JSON file") } key := t.(string) keyLower := strings.ToLower(key) // the root key should be RowRootNode if keyLower != RowRootNode { - return merr.WrapErrImportFailed(fmt.Sprintf("invalid JSON format, the root key should be '%s', but get '%s'", RowRootNode, key)) + return merr.WrapErrImportFailedMsg("invalid JSON format, the root key should be '%s', but get '%s'", RowRootNode, key) } // started by '[' t, err = j.dec.Token() if err != nil { - return merr.WrapErrImportFailed(fmt.Sprintf("failed to decode the JSON file, error: %v", err)) + return wrapDecodeError(err, "failed to decode the JSON file") } if t != json.Delim('[') { @@ -206,7 +217,7 @@ func (j *reader) readNormalJSON(insertData *storage.InsertData) error { if !j.dec.More() { t, err := j.dec.Token() if err != nil { - return merr.WrapErrImportFailed(fmt.Sprintf("failed to decode JSON, error: %v", err)) + return wrapDecodeError(err, "failed to decode JSON") } if t != json.Delim(']') { return merr.WrapErrImportFailed("invalid JSON format, rows list should end with ']'") @@ -231,16 +242,16 @@ func (j *reader) readJSONLines(insertData *storage.InsertData) error { for j.dec.More() { var value any if err := j.dec.Decode(&value); err != nil { - return merr.WrapErrImportFailed(fmt.Sprintf("failed to decode row, error: %v", err)) + return wrapDecodeError(err, "failed to decode row") } row, err := j.parser.Parse(value) if err != nil { - return merr.WrapErrImportFailed(fmt.Sprintf("failed to parse row, error: %v", err)) + return merr.WrapErrImportFailedMsg("failed to parse row, error: %v", err) } err = insertData.Append(row) if err != nil { - return merr.WrapErrImportFailed(fmt.Sprintf("failed to append row, err=%s", err.Error())) + return merr.WrapErrImportFailedMsg("failed to append row, err=%s", err.Error()) } cnt++ if cnt >= j.count { diff --git a/internal/util/importutilv2/json/row_parser.go b/internal/util/importutilv2/json/row_parser.go index b72d3ffc3c..d2add86b1a 100644 --- a/internal/util/importutilv2/json/row_parser.go +++ b/internal/util/importutilv2/json/row_parser.go @@ -179,7 +179,7 @@ func (r *rowParser) wrapArrayValueTypeError(v any, eleType schemapb.DataType) er func reconstructArrayForStructArray(raw any, subFieldNames []string) (map[string]any, error) { rows, ok := raw.([]any) if !ok { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("invalid StructArray format in JSON, each row should be a key-value map, but got type %T", raw)) + return nil, merr.WrapErrImportFailedMsg("invalid StructArray format in JSON, each row should be a key-value map, but got type %T", raw) } expectedFields := make(map[string]struct{}, len(subFieldNames)) @@ -191,7 +191,7 @@ func reconstructArrayForStructArray(raw any, subFieldNames []string) (map[string for i, elem := range rows { row, ok := elem.(map[string]any) if !ok { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("invalid element in StructArray, expect map[string]any but got type %T", elem)) + return nil, merr.WrapErrImportFailedMsg("invalid element in StructArray, expect map[string]any but got type %T", elem) } if len(row) != len(subFieldNames) { return nil, merr.WrapErrImportFailed( @@ -270,9 +270,9 @@ func (r *rowParser) Parse(raw any) (Row, error) { // read values from json file for key, value := range stringMap { if structName, ok := r.structArraySubFields[key]; ok { - return nil, merr.WrapErrImportFailed(fmt.Sprintf( + return nil, merr.WrapErrImportFailedMsg( "struct field '%s' must be provided as a struct array; flat sub-field '%s' is not supported", - structName, key)) + structName, key) } if subFieldNames, ok := r.structArrays[key]; ok { structField := r.structArrayFields[key] @@ -331,7 +331,7 @@ func (r *rowParser) Parse(raw any) (Row, error) { } } if _, ok = row[fieldID]; !ok { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("value of field '%s' is missed", fieldName)) + return nil, merr.WrapErrImportFailedMsg("value of field '%s' is missed", fieldName) } } @@ -387,7 +387,7 @@ func (r *rowParser) combineDynamicRow(dynamicValues map[string]any, row Row) err for k, v := range mp { if _, ok = dynamicValues[k]; ok { // case 8, 9 - return merr.WrapErrImportFailed(fmt.Sprintf("duplicated key is not allowed, key=%s", k)) + return merr.WrapErrImportFailedMsg("duplicated key is not allowed, key=%s", k) } dynamicValues[k] = v } @@ -727,7 +727,7 @@ func (r *rowParser) arrayToFieldData(arr []interface{}, field *schemapb.FieldSch } num, err := strconv.ParseInt(value.String(), 0, 32) if err != nil { - return nil, fmt.Errorf("failed to parse int32: %w", err) + return nil, merr.Wrap(err, "failed to parse int32") } values[i] = int32(num) } @@ -747,7 +747,7 @@ func (r *rowParser) arrayToFieldData(arr []interface{}, field *schemapb.FieldSch } num, err := strconv.ParseInt(value.String(), 0, 64) if err != nil { - return nil, fmt.Errorf("failed to parse int64: %w", err) + return nil, merr.Wrap(err, "failed to parse int64") } values[i] = num } @@ -767,12 +767,12 @@ func (r *rowParser) arrayToFieldData(arr []interface{}, field *schemapb.FieldSch } num, err := strconv.ParseFloat(value.String(), 32) if err != nil { - return nil, fmt.Errorf("failed to parse float32: %w", err) + return nil, merr.Wrap(err, "failed to parse float32") } values[i] = float32(num) } if err := typeutil.VerifyFloats32(values); err != nil { - return nil, fmt.Errorf("float32 verification failed: %w", err) + return nil, merr.Wrap(err, "float32 verification failed") } return &schemapb.ScalarField{ Data: &schemapb.ScalarField_FloatData{ @@ -790,12 +790,12 @@ func (r *rowParser) arrayToFieldData(arr []interface{}, field *schemapb.FieldSch } num, err := strconv.ParseFloat(value.String(), 64) if err != nil { - return nil, fmt.Errorf("failed to parse float64: %w", err) + return nil, merr.Wrap(err, "failed to parse float64") } values[i] = num } if err := typeutil.VerifyFloats64(values); err != nil { - return nil, fmt.Errorf("float64 verification failed: %w", err) + return nil, merr.Wrap(err, "float64 verification failed") } return &schemapb.ScalarField{ Data: &schemapb.ScalarField_DoubleData{ @@ -843,7 +843,7 @@ func (r *rowParser) arrayOfVectorToFieldData(vectors []any, field *schemapb.Fiel for i, vectorAny := range vectors { vector, ok := vectorAny.([]any) if !ok { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("expected slice as vector, but got %T", vectorAny)) + return nil, merr.WrapErrImportFailedMsg("expected slice as vector, but got %T", vectorAny) } if len(vector) != dim { return nil, merr.WrapErrImportFailed( @@ -872,8 +872,8 @@ func (r *rowParser) arrayOfVectorToFieldData(vectors []any, field *schemapb.Fiel case schemapb.DataType_Float16Vector, schemapb.DataType_BFloat16Vector, schemapb.DataType_BinaryVector, schemapb.DataType_Int8Vector, schemapb.DataType_SparseFloatVector: - return nil, merr.WrapErrImportFailed(fmt.Sprintf("not implemented element type: %s", elementType.String())) + return nil, merr.WrapErrImportFailedMsg("not implemented element type: %s", elementType.String()) default: - return nil, merr.WrapErrImportFailed(fmt.Sprintf("unsupported element type: %s", elementType.String())) + return nil, merr.WrapErrImportFailedMsg("unsupported element type: %s", elementType.String()) } } diff --git a/internal/util/importutilv2/numpy/field_reader.go b/internal/util/importutilv2/numpy/field_reader.go index 4534200410..a0608dd6f4 100644 --- a/internal/util/importutilv2/numpy/field_reader.go +++ b/internal/util/importutilv2/numpy/field_reader.go @@ -325,7 +325,7 @@ func (c *FieldReader) Next(count int64) (any, any, error) { } c.readPosition += int(readCount) default: - return nil, nil, merr.WrapErrImportFailed(fmt.Sprintf("unsupported data type: %s", dt.String())) + return nil, nil, merr.WrapErrImportFailedMsg("unsupported data type: %s", dt.String()) } return data, validData, nil } @@ -358,7 +358,7 @@ func (c *FieldReader) readFP16BF16Vector(readCount int64) ([]byte, error) { } return typeutil.ConvertFloat32ToFP16BF16Bytes(floatData, c.field.GetDataType()) default: - return nil, merr.WrapErrImportFailed(fmt.Sprintf("unsupported numpy element type %s for field '%s'", elementType.String(), c.field.GetName())) + return nil, merr.WrapErrImportFailedMsg("unsupported numpy element type %s for field '%s'", elementType.String(), c.field.GetName()) } } @@ -407,7 +407,7 @@ func (c *FieldReader) ReadString(count int64) ([]string, error) { // for non-ascii characters, the unicode could be 1 ~ 4 bytes, each character occupies 4 bytes, too raw, err := io.ReadAll(io.LimitReader(c.reader, utf8.UTFMax*int64(maxLen))) if err != nil { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("failed to read utf32 bytes from numpy file, error: %v", err)) + return nil, merr.WrapErrImportFailedMsg("failed to read utf32 bytes from numpy file, error: %v", err) } str, err := decodeUtf32(raw, c.order) if c.field.DataType == schemapb.DataType_VarChar { @@ -416,7 +416,7 @@ func (c *FieldReader) ReadString(count int64) ([]string, error) { } } if err != nil { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("failed to decode utf32 bytes, error: %v", err)) + return nil, merr.WrapErrImportFailedMsg("failed to decode utf32 bytes, error: %v", err) } data = append(data, str) } else { @@ -424,7 +424,7 @@ func (c *FieldReader) ReadString(count int64) ([]string, error) { // bytes.Index(buf, []byte{0}) tell us which position is the end of the string buf, err := io.ReadAll(io.LimitReader(c.reader, int64(maxLen))) if err != nil { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("failed to read ascii bytes from numpy file, error: %v", err)) + return nil, merr.WrapErrImportFailedMsg("failed to read ascii bytes from numpy file, error: %v", err) } n := bytes.Index(buf, []byte{0}) if n > 0 { diff --git a/internal/util/importutilv2/numpy/util.go b/internal/util/importutilv2/numpy/util.go index c9e0bfa662..d62bc61dd8 100644 --- a/internal/util/importutilv2/numpy/util.go +++ b/internal/util/importutilv2/numpy/util.go @@ -174,12 +174,12 @@ func stringLen(dtype string) (int, bool, error) { return v, utf, nil } - return 0, false, merr.WrapErrImportFailed(fmt.Sprintf("dtype '%s' of numpy file is not varchar data type", dtype)) + return 0, false, merr.WrapErrImportFailedMsg("dtype '%s' of numpy file is not varchar data type", dtype) } func decodeUtf32(src []byte, order binary.ByteOrder) (string, error) { if len(src)%4 != 0 { - return "", merr.WrapErrImportFailed(fmt.Sprintf("invalid utf32 bytes length %d, the byte array length should be multiple of 4", len(src))) + return "", merr.WrapErrImportFailedMsg("invalid utf32 bytes length %d, the byte array length should be multiple of 4", len(src)) } var str string @@ -208,7 +208,7 @@ func decodeUtf32(src []byte, order binary.ByteOrder) (string, error) { decoder := unicode.UTF16(uOrder, unicode.IgnoreBOM).NewDecoder() res, err := decoder.Bytes(src[lowbytesPosition : lowbytesPosition+2]) if err != nil { - return "", merr.WrapErrImportFailed(fmt.Sprintf("failed to decode utf32 binary bytes, error: %v", err)) + return "", merr.WrapErrImportFailedMsg("failed to decode utf32 binary bytes, error: %v", err) } str += string(res) } @@ -225,7 +225,7 @@ func decodeUtf32(src []byte, order binary.ByteOrder) (string, error) { utf8Code := make([]byte, 4) utf8.EncodeRune(utf8Code, r) if r == utf8.RuneError { - return "", merr.WrapErrImportFailed(fmt.Sprintf("failed to convert 4 bytes unicode %d to utf8 rune", x)) + return "", merr.WrapErrImportFailedMsg("failed to convert 4 bytes unicode %d to utf8 rune", x) } str += string(utf8Code) } @@ -261,30 +261,30 @@ func convertNumpyType(typeStr string) (schemapb.DataType, error) { // Note: JSON field and VARCHAR field are using string type numpy return schemapb.DataType_VarChar, nil } - return schemapb.DataType_None, fmt.Errorf("the numpy file dtype '%s' is not supported", typeStr) + return schemapb.DataType_None, merr.WrapErrParameterInvalidMsg("the numpy file dtype '%s' is not supported", typeStr) } } func wrapElementTypeError(eleType schemapb.DataType, field *schemapb.FieldSchema) error { - return merr.WrapErrImportFailed(fmt.Sprintf("expected element type '%s' for field '%s', got type '%s'", - field.GetDataType().String(), field.GetName(), eleType.String())) + return merr.WrapErrImportFailedMsg("expected element type '%s' for field '%s', got type '%s'", + field.GetDataType().String(), field.GetName(), eleType.String()) } func wrapDimError(actualDim int, expectDim int, field *schemapb.FieldSchema) error { - return merr.WrapErrImportFailed(fmt.Sprintf("expected dim '%d' for %s field '%s', got dim '%d'", - expectDim, field.GetDataType().String(), field.GetName(), actualDim)) + return merr.WrapErrImportFailedMsg("expected dim '%d' for %s field '%s', got dim '%d'", + expectDim, field.GetDataType().String(), field.GetName(), actualDim) } func wrapShapeError(actualShape int, expectShape int, field *schemapb.FieldSchema) error { - return merr.WrapErrImportFailed(fmt.Sprintf("expected shape '%d' for %s field '%s', got shape '%d'", - expectShape, field.GetDataType().String(), field.GetName(), actualShape)) + return merr.WrapErrImportFailedMsg("expected shape '%d' for %s field '%s', got shape '%d'", + expectShape, field.GetDataType().String(), field.GetName(), actualShape) } func validateHeader(npyReader *npy.Reader, field *schemapb.FieldSchema, dim int) error { elementType, err := convertNumpyType(npyReader.Header.Descr.Type) if err != nil { - return merr.WrapErrImportFailed(fmt.Sprintf("unexpected numpy header for field '%s': '%s'", - field.GetName(), err.Error())) + return merr.WrapErrImportFailedMsg("unexpected numpy header for field '%s': '%s'", + field.GetName(), err.Error()) } shape := npyReader.Header.Descr.Shape @@ -341,7 +341,7 @@ func validateHeader(npyReader *npy.Reader, field *schemapb.FieldSchema, dim int) return wrapShapeError(len(shape), 1, field) } case schemapb.DataType_None, schemapb.DataType_SparseFloatVector, schemapb.DataType_Array: - return merr.WrapErrImportFailed(fmt.Sprintf("unsupported data type: %s", field.GetDataType().String())) + return merr.WrapErrImportFailedMsg("unsupported data type: %s", field.GetDataType().String()) default: if elementType != field.GetDataType() { diff --git a/internal/util/importutilv2/option.go b/internal/util/importutilv2/option.go index db89bf3e04..f45466af99 100644 --- a/internal/util/importutilv2/option.go +++ b/internal/util/importutilv2/option.go @@ -82,7 +82,7 @@ func GetTimeoutTs(options Options) (uint64, error) { var dur time.Duration dur, err = time.ParseDuration(timeoutStr) if err != nil { - return 0, fmt.Errorf("parse timeout failed, err=%w", err) + return 0, merr.Wrap(err, "parse timeout failed") } curTs := tsoutil.GetCurrentTime() timeoutTs = tsoutil.AddPhysicalDurationOnTs(curTs, dur) @@ -98,10 +98,10 @@ func ParseTimeRange(options Options) (uint64, uint64, error) { if strings.EqualFold(key, targetKey) { ts, err := strconv.ParseUint(value, 10, 64) if err != nil { - return 0, merr.WrapErrImportFailed(fmt.Sprintf("parse %s failed, value=%s, err=%s", targetKey, value, err)) + return 0, merr.WrapErrImportFailedMsg("parse %s failed, value=%s, err=%s", targetKey, value, err) } if !tsoutil.IsValidHybridTs(ts) { - return 0, merr.WrapErrImportFailed(fmt.Sprintf("%s is not a valid hybrid timestamp, value=%s", targetKey, value)) + return 0, merr.WrapErrImportFailedMsg("%s is not a valid hybrid timestamp, value=%s", targetKey, value) } return ts, nil } @@ -148,7 +148,7 @@ func GetStorageVersion(options Options) (int64, error) { } version, err := strconv.ParseInt(storageVersion, 10, 64) if err != nil { - return 0, merr.WrapErrImportFailed(fmt.Sprintf("parse storage_version failed, value=%s, err=%s", storageVersion, err)) + return 0, merr.WrapErrImportFailedMsg("parse storage_version failed, value=%s, err=%s", storageVersion, err) } switch version { case storage.StorageV2: @@ -182,7 +182,7 @@ func GetCSVSep(options Options) (rune, error) { if err != nil || len(sep) == 0 { return defaultSep, nil } else if lo.Contains(unsupportedSep, []rune(sep)[0]) { - return 0, merr.WrapErrImportFailed(fmt.Sprintf("unsupported csv separator: %s", sep)) + return 0, merr.WrapErrImportFailedMsg("unsupported csv separator: %s", sep) } return []rune(sep)[0], nil } diff --git a/internal/util/importutilv2/parquet/field_reader.go b/internal/util/importutilv2/parquet/field_reader.go index 443ad89454..49c557f1ae 100644 --- a/internal/util/importutilv2/parquet/field_reader.go +++ b/internal/util/importutilv2/parquet/field_reader.go @@ -23,7 +23,6 @@ import ( "github.com/apache/arrow/go/v17/arrow" "github.com/apache/arrow/go/v17/arrow/array" "github.com/apache/arrow/go/v17/parquet/pqarrow" - "github.com/cockroachdb/errors" "github.com/samber/lo" "golang.org/x/exp/constraints" @@ -254,8 +253,8 @@ func (c *FieldReader) Next(count int64) (any, any, error) { data, err := ReadVectorArrayData(c, count) return data, nil, err default: - return nil, nil, merr.WrapErrImportFailed(fmt.Sprintf("unsupported data type '%s' for field '%s'", - c.field.GetDataType().String(), c.field.GetName())) + return nil, nil, merr.WrapErrImportFailedMsg("unsupported data type '%s' for field '%s'", + c.field.GetDataType().String(), c.field.GetName()) } } @@ -886,7 +885,7 @@ func ReadBinaryData(pcr *FieldReader, count int64) (any, error) { return nil, err } if err = checkListLikeVectorAligned(listReader, pcr.dim, dataType); err != nil { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("length of vector is not aligned: %s, data type: %s", err.Error(), dataType.String())) + return nil, merr.WrapErrImportFailedMsg("length of vector is not aligned: %s, data type: %s", err.Error(), dataType.String()) } uint8Reader, ok := listReader.ListValues().(*array.Uint8) if !ok { @@ -944,8 +943,8 @@ func ReadNullableBinaryData(pcr *FieldReader, count int64) (any, []bool, error) } else { value := binaryReader.Value(i) if len(value) != int(expectedRowWidth) { - return nil, nil, merr.WrapErrImportFailed(fmt.Sprintf("vector row width mismatch: field %s, row %d, expected %d bytes but got %d bytes, data type: %s", - pcr.field.GetName(), len(validData), expectedRowWidth, len(value), dataType.String())) + return nil, nil, merr.WrapErrImportFailedMsg("vector row width mismatch: field %s, row %d, expected %d bytes but got %d bytes, data type: %s", + pcr.field.GetName(), len(validData), expectedRowWidth, len(value), dataType.String()) } data = append(data, value...) validData = append(validData, true) @@ -957,7 +956,7 @@ func ReadNullableBinaryData(pcr *FieldReader, count int64) (any, []bool, error) return nil, nil, err } if err = checkNullableListLikeVectorAligned(listReader, pcr.dim, dataType); err != nil { - return nil, nil, merr.WrapErrImportFailed(fmt.Sprintf("length of vector is not aligned: %s, data type: %s", err.Error(), dataType.String())) + return nil, nil, merr.WrapErrImportFailedMsg("length of vector is not aligned: %s, data type: %s", err.Error(), dataType.String()) } uint8Reader, ok := listReader.ListValues().(*array.Uint8) if !ok { @@ -1003,7 +1002,7 @@ func ReadNullableBinaryData(pcr *FieldReader, count int64) (any, []bool, error) func parseSparseFloatRowVector(str string) ([]byte, uint32, error) { rowVec, err := typeutil.CreateSparseFloatRowFromJSON([]byte(str)) if err != nil { - return nil, 0, merr.WrapErrImportFailed(fmt.Sprintf("Invalid JSON string for SparseFloatVector: '%s', err = %v", str, err)) + return nil, 0, merr.WrapErrImportFailedMsg("Invalid JSON string for SparseFloatVector: '%s', err = %v", str, err) } elemCount := len(rowVec) / 8 maxIdx := uint32(0) @@ -1312,7 +1311,7 @@ func ReadNullableSparseFloatVectorData(pcr *FieldReader, count int64) (any, []bo func checkVectorAlignWithDim(offsets []int32, dim int32) error { for i := 1; i < len(offsets); i++ { if offsets[i]-offsets[i-1] != dim { - return fmt.Errorf("expected %d but got %d", dim, offsets[i]-offsets[i-1]) + return merr.WrapErrParameterInvalidMsg("expected %d but got %d", dim, offsets[i]-offsets[i-1]) } } return nil @@ -1320,7 +1319,7 @@ func checkVectorAlignWithDim(offsets []int32, dim int32) error { func checkVectorAligned(offsets []int32, dim int, dataType schemapb.DataType) error { if len(offsets) < 1 { - return errors.New("empty offsets") + return merr.WrapErrParameterInvalidMsg("empty offsets") } switch dataType { case schemapb.DataType_BinaryVector: @@ -1335,7 +1334,7 @@ func checkVectorAligned(offsets []int32, dim int, dataType schemapb.DataType) er case schemapb.DataType_Int8Vector: return checkVectorAlignWithDim(offsets, int32(dim)) default: - return fmt.Errorf("unexpected vector data type %s", dataType.String()) + return merr.WrapErrParameterInvalidMsg("unexpected vector data type %s", dataType.String()) } } @@ -1422,7 +1421,7 @@ func ReadIntegerOrFloatArrayData[T constraints.Integer | constraints.Float](pcr dataType := pcr.field.GetDataType() if typeutil.IsVectorType(dataType) { if err = checkListLikeVectorAligned(listReader, pcr.dim, dataType); err != nil { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("length of vector is not aligned: %s, data type: %s", err.Error(), dataType.String())) + return nil, merr.WrapErrImportFailedMsg("length of vector is not aligned: %s, data type: %s", err.Error(), dataType.String()) } } if err = readIntegerOrFloatListLikeData(pcr.field, listReader, func(arr []T, valid bool) { @@ -1459,7 +1458,7 @@ func ReadNullableIntegerOrFloatArrayData[T constraints.Integer | constraints.Flo dataType := pcr.field.GetDataType() if typeutil.IsVectorType(dataType) { if err = checkListLikeVectorAligned(listReader, pcr.dim, dataType); err != nil { - return nil, nil, merr.WrapErrImportFailed(fmt.Sprintf("length of vector is not aligned: %s, data type: %s", err.Error(), dataType.String())) + return nil, nil, merr.WrapErrImportFailedMsg("length of vector is not aligned: %s, data type: %s", err.Error(), dataType.String()) } } if err = readIntegerOrFloatListLikeData(pcr.field, listReader, func(arr []T, valid bool) { @@ -1501,7 +1500,7 @@ func ReadNullableFloatVectorData(pcr *FieldReader, count int64) (any, []bool, er dataType := pcr.field.GetDataType() if err = checkNullableListLikeVectorAligned(listReader, pcr.dim, dataType); err != nil { - return nil, nil, merr.WrapErrImportFailed(fmt.Sprintf("length of vector is not aligned: %s, data type: %s", err.Error(), dataType.String())) + return nil, nil, merr.WrapErrImportFailedMsg("length of vector is not aligned: %s, data type: %s", err.Error(), dataType.String()) } valueReader := listReader.ListValues() @@ -1583,7 +1582,7 @@ func ReadFP16BF16FloatVectorData(pcr *FieldReader, count int64) (any, error) { return nil, err } if err = checkListLikeVectorAlignedWithExpected(listReader, int32(pcr.dim)); err != nil { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("length of vector is not aligned: %s, data type: %s", err.Error(), pcr.field.GetDataType().String())) + return nil, merr.WrapErrImportFailedMsg("length of vector is not aligned: %s, data type: %s", err.Error(), pcr.field.GetDataType().String()) } floatRows := make([][]float32, 0, int(count)) if err = readFloatListLikeDataAsFloat32(pcr.field, listReader, func(arr []float32, valid bool) { @@ -1621,7 +1620,7 @@ func ReadNullableFP16BF16FloatVectorData(pcr *FieldReader, count int64) (any, [] return nil, nil, err } if err = checkNullableListLikeVectorAlignedWithExpected(listReader, int32(pcr.dim)); err != nil { - return nil, nil, merr.WrapErrImportFailed(fmt.Sprintf("length of vector is not aligned: %s, data type: %s", err.Error(), pcr.field.GetDataType().String())) + return nil, nil, merr.WrapErrImportFailedMsg("length of vector is not aligned: %s, data type: %s", err.Error(), pcr.field.GetDataType().String()) } floatRows := make([][]float32, 0, int(count)) if err = readFloatListLikeDataAsFloat32(pcr.field, listReader, func(arr []float32, valid bool) { @@ -1666,7 +1665,7 @@ func ReadNullableInt8VectorData(pcr *FieldReader, count int64) (any, []bool, err dataType := pcr.field.GetDataType() if err = checkNullableListLikeVectorAligned(listReader, pcr.dim, dataType); err != nil { - return nil, nil, merr.WrapErrImportFailed(fmt.Sprintf("length of vector is not aligned: %s, data type: %s", err.Error(), dataType.String())) + return nil, nil, merr.WrapErrImportFailedMsg("length of vector is not aligned: %s, data type: %s", err.Error(), dataType.String()) } valueReader := listReader.ListValues() @@ -1893,7 +1892,7 @@ func ReadArrayData(pcr *FieldReader, count int64) (any, error) { } for _, elementArray := range float32Array.([][]float32) { if err := typeutil.VerifyFloats32(elementArray); err != nil { - return nil, fmt.Errorf("float32 verification failed: %w", err) + return nil, merr.Wrap(err, "float32 verification failed") } if err = common.CheckArrayCapacity(len(elementArray), maxCapacity, pcr.field); err != nil { return nil, err @@ -1916,7 +1915,7 @@ func ReadArrayData(pcr *FieldReader, count int64) (any, error) { } for _, elementArray := range float64Array.([][]float64) { if err := typeutil.VerifyFloats64(elementArray); err != nil { - return nil, fmt.Errorf("float64 verification failed: %w", err) + return nil, merr.Wrap(err, "float64 verification failed") } if err = common.CheckArrayCapacity(len(elementArray), maxCapacity, pcr.field); err != nil { return nil, err @@ -1970,8 +1969,8 @@ func ReadArrayData(pcr *FieldReader, count int64) (any, error) { }) } default: - return nil, merr.WrapErrImportFailed(fmt.Sprintf("unsupported data type '%s' for array field '%s'", - elementType.String(), pcr.field.GetName())) + return nil, merr.WrapErrImportFailedMsg("unsupported data type '%s' for array field '%s'", + elementType.String(), pcr.field.GetName()) } return data, nil } @@ -2174,8 +2173,8 @@ func ReadNullableArrayData(pcr *FieldReader, count int64) (any, []bool, error) { } return data, validData, nil default: - return nil, nil, merr.WrapErrImportFailed(fmt.Sprintf("unsupported data type '%s' for array field '%s'", - elementType.String(), pcr.field.GetName())) + return nil, nil, merr.WrapErrImportFailedMsg("unsupported data type '%s' for array field '%s'", + elementType.String(), pcr.field.GetName()) } } @@ -2309,7 +2308,7 @@ func vectorArrayBytesPerVector(elementType schemapb.DataType, dim int64) (int, e case schemapb.DataType_SparseFloatVector: return 0, merr.WrapErrImportFailed("ArrayOfVector with SparseFloatVector element type is not implemented yet") default: - return 0, merr.WrapErrImportFailed(fmt.Sprintf("unsupported ArrayOfVector element type: %v", elementType)) + return 0, merr.WrapErrImportFailedMsg("unsupported ArrayOfVector element type: %v", elementType) } } @@ -2320,8 +2319,8 @@ func buildVectorArrayFieldFromFixedSizeBinary(field *schemapb.FieldSchema, dim i } actualByteSize := values.DataType().(*arrow.FixedSizeBinaryType).ByteWidth if actualByteSize != bytesPerVector { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("vector byte size mismatch: expected %d, got %d for field '%s'", - bytesPerVector, actualByteSize, field.GetName())) + return nil, merr.WrapErrImportFailedMsg("vector byte size mismatch: expected %d, got %d for field '%s'", + bytesPerVector, actualByteSize, field.GetName()) } switch field.GetElementType() { @@ -2392,7 +2391,7 @@ func buildVectorArrayFieldFromFixedSizeBinary(field *schemapb.FieldSchema, dim i Data: &schemapb.VectorField_Int8Vector{Int8Vector: int8Data}, }, nil default: - return nil, merr.WrapErrImportFailed(fmt.Sprintf("unsupported ArrayOfVector element type: %v", field.GetElementType())) + return nil, merr.WrapErrImportFailedMsg("unsupported ArrayOfVector element type: %v", field.GetElementType()) } } @@ -2483,7 +2482,7 @@ func buildVectorArrayFieldFromList(field *schemapb.FieldSchema, dim int64, vecto case schemapb.DataType_SparseFloatVector: return nil, merr.WrapErrImportFailed("ArrayOfVector with SparseFloatVector element type is not implemented yet") default: - return nil, merr.WrapErrImportFailed(fmt.Sprintf("unsupported ArrayOfVector element type: %v", field.GetElementType())) + return nil, merr.WrapErrImportFailedMsg("unsupported ArrayOfVector element type: %v", field.GetElementType()) } } diff --git a/internal/util/importutilv2/parquet/list_like.go b/internal/util/importutilv2/parquet/list_like.go index e4fedc5ab4..a3798e8ddd 100644 --- a/internal/util/importutilv2/parquet/list_like.go +++ b/internal/util/importutilv2/parquet/list_like.go @@ -17,13 +17,12 @@ package parquet import ( - "fmt" - "github.com/apache/arrow/go/v17/arrow" "github.com/apache/arrow/go/v17/arrow/array" "golang.org/x/exp/constraints" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type listLikeArray struct { @@ -193,7 +192,7 @@ func expectedVectorListLength(dim int, dataType schemapb.DataType) (int32, error case schemapb.DataType_Int8Vector: return int32(dim), nil default: - return 0, fmt.Errorf("unexpected vector data type %s", dataType.String()) + return 0, merr.WrapErrParameterInvalidMsg("unexpected vector data type %s", dataType.String()) } } diff --git a/internal/util/importutilv2/parquet/reader.go b/internal/util/importutilv2/parquet/reader.go index a3325eb743..7e555089d1 100644 --- a/internal/util/importutilv2/parquet/reader.go +++ b/internal/util/importutilv2/parquet/reader.go @@ -18,7 +18,6 @@ package parquet import ( "context" - "fmt" "io" "github.com/apache/arrow/go/v17/arrow/memory" @@ -73,7 +72,7 @@ func NewReader(ctx context.Context, cm storage.ChunkManager, schema *schemapb.Co })) if err != nil { retryableReader.Close() - return nil, merr.WrapErrImportFailed(fmt.Sprintf("new parquet reader failed, err=%v", err)) + return nil, merr.WrapErrImportSysFailedMsg("new parquet reader failed, err=%v", err) } log.Info("parquet file info", zap.Int("row group num", r.NumRowGroups()), zap.Int64("num rows", r.NumRows())) @@ -90,7 +89,7 @@ func NewReader(ctx context.Context, cm storage.ChunkManager, schema *schemapb.Co fileReader, err := pqarrow.NewFileReader(r, readProps, memory.DefaultAllocator) if err != nil { r.Close() - return nil, merr.WrapErrImportFailed(fmt.Sprintf("new parquet file reader failed, err=%v", err)) + return nil, merr.WrapErrImportSysFailedMsg("new parquet file reader failed, err=%v", err) } crs, err := CreateFieldReaders(ctx, fileReader, schema) diff --git a/internal/util/importutilv2/parquet/struct_field_reader.go b/internal/util/importutilv2/parquet/struct_field_reader.go index 96059f560f..059e01f8a1 100644 --- a/internal/util/importutilv2/parquet/struct_field_reader.go +++ b/internal/util/importutilv2/parquet/struct_field_reader.go @@ -18,7 +18,6 @@ package parquet import ( "context" - "fmt" "strings" "github.com/apache/arrow/go/v17/arrow" @@ -101,7 +100,7 @@ func (r *StructFieldReader) Next(count int64) (any, any, error) { case schemapb.DataType_ArrayOfVector: return r.readArrayOfVectorField(chunked) default: - return nil, nil, merr.WrapErrImportFailed(fmt.Sprintf("unsupported data type for struct field: %v", r.field.GetDataType())) + return nil, nil, merr.WrapErrImportFailedMsg("unsupported data type for struct field: %v", r.field.GetDataType()) } } @@ -113,7 +112,7 @@ func (r *StructFieldReader) toScalarField(data []interface{}) (*schemapb.ScalarF for i, v := range data { val, ok := v.(bool) if !ok { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("expected bool for field '%s', got %T at index %d", r.field.GetName(), v, i)) + return nil, merr.WrapErrImportFailedMsg("expected bool for field '%s', got %T at index %d", r.field.GetName(), v, i) } boolData[i] = val } @@ -127,7 +126,7 @@ func (r *StructFieldReader) toScalarField(data []interface{}) (*schemapb.ScalarF for i, v := range data { val, ok := v.(int8) if !ok { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("expected int8 for field '%s', got %T at index %d", r.field.GetName(), v, i)) + return nil, merr.WrapErrImportFailedMsg("expected int8 for field '%s', got %T at index %d", r.field.GetName(), v, i) } intData[i] = int32(val) } @@ -141,7 +140,7 @@ func (r *StructFieldReader) toScalarField(data []interface{}) (*schemapb.ScalarF for i, v := range data { val, ok := v.(int16) if !ok { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("expected int16 for field '%s', got %T at index %d", r.field.GetName(), v, i)) + return nil, merr.WrapErrImportFailedMsg("expected int16 for field '%s', got %T at index %d", r.field.GetName(), v, i) } intData[i] = int32(val) } @@ -155,7 +154,7 @@ func (r *StructFieldReader) toScalarField(data []interface{}) (*schemapb.ScalarF for i, v := range data { val, ok := v.(int32) if !ok { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("expected int32 for field '%s', got %T at index %d", r.field.GetName(), v, i)) + return nil, merr.WrapErrImportFailedMsg("expected int32 for field '%s', got %T at index %d", r.field.GetName(), v, i) } intData[i] = val } @@ -169,7 +168,7 @@ func (r *StructFieldReader) toScalarField(data []interface{}) (*schemapb.ScalarF for i, v := range data { val, ok := v.(int64) if !ok { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("expected int64 for field '%s', got %T at index %d", r.field.GetName(), v, i)) + return nil, merr.WrapErrImportFailedMsg("expected int64 for field '%s', got %T at index %d", r.field.GetName(), v, i) } intData[i] = val } @@ -183,7 +182,7 @@ func (r *StructFieldReader) toScalarField(data []interface{}) (*schemapb.ScalarF for i, v := range data { val, ok := v.(float32) if !ok { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("expected float32 for field '%s', got %T at index %d", r.field.GetName(), v, i)) + return nil, merr.WrapErrImportFailedMsg("expected float32 for field '%s', got %T at index %d", r.field.GetName(), v, i) } floatData[i] = val } @@ -197,7 +196,7 @@ func (r *StructFieldReader) toScalarField(data []interface{}) (*schemapb.ScalarF for i, v := range data { val, ok := v.(float64) if !ok { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("expected float64 for field '%s', got %T at index %d", r.field.GetName(), v, i)) + return nil, merr.WrapErrImportFailedMsg("expected float64 for field '%s', got %T at index %d", r.field.GetName(), v, i) } floatData[i] = val } @@ -211,7 +210,7 @@ func (r *StructFieldReader) toScalarField(data []interface{}) (*schemapb.ScalarF for i, v := range data { val, ok := v.(string) if !ok { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("expected string for field '%s', got %T at index %d", r.field.GetName(), v, i)) + return nil, merr.WrapErrImportFailedMsg("expected string for field '%s', got %T at index %d", r.field.GetName(), v, i) } strData[i] = val } @@ -221,7 +220,7 @@ func (r *StructFieldReader) toScalarField(data []interface{}) (*schemapb.ScalarF }, }, nil default: - return nil, merr.WrapErrImportFailed(fmt.Sprintf("unsupported element type for struct field: %v", r.field.GetElementType())) + return nil, merr.WrapErrImportFailedMsg("unsupported element type for struct field: %v", r.field.GetElementType()) } } @@ -325,7 +324,7 @@ func (r *StructFieldReader) readArrayField(chunked *arrow.Chunked) (any, any, er } value := field.Value(int(structIdx)) if err := typeutil.VerifyFloat(float64(value)); err != nil { - return nil, nil, fmt.Errorf("float32 verification failed: %w", err) + return nil, nil, merr.Wrap(err, "float32 verification failed") } combinedData = append(combinedData, value) case *array.Float64: @@ -334,7 +333,7 @@ func (r *StructFieldReader) readArrayField(chunked *arrow.Chunked) (any, any, er } value := field.Value(int(structIdx)) if err := typeutil.VerifyFloat(value); err != nil { - return nil, nil, fmt.Errorf("float64 verification failed: %w", err) + return nil, nil, merr.Wrap(err, "float64 verification failed") } combinedData = append(combinedData, value) case *array.String: diff --git a/internal/util/importutilv2/parquet/util.go b/internal/util/importutilv2/parquet/util.go index c657edf21b..f680e96143 100644 --- a/internal/util/importutilv2/parquet/util.go +++ b/internal/util/importutilv2/parquet/util.go @@ -75,7 +75,7 @@ func CreateFieldReaders(ctx context.Context, fileReader *pqarrow.FileReader, sch pqSchema, err := fileReader.Schema() if err != nil { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("get parquet schema failed, err=%v", err)) + return nil, merr.WrapErrImportFailedMsg("get parquet schema failed, err=%v", err) } if err := rejectFlatStructSubFieldColumns(schema, pqSchema); err != nil { @@ -91,11 +91,11 @@ func CreateFieldReaders(ctx context.Context, fileReader *pqarrow.FileReader, sch } listType, ok := pqField.Type.(*arrow.ListType) if !ok { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("struct field is not a list of structs: %s", structField.Name)) + return nil, merr.WrapErrImportFailedMsg("struct field is not a list of structs: %s", structField.Name) } structType, ok := listType.Elem().(*arrow.StructType) if !ok { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("struct field is not a list of structs: %s", structField.Name)) + return nil, merr.WrapErrImportFailedMsg("struct field is not a list of structs: %s", structField.Name) } nestedStructs[structField.Name] = i // Verify struct fields match @@ -112,7 +112,7 @@ func CreateFieldReaders(ctx context.Context, fileReader *pqarrow.FileReader, sch } } if !found { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("field not found in struct: %s", fieldName)) + return nil, merr.WrapErrImportFailedMsg("field not found in struct: %s", fieldName) } } } @@ -121,7 +121,7 @@ func CreateFieldReaders(ctx context.Context, fileReader *pqarrow.FileReader, sch // Original flat format handling err = isSchemaEqual(schema, pqSchema) if err != nil { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("schema not equal, err=%v", err)) + return nil, merr.WrapErrImportFailedMsg("schema not equal, err=%v", err) } // this loop is for "how many fields are provided by this parquet file?" @@ -175,7 +175,7 @@ func CreateFieldReaders(ctx context.Context, fileReader *pqarrow.FileReader, sch if structField.GetNullable() { continue } - return nil, merr.WrapErrImportFailed(fmt.Sprintf("struct field not found in parquet schema: %s", structField.Name)) + return nil, merr.WrapErrImportFailedMsg("struct field not found in parquet schema: %s", structField.Name) } listType := pqSchema.Field(columnIndex).Type.(*arrow.ListType) @@ -198,7 +198,7 @@ func CreateFieldReaders(ctx context.Context, fileReader *pqarrow.FileReader, sch } if fieldIndex == -1 { - return nil, merr.WrapErrImportFailed(fmt.Sprintf("field not found in struct: %s", fieldName)) + return nil, merr.WrapErrImportFailedMsg("field not found in struct: %s", fieldName) } // Create struct field reader @@ -238,9 +238,9 @@ func rejectFlatStructSubFieldColumns(schema *schemapb.CollectionSchema, arrSchem for _, structField := range schema.GetStructArrayFields() { for _, subField := range structField.GetFields() { if _, ok := arrNameToField[subField.GetName()]; ok { - return merr.WrapErrImportFailed(fmt.Sprintf( + return merr.WrapErrImportFailedMsg( "struct field '%s' must be provided as list; flat sub-field column '%s' is not supported", - structField.GetName(), subField.GetName())) + structField.GetName(), subField.GetName()) } } } @@ -585,15 +585,15 @@ func isSchemaEqual(schema *schemapb.CollectionSchema, arrSchema *arrow.Schema) e if field.GetIsDynamic() || field.GetNullable() || field.GetDefaultValue() != nil { continue } - return merr.WrapErrImportFailed(fmt.Sprintf("field '%s' not in arrow schema", field.GetName())) + return merr.WrapErrImportFailedMsg("field '%s' not in arrow schema", field.GetName()) } toArrDataType, err := convertToArrowDataType(field, false) if err != nil { return err } if !isArrowDataTypeConvertible(arrField.Type, toArrDataType, field, true) { - return merr.WrapErrImportFailed(fmt.Sprintf("field '%s' type mis-match, expect arrow type '%s', get arrow data type '%s'", - field.Name, toArrDataType.String(), arrField.Type.String())) + return merr.WrapErrImportFailedMsg("field '%s' type mis-match, expect arrow type '%s', get arrow data type '%s'", + field.Name, toArrDataType.String(), arrField.Type.String()) } } @@ -603,20 +603,20 @@ func isSchemaEqual(schema *schemapb.CollectionSchema, arrSchema *arrow.Schema) e if structField.GetNullable() { continue } - return merr.WrapErrImportFailed(fmt.Sprintf("struct field not found in arrow schema: %s", structField.Name)) + return merr.WrapErrImportFailedMsg("struct field not found in arrow schema: %s", structField.Name) } // Verify the arrow field is list type listType, ok := arrStructField.Type.(*arrow.ListType) if !ok { - return merr.WrapErrImportFailed(fmt.Sprintf("struct field '%s' should be list type in arrow schema, but got '%s'", - structField.Name, arrStructField.Type.String())) + return merr.WrapErrImportFailedMsg("struct field '%s' should be list type in arrow schema, but got '%s'", + structField.Name, arrStructField.Type.String()) } structType, ok := listType.Elem().(*arrow.StructType) if !ok { - return merr.WrapErrImportFailed(fmt.Sprintf("struct field '%s' should contain struct elements in arrow schema, but got '%s'", - structField.Name, listType.Elem().String())) + return merr.WrapErrImportFailedMsg("struct field '%s' should contain struct elements in arrow schema, but got '%s'", + structField.Name, listType.Elem().String()) } // Create a map of struct field names to arrow.Field for quick lookup @@ -635,8 +635,8 @@ func isSchemaEqual(schema *schemapb.CollectionSchema, arrSchema *arrow.Schema) e arrowSubField, ok := structFieldMap[fieldName] if !ok { - return merr.WrapErrImportFailed(fmt.Sprintf("sub-field '%s' not found in struct '%s' of arrow schema", - fieldName, structField.Name)) + return merr.WrapErrImportFailedMsg("sub-field '%s' not found in struct '%s' of arrow schema", + fieldName, structField.Name) } // Convert Milvus field type to expected Arrow type @@ -655,18 +655,18 @@ func isSchemaEqual(schema *schemapb.CollectionSchema, arrSchema *arrow.Schema) e return err } default: - return merr.WrapErrImportFailed(fmt.Sprintf("unsupported data type in struct field: %v", subField.DataType)) + return merr.WrapErrImportFailedMsg("unsupported data type in struct field: %v", subField.DataType) } // Check if the arrow type is convertible to the expected type if !isArrowDataTypeConvertible(arrowSubField.Type, expectedArrowType, subField, false) { - return merr.WrapErrImportFailed(fmt.Sprintf("sub-field '%s' in struct '%s' type mis-match, expect arrow type '%s', got '%s'", - fieldName, structField.Name, expectedArrowType.String(), arrowSubField.Type.String())) + return merr.WrapErrImportFailedMsg("sub-field '%s' in struct '%s' type mis-match, expect arrow type '%s', got '%s'", + fieldName, structField.Name, expectedArrowType.String(), arrowSubField.Type.String()) } } if len(structFieldMap) != len(structField.Fields) { - return merr.WrapErrImportFailed(fmt.Sprintf("struct field number dismatch: %s, expect %d, got %d", structField.Name, len(structField.Fields), len(structFieldMap))) + return merr.WrapErrImportFailedMsg("struct field number dismatch: %s, expect %d, got %d", structField.Name, len(structField.Fields), len(structFieldMap)) } } diff --git a/internal/util/importutilv2/util.go b/internal/util/importutilv2/util.go index dedb291e58..a8e9f09973 100644 --- a/internal/util/importutilv2/util.go +++ b/internal/util/importutilv2/util.go @@ -105,5 +105,5 @@ func GetFileType(file *internalpb.ImportFile) (FileType, error) { } return CSV, nil } - return Invalid, merr.WrapErrImportFailed(fmt.Sprintf("unexpected file type, files=%v", file.GetPaths())) + return Invalid, merr.WrapErrImportFailedMsg("unexpected file type, files=%v", file.GetPaths()) } diff --git a/internal/util/indexcgowrapper/helper.go b/internal/util/indexcgowrapper/helper.go index 6e5c571b20..acfc67fc1a 100644 --- a/internal/util/indexcgowrapper/helper.go +++ b/internal/util/indexcgowrapper/helper.go @@ -13,8 +13,6 @@ import ( "fmt" "unsafe" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/util/merr" ) @@ -22,7 +20,7 @@ import ( func GetBinarySetKeys(cBinarySet C.CBinarySet) ([]string, error) { size := int(C.GetBinarySetSize(cBinarySet)) if size == 0 { - return nil, errors.New("BinarySet size is zero") + return nil, merr.WrapErrParameterInvalidMsg("BinarySet size is zero") } datas := make([]unsafe.Pointer, size) @@ -41,7 +39,7 @@ func GetBinarySetValue(cBinarySet C.CBinarySet, key string) ([]byte, error) { ret := C.GetBinarySetValueSize(cBinarySet, cIndexKey) size := int(ret) if size == 0 { - return nil, errors.New("GetBinarySetValueSize size is zero") + return nil, merr.WrapErrParameterInvalidMsg("GetBinarySetValueSize size is zero") } value := make([]byte, size) status := C.CopyBinarySetValue(unsafe.Pointer(&value[0]), cIndexKey, cBinarySet) @@ -71,13 +69,12 @@ func HandleCStatus(status *C.CStatus, extraInfo string) error { logMsg := fmt.Sprintf("%s, C Runtime Exception: %s\n", extraInfo, errorMsg) log.Warn(logMsg) - if errorCode == 2003 { - return merr.WrapErrSegcoreUnsupported(int32(errorCode), logMsg) + if merr.IsSegcoreSignal(int32(errorCode)) { + log.Info("fake finished the task") } - if errorCode == 2033 { - return merr.ErrSegcorePretendFinished - } - return merr.WrapErrSegcore(int32(errorCode), logMsg) + // Pass the raw errorMsg (not the polluted logMsg) so the merr reason stays + // clean; the extraInfo breadcrumb lives in the log above. + return merr.SegcoreError(int32(errorCode), errorMsg) } func GetLocalUsedSize(path string) (int64, error) { diff --git a/internal/util/indexcgowrapper/index.go b/internal/util/indexcgowrapper/index.go index fcddf10652..5ae0120d30 100644 --- a/internal/util/indexcgowrapper/index.go +++ b/internal/util/indexcgowrapper/index.go @@ -11,7 +11,6 @@ import "C" import ( "context" - "fmt" "path/filepath" "runtime" "unsafe" @@ -28,6 +27,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/proto/cgopb" "github.com/milvus-io/milvus/pkg/v3/proto/indexcgopb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type Blob = storage.Blob @@ -182,7 +182,7 @@ func CreateJSONKeyStats(ctx context.Context, buildIndexInfo *indexcgopb.BuildInd func (index *CgoIndex) Build(dataset *Dataset) error { switch dataset.DType { case schemapb.DataType_None: - return fmt.Errorf("build index on supported data type: %s", dataset.DType.String()) + return merr.WrapErrParameterInvalidMsg("build index on supported data type: %s", dataset.DType.String()) case schemapb.DataType_FloatVector: return index.buildFloatVecIndex(dataset) case schemapb.DataType_Float16Vector: @@ -214,7 +214,7 @@ func (index *CgoIndex) Build(dataset *Dataset) error { case schemapb.DataType_VarChar: return index.buildStringIndex(dataset) default: - return fmt.Errorf("build index on unsupported data type: %s", dataset.DType.String()) + return merr.WrapErrParameterInvalidMsg("build index on unsupported data type: %s", dataset.DType.String()) } } @@ -306,7 +306,7 @@ func (index *CgoIndex) buildSparseFloatVecIndex(dataset *Dataset) error { if validData, ok := dataset.Data[keyValidArr].([]bool); ok && len(validData) > 0 { validRows := validCount(validData) if validRows > 0 && len(vectors) == 0 { - return fmt.Errorf("sparse float vector cgo build requires encoded sparse rows") + return merr.WrapErrParameterInvalidMsg("sparse float vector cgo build requires encoded sparse rows") } status := C.BuildSparseFloatVecIndexWithValidData( index.indexPtr, @@ -318,7 +318,7 @@ func (index *CgoIndex) buildSparseFloatVecIndex(dataset *Dataset) error { return HandleCStatus(&status, "failed to build sparse float vector index with valid data") } if len(vectors) == 0 { - return fmt.Errorf("sparse float vector cgo build requires encoded sparse rows") + return merr.WrapErrParameterInvalidMsg("sparse float vector cgo build requires encoded sparse rows") } status := C.BuildSparseFloatVecIndex(index.indexPtr, (C.int64_t)(len(vectors)), (C.int64_t)(0), cUint8Ptr(vectors)) return HandleCStatus(&status, "failed to build sparse float vector index") diff --git a/internal/util/indexparamcheck/base_checker.go b/internal/util/indexparamcheck/base_checker.go index 34c9ea4e75..b8bf749d94 100644 --- a/internal/util/indexparamcheck/base_checker.go +++ b/internal/util/indexparamcheck/base_checker.go @@ -17,9 +17,8 @@ package indexparamcheck import ( - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type baseChecker struct{} @@ -36,7 +35,7 @@ func (c baseChecker) CheckValidDataType(indexType IndexType, field *schemapb.Fie func (c baseChecker) SetDefaultMetricTypeIfNotExist(dType schemapb.DataType, m map[string]string) {} func (c baseChecker) StaticCheck(dataType schemapb.DataType, elementType schemapb.DataType, params map[string]string) error { - return errors.New("unsupported index type") + return merr.WrapErrParameterInvalidMsg("unsupported index type") } func newBaseChecker() IndexChecker { diff --git a/internal/util/indexparamcheck/bitmap_index_checker.go b/internal/util/indexparamcheck/bitmap_index_checker.go index 84b0bdb4af..86096b8ee9 100644 --- a/internal/util/indexparamcheck/bitmap_index_checker.go +++ b/internal/util/indexparamcheck/bitmap_index_checker.go @@ -1,7 +1,6 @@ package indexparamcheck import ( - "github.com/cockroachdb/errors" "github.com/samber/lo" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" @@ -34,7 +33,7 @@ func (c *BITMAPChecker) CheckTrain(dataType schemapb.DataType, elementType schem func (c *BITMAPChecker) CheckValidDataType(indexType IndexType, field *schemapb.FieldSchema) error { if field.IsPrimaryKey { - return errors.New("create bitmap index on primary key not supported") + return merr.WrapErrParameterInvalidMsg("create bitmap index on primary key not supported") } mainType := field.GetDataType() elemType := field.GetElementType() @@ -43,12 +42,12 @@ func (c *BITMAPChecker) CheckValidDataType(indexType IndexType, field *schemapb. } if !typeutil.IsBoolType(mainType) && !typeutil.IsIntegerType(mainType) && !typeutil.IsStringType(mainType) && !typeutil.IsArrayType(mainType) { - return errors.New("bitmap index are only supported on bool, int, string and array field") + return merr.WrapErrParameterInvalidMsg("bitmap index are only supported on bool, int, string and array field") } if typeutil.IsArrayType(mainType) { if !typeutil.IsBoolType(elemType) && !typeutil.IsIntegerType(elemType) && !typeutil.IsStringType(elemType) { - return errors.New("bitmap index are only supported on bool, int, string for array field") + return merr.WrapErrParameterInvalidMsg("bitmap index are only supported on bool, int, string for array field") } } return nil diff --git a/internal/util/indexparamcheck/conf_adapter_mgr.go b/internal/util/indexparamcheck/conf_adapter_mgr.go index 81664e0e99..a2316ceee3 100644 --- a/internal/util/indexparamcheck/conf_adapter_mgr.go +++ b/internal/util/indexparamcheck/conf_adapter_mgr.go @@ -19,9 +19,8 @@ package indexparamcheck import ( "sync" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus/internal/util/vecindexmgr" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type IndexCheckerMgr interface { @@ -44,7 +43,7 @@ func (mgr *indexCheckerMgrImpl) GetChecker(indexType string) (IndexChecker, erro if ok { return adapter, nil } - return nil, errors.New("Can not find index: " + indexType + " , please check") + return nil, merr.WrapErrParameterInvalidMsg("Can not find index: " + indexType + " , please check") } func (mgr *indexCheckerMgrImpl) registerIndexChecker() { diff --git a/internal/util/indexparamcheck/hybrid_index_checker.go b/internal/util/indexparamcheck/hybrid_index_checker.go index e7c254046d..fc312ee621 100644 --- a/internal/util/indexparamcheck/hybrid_index_checker.go +++ b/internal/util/indexparamcheck/hybrid_index_checker.go @@ -1,9 +1,6 @@ package indexparamcheck import ( - "fmt" - - "github.com/cockroachdb/errors" "github.com/samber/lo" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" @@ -33,14 +30,14 @@ func (c *HYBRIDChecker) CheckTrain(dataType schemapb.DataType, elementType schem // For JSON, bitmap_cardinality_limit is optional — validate only if present if _, exist := params[common.BitmapCardinalityLimitKey]; exist { if !CheckIntByRange(params, common.BitmapCardinalityLimitKey, 1, MaxBitmapCardinalityLimit) { - return fmt.Errorf("failed to check bitmap cardinality limit, should be larger than 0 and smaller than %d", + return merr.WrapErrParameterInvalidMsg("failed to check bitmap cardinality limit, should be larger than 0 and smaller than %d", MaxBitmapCardinalityLimit) } } return nil } if !CheckIntByRange(params, common.BitmapCardinalityLimitKey, 1, MaxBitmapCardinalityLimit) { - return fmt.Errorf("failed to check bitmap cardinality limit, should be larger than 0 and smaller than %d", + return merr.WrapErrParameterInvalidMsg("failed to check bitmap cardinality limit, should be larger than 0 and smaller than %d", MaxBitmapCardinalityLimit) } return c.scalarIndexChecker.CheckTrain(dataType, elementType, params) @@ -55,12 +52,12 @@ func (c *HYBRIDChecker) CheckValidDataType(indexType IndexType, field *schemapb. if !typeutil.IsBoolType(mainType) && !typeutil.IsIntegerType(mainType) && !typeutil.IsStringType(mainType) && !typeutil.IsArrayType(mainType) && !typeutil.IsFloatingType(mainType) { - return errors.New("hybrid index are only supported on bool, int, float, string and array field") + return merr.WrapErrParameterInvalidMsg("hybrid index are only supported on bool, int, float, string and array field") } if typeutil.IsArrayType(mainType) { if !typeutil.IsBoolType(elemType) && !typeutil.IsIntegerType(elemType) && !typeutil.IsStringType(elemType) && !typeutil.IsFloatingType(elemType) { - return errors.New("hybrid index are only supported on bool, int, float, string for array field") + return merr.WrapErrParameterInvalidMsg("hybrid index are only supported on bool, int, float, string for array field") } } return nil diff --git a/internal/util/indexparamcheck/index_type.go b/internal/util/indexparamcheck/index_type.go index f3f4cb3e99..fca35fb372 100644 --- a/internal/util/indexparamcheck/index_type.go +++ b/internal/util/indexparamcheck/index_type.go @@ -12,11 +12,11 @@ package indexparamcheck import ( - "fmt" "strconv" "github.com/milvus-io/milvus/internal/util/vecindexmgr" "github.com/milvus-io/milvus/pkg/v3/common" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // IndexType string. @@ -76,11 +76,11 @@ func ValidateMmapIndexParams(indexType IndexType, indexParams map[string]string) } enable, err := strconv.ParseBool(mmapEnable) if err != nil { - return fmt.Errorf("invalid %s value: %s, expected: true, false", common.MmapEnabledKey, mmapEnable) + return merr.WrapErrParameterInvalidMsg("invalid %s value: %s, expected: true, false", common.MmapEnabledKey, mmapEnable) } mmapSupport := indexType == AutoIndex || IsVectorMmapIndex(indexType) || IsScalarMmapIndex(indexType) if enable && !mmapSupport { - return fmt.Errorf("index type %s does not support mmap", indexType) + return merr.WrapErrParameterInvalidMsg("index type %s does not support mmap", indexType) } return nil } @@ -92,10 +92,10 @@ func ValidateOffsetCacheIndexParams(indexType IndexType, indexParams map[string] } enable, err := strconv.ParseBool(offsetCacheEnable) if err != nil { - return fmt.Errorf("invalid %s value: %s, expected: true, false", common.IndexOffsetCacheEnabledKey, offsetCacheEnable) + return merr.WrapErrParameterInvalidMsg("invalid %s value: %s, expected: true, false", common.IndexOffsetCacheEnabledKey, offsetCacheEnable) } if enable && !IsOffsetCacheSupported(indexType) { - return fmt.Errorf("only bitmap index support %s now", common.IndexOffsetCacheEnabledKey) + return merr.WrapErrParameterInvalidMsg("only bitmap index support %s now", common.IndexOffsetCacheEnabledKey) } return nil } diff --git a/internal/util/indexparamcheck/inverted_checker.go b/internal/util/indexparamcheck/inverted_checker.go index 1169249d0f..ad9f9df598 100644 --- a/internal/util/indexparamcheck/inverted_checker.go +++ b/internal/util/indexparamcheck/inverted_checker.go @@ -1,8 +1,6 @@ package indexparamcheck import ( - "fmt" - "github.com/samber/lo" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" @@ -51,7 +49,7 @@ func (c *INVERTEDChecker) CheckValidDataType(indexType IndexType, field *schemap dType := field.GetDataType() if !typeutil.IsBoolType(dType) && !typeutil.IsArithmetic(dType) && !typeutil.IsStringType(dType) && !typeutil.IsArrayType(dType) && !typeutil.IsJSONType(dType) { - return fmt.Errorf("INVERTED are not supported on %s field", dType.String()) + return merr.WrapErrParameterInvalidMsg("INVERTED are not supported on %s field", dType.String()) } return nil } diff --git a/internal/util/indexparamcheck/ngram_index_checker.go b/internal/util/indexparamcheck/ngram_index_checker.go index 1e148a3ff9..d6afad6de4 100644 --- a/internal/util/indexparamcheck/ngram_index_checker.go +++ b/internal/util/indexparamcheck/ngram_index_checker.go @@ -1,7 +1,6 @@ package indexparamcheck import ( - "fmt" "strconv" "strings" @@ -33,7 +32,7 @@ func (c *NgramIndexChecker) CheckTrain(dataType schemapb.DataType, elementType s if dataType == schemapb.DataType_JSON { castType, exists := params[common.JSONCastTypeKey] if !exists { - return merr.WrapErrParameterInvalidMsg("JSON field with ngram index must specify json_cast_type") + return merr.WrapErrParameterMissingMsg("JSON field with ngram index must specify json_cast_type") } // Normalize cast type to uppercase for comparison castType = strings.ToUpper(strings.TrimSpace(castType)) @@ -45,7 +44,7 @@ func (c *NgramIndexChecker) CheckTrain(dataType schemapb.DataType, elementType s minGramStr, minGramExist := params[MinGramKey] maxGramStr, maxGramExist := params[MaxGramKey] if !minGramExist || !maxGramExist { - return merr.WrapErrParameterInvalidMsg("Ngram index must specify both min_gram and max_gram") + return merr.WrapErrParameterMissingMsg("Ngram index must specify both min_gram and max_gram") } minGram, err := strconv.Atoi(minGramStr) @@ -68,7 +67,7 @@ func (c *NgramIndexChecker) CheckTrain(dataType schemapb.DataType, elementType s func (c *NgramIndexChecker) CheckValidDataType(indexType IndexType, field *schemapb.FieldSchema) error { dType := field.GetDataType() if !typeutil.IsStringType(dType) && dType != schemapb.DataType_JSON { - return fmt.Errorf("ngram index can only be created on VARCHAR or JSON field") + return merr.WrapErrParameterInvalidMsg("ngram index can only be created on VARCHAR or JSON field") } return nil } diff --git a/internal/util/indexparamcheck/rtree_checker.go b/internal/util/indexparamcheck/rtree_checker.go index 1051618c8b..6ee6c8f021 100644 --- a/internal/util/indexparamcheck/rtree_checker.go +++ b/internal/util/indexparamcheck/rtree_checker.go @@ -17,9 +17,8 @@ package indexparamcheck import ( - "fmt" - "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -30,7 +29,7 @@ type RTREEChecker struct { func (c *RTREEChecker) CheckTrain(dataType schemapb.DataType, elementType schemapb.DataType, params map[string]string) error { if !typeutil.IsGeometryType(dataType) { - return fmt.Errorf("RTREE index can only be built on geometry field") + return merr.WrapErrParameterInvalidMsg("RTREE index can only be built on geometry field") } return c.scalarIndexChecker.CheckTrain(dataType, elementType, params) @@ -39,7 +38,7 @@ func (c *RTREEChecker) CheckTrain(dataType schemapb.DataType, elementType schema func (c *RTREEChecker) CheckValidDataType(indexType IndexType, field *schemapb.FieldSchema) error { dType := field.GetDataType() if !typeutil.IsGeometryType(dType) { - return fmt.Errorf("RTREE index can only be built on geometry field, got %s", dType.String()) + return merr.WrapErrParameterInvalidMsg("RTREE index can only be built on geometry field, got %s", dType.String()) } return nil } diff --git a/internal/util/indexparamcheck/stl_sort_checker.go b/internal/util/indexparamcheck/stl_sort_checker.go index 7abccfb9e5..24b0ecca8d 100644 --- a/internal/util/indexparamcheck/stl_sort_checker.go +++ b/internal/util/indexparamcheck/stl_sort_checker.go @@ -1,9 +1,6 @@ package indexparamcheck import ( - "fmt" - - "github.com/cockroachdb/errors" "github.com/samber/lo" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" @@ -40,7 +37,7 @@ func (c *STLSORTChecker) CheckValidDataType(indexType IndexType, field *schemapb if typeutil.IsArrayType(dataType) && typeutil.IsStructSubField(field.GetName()) { elemType := field.GetElementType() if !typeutil.IsArithmetic(elemType) && !typeutil.IsStringType(elemType) && !typeutil.IsTimestamptzType(elemType) { - return errors.New(fmt.Sprintf("STL_SORT are only supported on numeric, varchar or timestamptz field, got struct sub-field of %s", field.GetElementType())) + return merr.WrapErrParameterInvalidMsg("STL_SORT are only supported on numeric, varchar or timestamptz field, got struct sub-field of %s", field.GetElementType()) } return nil } @@ -48,7 +45,7 @@ func (c *STLSORTChecker) CheckValidDataType(indexType IndexType, field *schemapb return nil } if !typeutil.IsArithmetic(dataType) && !typeutil.IsStringType(dataType) && !typeutil.IsTimestamptzType(dataType) { - return errors.New(fmt.Sprintf("STL_SORT are only supported on numeric, varchar or timestamptz field, got %s", field.GetDataType())) + return merr.WrapErrParameterInvalidMsg("STL_SORT are only supported on numeric, varchar or timestamptz field, got %s", field.GetDataType()) } return nil } diff --git a/internal/util/indexparamcheck/trie_checker.go b/internal/util/indexparamcheck/trie_checker.go index c1a0c5b204..41e0499617 100644 --- a/internal/util/indexparamcheck/trie_checker.go +++ b/internal/util/indexparamcheck/trie_checker.go @@ -1,9 +1,8 @@ package indexparamcheck import ( - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -18,7 +17,7 @@ func (c *TRIEChecker) CheckTrain(dataType schemapb.DataType, elementType schemap func (c *TRIEChecker) CheckValidDataType(indexType IndexType, field *schemapb.FieldSchema) error { if !typeutil.IsStringType(field.GetDataType()) { - return errors.New("TRIE are only supported on varchar field") + return merr.WrapErrParameterInvalidMsg("TRIE are only supported on varchar field") } return nil } diff --git a/internal/util/indexparamcheck/utils.go b/internal/util/indexparamcheck/utils.go index 9d9d7f3ae1..d182963563 100644 --- a/internal/util/indexparamcheck/utils.go +++ b/internal/util/indexparamcheck/utils.go @@ -23,6 +23,7 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/pkg/v3/common" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -68,27 +69,27 @@ func CheckStrByValues(params map[string]string, key string, container []string) func ValidateArrayOfVectorMetricType(elementType schemapb.DataType, metricType string) error { if typeutil.IsDenseFloatVectorType(elementType) { if !funcutil.SliceContain(ArrayOfVectorFloatMetrics, metricType) { - return fmt.Errorf("array of vector with float element type does not support metric type: %s, supported: %v", metricType, ArrayOfVectorFloatMetrics) + return merr.WrapErrParameterInvalidMsg("array of vector with float element type does not support metric type: %s, supported: %v", metricType, ArrayOfVectorFloatMetrics) } return nil } if typeutil.IsBinaryVectorType(elementType) { if !funcutil.SliceContain(ArrayOfVectorBinaryMetrics, metricType) { - return fmt.Errorf("array of vector with binary element type does not support metric type: %s, supported: %v", metricType, ArrayOfVectorBinaryMetrics) + return merr.WrapErrParameterInvalidMsg("array of vector with binary element type does not support metric type: %s, supported: %v", metricType, ArrayOfVectorBinaryMetrics) } return nil } if typeutil.IsIntVectorType(elementType) { if !funcutil.SliceContain(ArrayOfVectorIntMetrics, metricType) { - return fmt.Errorf("array of vector with int element type does not support metric type: %s, supported: %v", metricType, ArrayOfVectorIntMetrics) + return merr.WrapErrParameterInvalidMsg("array of vector with int element type does not support metric type: %s, supported: %v", metricType, ArrayOfVectorIntMetrics) } return nil } - return fmt.Errorf("array of vector index does not support element type: %s", elementType.String()) + return merr.WrapErrParameterInvalidMsg("array of vector index does not support element type: %s", elementType.String()) } func errOutOfRange(x interface{}, lb interface{}, ub interface{}) error { - return fmt.Errorf("%v out of range: [%v, %v]", x, lb, ub) + return merr.WrapErrParameterInvalidMsg("%v out of range: [%v, %v]", x, lb, ub) } func setDefaultIfNotExist(params map[string]string, key string, defaultValue string) { diff --git a/internal/util/indexparamcheck/vector_index_checker.go b/internal/util/indexparamcheck/vector_index_checker.go index daa8557e02..cd473d8848 100644 --- a/internal/util/indexparamcheck/vector_index_checker.go +++ b/internal/util/indexparamcheck/vector_index_checker.go @@ -9,11 +9,9 @@ package indexparamcheck import "C" import ( - "fmt" "math" "unsafe" - "github.com/cockroachdb/errors" "google.golang.org/protobuf/proto" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" @@ -22,6 +20,7 @@ import ( "github.com/milvus-io/milvus/internal/util/vecindexmgr" "github.com/milvus-io/milvus/pkg/v3/common" "github.com/milvus-io/milvus/pkg/v3/proto/indexcgopb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -38,17 +37,27 @@ func HandleCStatus(status *C.CStatus) error { errorMsg := C.GoString(status.error_msg) defer C.free(unsafe.Pointer(status.error_msg)) - return fmt.Errorf("%s", errorMsg) + // The sole caller validates USER-SUPPLIED index params via knowhere, which + // reports bad params as ConfigInvalid (2006). At this boundary that is the + // established ParameterInvalid wire contract (code 1100, message suffix + // "invalid parameter") that SDK/e2e error handling is built on. Any other + // code is an internal C++ failure and classifies via the shared segcore + // table (system blame, original code preserved in the message). + const knowhereConfigInvalid = 2006 + if int32(status.error_code) == knowhereConfigInvalid { + return merr.WrapErrParameterInvalidMsg("%s", errorMsg) + } + return merr.SegcoreError(int32(status.error_code), errorMsg) } func (c vecIndexChecker) StaticCheck(dataType schemapb.DataType, elementType schemapb.DataType, params map[string]string) error { if typeutil.IsDenseFloatVectorType(dataType) { if !CheckStrByValues(params, Metric, FloatVectorMetrics) { - return fmt.Errorf("metric type %s not found or not supported, supported: %v", params[Metric], FloatVectorMetrics) + return merr.WrapErrParameterInvalidMsg("metric type %s not found or not supported, supported: %v", params[Metric], FloatVectorMetrics) } } else if typeutil.IsSparseFloatVectorType(dataType) { if !CheckStrByValues(params, Metric, SparseMetrics) { - return fmt.Errorf("metric type not found or not supported, supported: %v", SparseMetrics) + return merr.WrapErrParameterInvalidMsg("metric type not found or not supported, supported: %v", SparseMetrics) } // Validate inverted_index_algo if provided. This check is done in Go because // the C++ knowhere library no longer validates this parameter (removed in knowhere fd532fb). @@ -61,16 +70,16 @@ func (c vecIndexChecker) StaticCheck(dataType schemapb.DataType, elementType sch } } if !validAlgo { - return fmt.Errorf("sparse inverted index algo %s not found or not supported, supported: %v", algo, SparseInvertedIndexAlgos) + return merr.WrapErrParameterInvalidMsg("sparse inverted index algo %s not found or not supported, supported: %v", algo, SparseInvertedIndexAlgos) } } } else if typeutil.IsBinaryVectorType(dataType) { if !CheckStrByValues(params, Metric, BinaryVectorMetrics) { - return fmt.Errorf("metric type %s not found or not supported, supported: %v", params[Metric], BinaryVectorMetrics) + return merr.WrapErrParameterInvalidMsg("metric type %s not found or not supported, supported: %v", params[Metric], BinaryVectorMetrics) } } else if typeutil.IsIntVectorType(dataType) { if !CheckStrByValues(params, Metric, IntVectorMetrics) { - return fmt.Errorf("metric type %s not found or not supported, supported: %v", params[Metric], IntVectorMetrics) + return merr.WrapErrParameterInvalidMsg("metric type %s not found or not supported, supported: %v", params[Metric], IntVectorMetrics) } } else if typeutil.IsArrayOfVectorType(dataType) { if err := ValidateArrayOfVectorMetricType(elementType, params[Metric]); err != nil { @@ -81,11 +90,11 @@ func (c vecIndexChecker) StaticCheck(dataType schemapb.DataType, elementType sch indexType, exist := params[common.IndexTypeKey] if !exist { - return errors.New("no indexType is specified") + return merr.WrapErrParameterInvalidMsg("no indexType is specified") } if !vecindexmgr.GetVecIndexMgrInstance().IsVecIndex(indexType) { - return fmt.Errorf("indexType %s is not supported", indexType) + return merr.WrapErrParameterInvalidMsg("indexType %s is not supported", indexType) } protoIndexParams := &indexcgopb.IndexParams{ @@ -98,7 +107,7 @@ func (c vecIndexChecker) StaticCheck(dataType schemapb.DataType, elementType sch indexParamsBlob, err := proto.Marshal(protoIndexParams) if err != nil { - return fmt.Errorf("failed to marshal index params: %s", err) + return merr.WrapErrParameterInvalidMsg("failed to marshal index params: %s", err) } var status C.CStatus @@ -119,7 +128,7 @@ func (c vecIndexChecker) CheckTrain(dataType schemapb.DataType, elementType sche if typeutil.IsFixDimVectorType(dataType) || (typeutil.IsArrayOfVectorType(dataType) && typeutil.IsFixDimVectorType(elementType)) { if !CheckIntByRange(params, DIM, 1, math.MaxInt) { - return errors.New("failed to check vector dimension, should be larger than 0 and smaller than math.MaxInt") + return merr.WrapErrParameterInvalidMsg("failed to check vector dimension, should be larger than 0 and smaller than math.MaxInt") } } @@ -128,10 +137,10 @@ func (c vecIndexChecker) CheckTrain(dataType schemapb.DataType, elementType sche func (c vecIndexChecker) CheckValidDataType(indexType IndexType, field *schemapb.FieldSchema) error { if !typeutil.IsVectorType(field.GetDataType()) { - return fmt.Errorf("index %s only supports vector data type", indexType) + return merr.WrapErrParameterInvalidMsg("index %s only supports vector data type", indexType) } if !vecindexmgr.GetVecIndexMgrInstance().IsDataTypeSupport(indexType, field.GetDataType(), field.GetElementType()) { - return fmt.Errorf("index %s do not support data type: %s", indexType, schemapb.DataType_name[int32(field.GetDataType())]) + return merr.WrapErrParameterInvalidMsg("index %s do not support data type: %s", indexType, schemapb.DataType_name[int32(field.GetDataType())]) } return nil } diff --git a/internal/util/initcore/init_core.go b/internal/util/initcore/init_core.go index 69c4b014c5..181da0ac38 100644 --- a/internal/util/initcore/init_core.go +++ b/internal/util/initcore/init_core.go @@ -32,21 +32,19 @@ import "C" import ( "context" "encoding/base64" - "fmt" "strconv" "sync" "time" "unsafe" - "github.com/cockroachdb/errors" "go.uber.org/zap" - "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" _ "github.com/milvus-io/milvus/internal/util/cgo" "github.com/milvus-io/milvus/internal/util/hookutil" "github.com/milvus-io/milvus/internal/util/pathutil" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/util/hardware" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -317,7 +315,7 @@ func ConvertCacheWarmupPolicy(policy string) (C.CacheWarmupPolicy, error) { case "disable": return C.CacheWarmupPolicy_Disable, nil default: - return C.CacheWarmupPolicy_Disable, fmt.Errorf("invalid Tiered Storage cache warmup policy: %s", policy) + return C.CacheWarmupPolicy_Disable, merr.WrapErrParameterInvalidMsg("invalid Tiered Storage cache warmup policy: %s", policy) } } @@ -360,23 +358,23 @@ func InitTieredStorage(params *paramtable.ComponentParam) error { diskMaxRatio := params.QueryNodeCfg.MaxDiskUsagePercentage.GetAsFloat() if memoryLowWatermarkRatio > memoryHighWatermarkRatio { - return errors.New("memoryLowWatermarkRatio should not be greater than memoryHighWatermarkRatio") + return merr.WrapErrParameterInvalidMsg("memoryLowWatermarkRatio should not be greater than memoryHighWatermarkRatio") } if memoryHighWatermarkRatio > memoryMaxRatio { - return errors.New("memoryHighWatermarkRatio should not be greater than memoryMaxRatio") + return merr.WrapErrParameterInvalidMsg("memoryHighWatermarkRatio should not be greater than memoryMaxRatio") } if memoryMaxRatio >= 1 { - return errors.New("memoryMaxRatio should not be greater than 1") + return merr.WrapErrParameterInvalidMsg("memoryMaxRatio should not be greater than 1") } if diskLowWatermarkRatio > diskHighWatermarkRatio { - return errors.New("diskLowWatermarkRatio should not be greater than diskHighWatermarkRatio") + return merr.WrapErrParameterInvalidMsg("diskLowWatermarkRatio should not be greater than diskHighWatermarkRatio") } if diskHighWatermarkRatio > diskMaxRatio { - return errors.New("diskHighWatermarkRatio should not be greater than diskMaxRatio") + return merr.WrapErrParameterInvalidMsg("diskHighWatermarkRatio should not be greater than diskMaxRatio") } if diskMaxRatio >= 1 { - return errors.New("diskMaxRatio should not be greater than 1") + return merr.WrapErrParameterInvalidMsg("diskMaxRatio should not be greater than 1") } memoryLowWatermarkBytes := C.int64_t(memoryLowWatermarkRatio * float64(osMemBytes)) @@ -757,18 +755,17 @@ func HandleCStatus(status *C.CStatus, extraInfo string) error { if status.error_code == 0 { return nil } - errorCode := status.error_code - errorName, ok := commonpb.ErrorCode_name[int32(errorCode)] - if !ok { - errorName = "UnknownError" - } + errorCode := int32(status.error_code) errorMsg := C.GoString(status.error_msg) defer C.free(unsafe.Pointer(status.error_msg)) - finalMsg := fmt.Sprintf("[%s] %s", errorName, errorMsg) - logMsg := fmt.Sprintf("%s, C Runtime Exception: %s\n", extraInfo, finalMsg) - log.Warn(logMsg) - return errors.New(finalMsg) + // SegcoreError classifies the raw C++ code (2000-2099) into the right merr + // sentinel + retriability instead of looking it up in the unrelated + // commonpb.ErrorCode enum and flattening it to ServiceInternal; the caller + // breadcrumb stays in the log. + err := merr.SegcoreError(errorCode, errorMsg) + log.Warn("C runtime exception", zap.Error(err), zap.String("extra", extraInfo)) + return err } // tlsMinVersionForStorage converts minio config's TLS min version value diff --git a/internal/util/mock/grpcclient.go b/internal/util/mock/grpcclient.go index 3a79505962..9e664cc5ec 100644 --- a/internal/util/mock/grpcclient.go +++ b/internal/util/mock/grpcclient.go @@ -19,7 +19,6 @@ package mock import ( "context" "crypto/x509" - "fmt" "sync" "go.uber.org/zap" @@ -30,6 +29,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/tracer" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" "github.com/milvus-io/milvus/pkg/v3/util/generic" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/retry" ) @@ -142,7 +142,7 @@ func (c *GRPCClientBase[T]) ReCall(ctx context.Context, caller func(client T) (a return ret, nil } - traceErr := fmt.Errorf("err: %s\n, %s", err.Error(), tracer.StackTrace()) + traceErr := merr.WrapErrParameterInvalidMsg("err: %s\n, %s", err.Error(), tracer.StackTrace()) log.Warn("GRPCClientBase[T] client grpc first call get error ", zap.Error(traceErr)) if !funcutil.CheckCtxValid(ctx) { @@ -151,7 +151,7 @@ func (c *GRPCClientBase[T]) ReCall(ctx context.Context, caller func(client T) (a ret, err = c.callOnce(ctx, caller) if err != nil { - traceErr = fmt.Errorf("err: %s\n, %s", err.Error(), tracer.StackTrace()) + traceErr = merr.WrapErrParameterInvalidMsg("err: %s\n, %s", err.Error(), tracer.StackTrace()) log.Error("GRPCClientBase[T] client grpc second call get error ", zap.Error(traceErr)) return nil, traceErr } diff --git a/internal/util/pipeline/errors.go b/internal/util/pipeline/errors.go index a98d618d5a..7bfa0d3b00 100644 --- a/internal/util/pipeline/errors.go +++ b/internal/util/pipeline/errors.go @@ -17,8 +17,6 @@ package pipeline import ( - "fmt" - "github.com/cockroachdb/errors" ) @@ -29,5 +27,5 @@ var ( ) func WrapErrRegDispather(err error) error { - return fmt.Errorf("%w :%s", ErrRegisterDispather, err) + return errors.Mark(err, ErrRegisterDispather) } diff --git a/internal/util/proxyutil/proxy_client_manager.go b/internal/util/proxyutil/proxy_client_manager.go index 353236d88d..e3d9091638 100644 --- a/internal/util/proxyutil/proxy_client_manager.go +++ b/internal/util/proxyutil/proxy_client_manager.go @@ -18,7 +18,6 @@ package proxyutil import ( "context" - "fmt" "sync" "github.com/cockroachdb/errors" @@ -216,10 +215,10 @@ func (p *ProxyClientManager) InvalidateCollectionMetaCache(ctx context.Context, return nil } - return fmt.Errorf("InvalidateCollectionMetaCache failed, proxyID = %d, err = %s", k, err) + return merr.Wrapf(err, "InvalidateCollectionMetaCache failed, proxyID = %d", k) } if sta.ErrorCode != commonpb.ErrorCode_Success { - return fmt.Errorf("InvalidateCollectionMetaCache failed, proxyID = %d, err = %s", k, sta.Reason) + return merr.Wrapf(merr.Error(sta), "InvalidateCollectionMetaCache failed, proxyID = %d", k) } return nil }) @@ -241,10 +240,10 @@ func (p *ProxyClientManager) InvalidateCredentialCache(ctx context.Context, requ group.Go(func() error { sta, err := v.InvalidateCredentialCache(ctx, request) if err != nil { - return fmt.Errorf("InvalidateCredentialCache failed, proxyID = %d, err = %s", k, err) + return merr.Wrapf(err, "InvalidateCredentialCache failed, proxyID = %d", k) } if sta.ErrorCode != commonpb.ErrorCode_Success { - return fmt.Errorf("InvalidateCredentialCache failed, proxyID = %d, err = %s", k, sta.Reason) + return merr.Wrapf(merr.Error(sta), "InvalidateCredentialCache failed, proxyID = %d", k) } return nil }) @@ -267,10 +266,10 @@ func (p *ProxyClientManager) UpdateCredentialCache(ctx context.Context, request group.Go(func() error { sta, err := v.UpdateCredentialCache(ctx, request) if err != nil { - return fmt.Errorf("UpdateCredentialCache failed, proxyID = %d, err = %s", k, err) + return merr.Wrapf(err, "UpdateCredentialCache failed, proxyID = %d", k) } if sta.ErrorCode != commonpb.ErrorCode_Success { - return fmt.Errorf("UpdateCredentialCache failed, proxyID = %d, err = %s", k, sta.Reason) + return merr.Wrapf(merr.Error(sta), "UpdateCredentialCache failed, proxyID = %d", k) } return nil }) @@ -292,7 +291,7 @@ func (p *ProxyClientManager) RefreshPolicyInfoCache(ctx context.Context, req *pr group.Go(func() error { status, err := v.RefreshPolicyInfoCache(ctx, req) if err != nil { - return fmt.Errorf("RefreshPolicyInfoCache failed, proxyID = %d, err = %s", k, err) + return merr.Wrapf(err, "RefreshPolicyInfoCache failed, proxyID = %d", k) } if status.GetErrorCode() != commonpb.ErrorCode_Success { return merr.Error(status) @@ -324,10 +323,10 @@ func (p *ProxyClientManager) GetProxyMetrics(ctx context.Context) ([]*milvuspb.G group.Go(func() error { rsp, err := v.GetProxyMetrics(ctx, req) if err != nil { - return fmt.Errorf("GetMetrics failed, proxyID = %d, err = %s", k, err) + return merr.Wrapf(err, "GetMetrics failed, proxyID = %d", k) } if rsp.GetStatus().GetErrorCode() != commonpb.ErrorCode_Success { - return fmt.Errorf("GetMetrics failed, proxyID = %d, err = %s", k, rsp.GetStatus().GetReason()) + return merr.Wrapf(merr.Error(rsp.GetStatus()), "GetMetrics failed, proxyID = %d", k) } metricRspsMu.Lock() metricRsps = append(metricRsps, rsp) @@ -356,10 +355,10 @@ func (p *ProxyClientManager) SetRates(ctx context.Context, request *proxypb.SetR group.Go(func() error { sta, err := v.SetRates(ctx, request) if err != nil { - return fmt.Errorf("SetRates failed, proxyID = %d, err = %s", k, err) + return merr.Wrapf(err, "SetRates failed, proxyID = %d", k) } if sta.GetErrorCode() != commonpb.ErrorCode_Success { - return fmt.Errorf("SetRates failed, proxyID = %d, err = %s", k, sta.Reason) + return merr.Wrapf(merr.Error(sta), "SetRates failed, proxyID = %d", k) } return nil }) @@ -461,10 +460,10 @@ func (p *ProxyClientManager) InvalidateShardLeaderCache(ctx context.Context, req log.Warn("InvalidateShardLeaderCache failed due to proxy service not found", zap.Error(err)) return nil } - return fmt.Errorf("InvalidateShardLeaderCache failed, proxyID = %d, err = %s", k, err) + return merr.Wrapf(err, "InvalidateShardLeaderCache failed, proxyID = %d", k) } if sta.ErrorCode != commonpb.ErrorCode_Success { - return fmt.Errorf("InvalidateShardLeaderCache failed, proxyID = %d, err = %s", k, sta.Reason) + return merr.Wrapf(merr.Error(sta), "InvalidateShardLeaderCache failed, proxyID = %d", k) } return nil }) diff --git a/internal/util/proxyutil/proxy_watcher.go b/internal/util/proxyutil/proxy_watcher.go index c1befdc16a..e67228254f 100644 --- a/internal/util/proxyutil/proxy_watcher.go +++ b/internal/util/proxyutil/proxy_watcher.go @@ -18,7 +18,6 @@ package proxyutil import ( "context" - "fmt" "path" "sync" "time" @@ -33,6 +32,7 @@ import ( "github.com/milvus-io/milvus/internal/util/sessionutil" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/util/lifetime" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -202,7 +202,7 @@ func (p *ProxyWatcher) getSessionsOnEtcd(ctx context.Context) ([]*sessionutil.Se clientv3.WithSort(clientv3.SortByKey, clientv3.SortAscend), ) if err != nil { - return nil, 0, fmt.Errorf("proxy manager failed to watch proxy with error %w", err) + return nil, 0, merr.Wrapf(err, "proxy manager failed to watch proxy") } var sessions []*sessionutil.Session diff --git a/internal/util/queryutil/dedup_pk_op.go b/internal/util/queryutil/dedup_pk_op.go index efaf094c53..3228b2519d 100644 --- a/internal/util/queryutil/dedup_pk_op.go +++ b/internal/util/queryutil/dedup_pk_op.go @@ -18,13 +18,13 @@ package queryutil import ( "context" - "fmt" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/pkg/v3/proto/internalpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -125,7 +125,7 @@ func (op *DeduplicatePKOperator) Run(ctx context.Context, span trace.Span, input } if op.maxOutputSize > 0 && retSize > op.maxOutputSize { - return nil, fmt.Errorf("query results exceed the maxOutputSize Limit %d", op.maxOutputSize) + return nil, merr.WrapErrParameterInvalidMsg("query results exceed the maxOutputSize Limit %d", op.maxOutputSize) } } } @@ -216,7 +216,7 @@ func (op *ConcatAndCheckPKOperator) Run(ctx context.Context, span trace.Span, in for j := int64(0); j < int64(idCount); j++ { pk := typeutil.GetPK(r.GetIds(), j) if _, exists := seenPKs[pk]; exists { - return nil, fmt.Errorf("duplicate PK %v found across shards, possible data integrity issue", pk) + return nil, merr.WrapErrDataIntegrityMsg("duplicate PK %v found across shards", pk) } seenPKs[pk] = struct{}{} selectedRows = append(selectedRows, rowRef{resultIdx: i, rowIdx: j}) diff --git a/internal/util/queryutil/helpers.go b/internal/util/queryutil/helpers.go index e9f23f72e0..f227a1aa4e 100644 --- a/internal/util/queryutil/helpers.go +++ b/internal/util/queryutil/helpers.go @@ -14,15 +14,20 @@ // See the License for the specific language governing permissions and // limitations under the License. +// The merge/slice helpers in this file consume RetrieveResults produced by +// segcore (and merged across query nodes), never raw user input, so every +// data-shape assertion below classifies as ServiceInternal: a violation means +// a segcore/Milvus bug, and must not be attributed to the user (fail_input) +// or suppress cross-replica failover the way an InputError would. + package queryutil import ( - "fmt" - "google.golang.org/protobuf/proto" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/pkg/v3/proto/internalpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -73,7 +78,7 @@ func buildMergedRetrieveResults(results []*internalpb.RetrieveResults, selectedR for _, ref := range selectedRows { refFields := len(results[ref.resultIdx].GetFieldsData()) if refFields != numFields { - return nil, fmt.Errorf( + return nil, merr.WrapErrServiceInternalMsg( "FieldsData count mismatch: result[%d] has %d fields, expected %d", ref.resultIdx, refFields, numFields) } @@ -147,7 +152,7 @@ func validateElementLevelConsistency(results []*internalpb.RetrieveResults, _ [] for i, r := range results { if r.GetElementLevel() != isElementLevel { - return fmt.Errorf( + return merr.WrapErrServiceInternalMsg( "inconsistent element-level flag: result[%d] has ElementLevel=%v, expected %v", i, r.GetElementLevel(), isElementLevel) } @@ -155,7 +160,7 @@ func validateElementLevelConsistency(results []*internalpb.RetrieveResults, _ [] idsLen := typeutil.GetSizeOfIDs(r.GetIds()) indicesLen := len(r.GetElementIndices()) if indicesLen != idsLen { - return fmt.Errorf( + return merr.WrapErrServiceInternalMsg( "element_indices length (%d) does not match ids length (%d) in result[%d]", indicesLen, idsLen, i) } @@ -428,14 +433,14 @@ func buildCompactIndices(results []*internalpb.RetrieveResults, fieldIdx int, is // but that path is unreachable for merge inputs (HasRawData gate + empty IDs filtering). // Therefore empty ValidData here is always a segcore bug, not a legitimate state. if len(vd) == 0 { - return nil, fmt.Errorf( + return nil, merr.WrapErrServiceInternalMsg( "buildCompactIndices: nullable vector field fid=%d name=%q has empty ValidData but numRows=%d in result[%d]; "+ "segcore must always provide ValidData for nullable fields with rows", fd.GetFieldId(), fd.GetFieldName(), numRows, ri) } if len(vd) != numRows { - return nil, fmt.Errorf( + return nil, merr.WrapErrServiceInternalMsg( "buildCompactIndices: nullable vector field fid=%d name=%q has len(ValidData)=%d but numRows=%d in result[%d]; "+ "segcore violated the nullable contract (len(ValidData) must equal numRows)", fd.GetFieldId(), fd.GetFieldName(), len(vd), numRows, ri) @@ -449,7 +454,7 @@ func buildCompactIndices(results []*internalpb.RetrieveResults, fieldIdx int, is func arrayOfVectorRowValid(fd *schemapb.FieldData, rowIdx int64, isNullable bool, numRows int, resultIdx int) (bool, error) { if rowIdx < 0 { - return false, fmt.Errorf( + return false, merr.WrapErrServiceInternalMsg( "arrayOfVectorRowValid: field fid=%d name=%q in result[%d] has invalid rowIdx=%d", fd.GetFieldId(), fd.GetFieldName(), resultIdx, rowIdx) } @@ -457,7 +462,7 @@ func arrayOfVectorRowValid(fd *schemapb.FieldData, rowIdx int64, isNullable bool validData := fd.GetValidData() if len(validData) == 0 { if isNullable { - return false, fmt.Errorf( + return false, merr.WrapErrServiceInternalMsg( "arrayOfVectorRowValid: nullable ArrayOfVector field fid=%d name=%q has empty ValidData but numRows=%d in result[%d]; "+ "segcore must always provide ValidData for nullable fields with rows", fd.GetFieldId(), fd.GetFieldName(), numRows, resultIdx) @@ -466,12 +471,12 @@ func arrayOfVectorRowValid(fd *schemapb.FieldData, rowIdx int64, isNullable bool } if int(rowIdx) >= len(validData) { - return false, fmt.Errorf( + return false, merr.WrapErrServiceInternalMsg( "arrayOfVectorRowValid: ArrayOfVector field fid=%d name=%q in result[%d] has rowIdx=%d outside ValidData bounds len(ValidData)=%d", fd.GetFieldId(), fd.GetFieldName(), resultIdx, rowIdx, len(validData)) } if isNullable && numRows > 0 && len(validData) != numRows { - return false, fmt.Errorf( + return false, merr.WrapErrServiceInternalMsg( "arrayOfVectorRowValid: nullable ArrayOfVector field fid=%d name=%q has len(ValidData)=%d but numRows=%d in result[%d]; "+ "segcore violated the nullable contract (len(ValidData) must equal numRows)", fd.GetFieldId(), fd.GetFieldName(), len(validData), numRows, resultIdx) @@ -576,7 +581,7 @@ func buildMergedVectorField(results []*internalpb.RetrieveResults, selectedRows // This indicates segcore returned truncated/malformed vector data. vecDataOOB := func(ref rowRef, di int, dataLen int) error { fd := results[ref.resultIdx].GetFieldsData()[fieldIdx] - return fmt.Errorf( + return merr.WrapErrServiceInternalMsg( "buildMergedVectorField: vector data too short for %s field fid=%d name=%q in result[%d]: "+ "dataIdx=%d requires offset beyond data length %d (dim=%d, numRows=%d); segcore returned truncated data", dataType, fd.GetFieldId(), fd.GetFieldName(), ref.resultIdx, @@ -679,7 +684,7 @@ func buildMergedVectorField(results []*internalpb.RetrieveResults, selectedRows sparse := results[ref.resultIdx].GetFieldsData()[fieldIdx].GetVectors().GetSparseFloatVector() if sparse == nil || di >= len(sparse.GetContents()) { fd := results[ref.resultIdx].GetFieldsData()[fieldIdx] - return nil, fmt.Errorf( + return nil, merr.WrapErrServiceInternalMsg( "buildMergedVectorField: sparse vector data missing for field fid=%d name=%q in result[%d]: "+ "dataIdx=%d but SparseFloatArray is nil or has only %d contents (numRows=%d); segcore returned truncated data", fd.GetFieldId(), fd.GetFieldName(), ref.resultIdx, @@ -740,14 +745,14 @@ func buildMergedVectorField(results []*internalpb.RetrieveResults, selectedRows } va := fd.GetVectors().GetVectorArray() if va == nil || len(va.GetData()) == 0 { - return nil, fmt.Errorf( + return nil, merr.WrapErrServiceInternalMsg( "buildMergedVectorField: VectorArray data missing for field fid=%d name=%q in result[%d]: "+ "dataIdx=%d but VectorArray is nil or has no entries (numRows=%d); segcore returned truncated data", fd.GetFieldId(), fd.GetFieldName(), ref.resultIdx, di, typeutil.GetSizeOfIDs(results[ref.resultIdx].GetIds())) } if di >= len(va.GetData()) { - return nil, fmt.Errorf( + return nil, merr.WrapErrServiceInternalMsg( "buildMergedVectorField: VectorArray data missing for field fid=%d name=%q in result[%d]: "+ "dataIdx=%d but VectorArray is nil or has only %d entries (numRows=%d); segcore returned truncated data", fd.GetFieldId(), fd.GetFieldName(), ref.resultIdx, diff --git a/internal/util/queryutil/order_op.go b/internal/util/queryutil/order_op.go index dfca4991b9..b825742778 100644 --- a/internal/util/queryutil/order_op.go +++ b/internal/util/queryutil/order_op.go @@ -19,7 +19,6 @@ package queryutil import ( "container/heap" "context" - "fmt" "sort" "go.opentelemetry.io/otel" @@ -28,6 +27,7 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/util/reduce/orderby" "github.com/milvus-io/milvus/pkg/v3/proto/internalpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -96,7 +96,7 @@ func (op *OrderByLimitOperator) Run(ctx context.Context, span trace.Span, inputs } } if op.fieldPositions[i] < 0 { - return nil, fmt.Errorf("ORDER BY field '%s' (ID=%d) not found in result FieldsData", f.FieldName, f.FieldID) + return nil, merr.WrapErrParameterInvalidMsg("ORDER BY field '%s' (ID=%d) not found in result FieldsData", f.FieldName, f.FieldID) } } } diff --git a/internal/util/queryutil/pipeline.go b/internal/util/queryutil/pipeline.go index 8e920ccc47..1bca1205bf 100644 --- a/internal/util/queryutil/pipeline.go +++ b/internal/util/queryutil/pipeline.go @@ -22,6 +22,8 @@ import ( "fmt" "go.opentelemetry.io/otel/trace" + + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // Well-known pipeline channel names @@ -71,7 +73,7 @@ func (n *Node) unpackInputs(msg OpMsg) ([]any, error) { for i, input := range n.inputs { val, ok := msg[input] if !ok { - return nil, fmt.Errorf("node [%s]: input channel '%s' not found", n.name, input) + return nil, merr.WrapErrParameterInvalidMsg("node [%s]: input channel '%s' not found", n.name, input) } inputs[i] = val } @@ -81,7 +83,7 @@ func (n *Node) unpackInputs(msg OpMsg) ([]any, error) { // packOutputs stores output values into the message by channel names. func (n *Node) packOutputs(outputs []any, msg OpMsg) error { if len(outputs) != len(n.outputs) { - return fmt.Errorf("node [%s]: output count mismatch, expected %d, got %d", + return merr.WrapErrParameterInvalidMsg("node [%s]: output count mismatch, expected %d, got %d", n.name, len(n.outputs), len(outputs)) } for i, output := range n.outputs { @@ -99,7 +101,7 @@ func (n *Node) Run(ctx context.Context, span trace.Span, msg OpMsg) error { outputs, err := n.op.Run(ctx, span, inputs...) if err != nil { - return fmt.Errorf("node [%s] operator failed: %w", n.name, err) + return merr.Wrapf(err, "node [%s] operator failed", n.name) } return n.packOutputs(outputs, msg) @@ -144,7 +146,7 @@ func (p *Pipeline) Run(ctx context.Context, span trace.Span, initialMsg OpMsg) ( for _, node := range p.nodes { if err := node.Run(ctx, span, msg); err != nil { - return nil, fmt.Errorf("pipeline [%s]: %w", p.name, err) + return nil, merr.Wrapf(err, "pipeline [%s]", p.name) } } diff --git a/internal/util/queryutil/pipeline_builders.go b/internal/util/queryutil/pipeline_builders.go index 53c5a3927e..a79becbc00 100644 --- a/internal/util/queryutil/pipeline_builders.go +++ b/internal/util/queryutil/pipeline_builders.go @@ -17,12 +17,11 @@ package queryutil import ( - "fmt" - "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/util/reduce" "github.com/milvus-io/milvus/internal/util/reduce/orderby" "github.com/milvus-io/milvus/pkg/v3/proto/planpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // BuildQueryReducePipeline builds a pipeline for QN/Delegator query reduction. @@ -129,7 +128,7 @@ func ComputeGroupByOrderPositions( } } if !found { - return nil, fmt.Errorf("ORDER BY field '%s' (ID=%d) not found in GROUP BY columns or aggregates", obf.FieldName, obf.FieldID) + return nil, merr.WrapErrParameterInvalidMsg("ORDER BY field '%s' (ID=%d) not found in GROUP BY columns or aggregates", obf.FieldName, obf.FieldID) } } return positions, nil diff --git a/internal/util/queryutil/reduce_by_pk_op.go b/internal/util/queryutil/reduce_by_pk_op.go index b61a27db38..b270163b6f 100644 --- a/internal/util/queryutil/reduce_by_pk_op.go +++ b/internal/util/queryutil/reduce_by_pk_op.go @@ -18,7 +18,6 @@ package queryutil import ( "context" - "fmt" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" @@ -26,6 +25,7 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/util/reduce" "github.com/milvus-io/milvus/pkg/v3/proto/internalpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -123,7 +123,7 @@ func (op *ReduceByPKOperator) mergeByPK(results []*internalpb.RetrieveResults, h seenPKs[pk] = struct{}{} selectedRows = append(selectedRows, rowRef{resultIdx: sel, rowIdx: cursors[sel]}) } else { - return nil, fmt.Errorf("duplicate PK %v found across shards, possible data integrity issue", pk) + return nil, merr.WrapErrDataIntegrityMsg("duplicate PK %v found across shards", pk) } cursors[sel]++ } @@ -294,7 +294,7 @@ func (op *ReduceByPKWithTimestampOperator) mergeByPKWithTimestamp(results []*tim } if op.maxOutputSize > 0 && retSize > op.maxOutputSize { - return nil, fmt.Errorf("query results exceed the maxOutputSize Limit %d", op.maxOutputSize) + return nil, merr.WrapErrParameterInvalidMsg("query results exceed the maxOutputSize Limit %d", op.maxOutputSize) } // Early termination when limit reached diff --git a/internal/util/queryutil/remap_op.go b/internal/util/queryutil/remap_op.go index e683b2a6f2..6c365a55b9 100644 --- a/internal/util/queryutil/remap_op.go +++ b/internal/util/queryutil/remap_op.go @@ -18,12 +18,12 @@ package queryutil import ( "context" - "fmt" "go.opentelemetry.io/otel/trace" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/pkg/v3/proto/internalpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // RemapOperator reorders FieldsData by pre-computed positional indices. @@ -58,7 +58,7 @@ func (op *RemapOperator) Run(_ context.Context, _ trace.Span, inputs ...any) ([] newFieldsData := make([]*schemapb.FieldData, 0, len(op.outputIndices)) for _, pos := range op.outputIndices { if pos < 0 || pos >= len(fieldsData) { - return nil, fmt.Errorf("RemapOperator: output index %d out of range [0, %d)", pos, len(fieldsData)) + return nil, merr.WrapErrParameterInvalidMsg("RemapOperator: output index %d out of range [0, %d)", pos, len(fieldsData)) } newFieldsData = append(newFieldsData, fieldsData[pos]) } diff --git a/internal/util/reduce/field_data.go b/internal/util/reduce/field_data.go index e558c6ad02..589a093ba0 100644 --- a/internal/util/reduce/field_data.go +++ b/internal/util/reduce/field_data.go @@ -1,9 +1,8 @@ package reduce import ( - "fmt" - "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -39,7 +38,7 @@ func ValidateGroupByFieldsPresent(searchResultData []*schemapb.SearchResultData, } for _, fieldID := range fieldIDs { if FindGroupByFieldData(data, fieldID, allowSingularFallback) == nil { - return fmt.Errorf("group-by field %d missing from search result %d", fieldID, resultIdx) + return merr.WrapErrParameterInvalidMsg("group-by field %d missing from search result %d", fieldID, resultIdx) } } } @@ -85,7 +84,7 @@ func WriteGroupByFieldValues( } } if template == nil { - return fmt.Errorf("group-by field %d missing from all source shards", fid) + return merr.WrapErrParameterInvalidMsg("group-by field %d missing from all source shards", fid) } builder, err := typeutil.NewFieldDataBuilder(template.GetType(), true, len(acceptedRows)) @@ -95,7 +94,7 @@ func WriteGroupByFieldValues( for _, row := range acceptedRows { iter := iters[row.ResultIdx] if iter == nil { - return fmt.Errorf("group-by field %d missing at source shard index %d", fid, row.ResultIdx) + return merr.WrapErrParameterInvalidMsg("group-by field %d missing at source shard index %d", fid, row.ResultIdx) } builder.Add(iter(int(row.RowIdx))) } diff --git a/internal/util/reduce/orderby/types.go b/internal/util/reduce/orderby/types.go index 7af3e6dd05..0c9da4aa7a 100644 --- a/internal/util/reduce/orderby/types.go +++ b/internal/util/reduce/orderby/types.go @@ -22,6 +22,7 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/pkg/v3/proto/planpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // OrderByField defines a single sort key for ORDER BY operations. @@ -134,7 +135,7 @@ func ParseOrderByFields(orderByStrs []string, schema *schemapb.CollectionSchema) case "asc", "ascending", "": ascending = true default: - return nil, fmt.Errorf("invalid order direction '%s' for field '%s', must be 'asc' or 'desc'", dir, fieldName) + return nil, merr.WrapErrParameterInvalidMsg("invalid order direction '%s' for field '%s', must be 'asc' or 'desc'", dir, fieldName) } } @@ -150,7 +151,7 @@ func ParseOrderByFields(orderByStrs []string, schema *schemapb.CollectionSchema) case "nulls_last": nullsFirst = false default: - return nil, fmt.Errorf("invalid null ordering '%s', must be 'nulls_first' or 'nulls_last'", nullOpt) + return nil, merr.WrapErrParameterInvalidMsg("invalid null ordering '%s', must be 'nulls_first' or 'nulls_last'", nullOpt) } } @@ -159,12 +160,12 @@ func ParseOrderByFields(orderByStrs []string, schema *schemapb.CollectionSchema) // so ORDER BY on dynamic fields is not supported in the current version. fieldSchema, found := fieldNameToSchema[fieldName] if !found { - return nil, fmt.Errorf("order_by field '%s' does not exist in collection schema", fieldName) + return nil, merr.WrapErrParameterInvalidMsg("order_by field '%s' does not exist in collection schema", fieldName) } // Validate field type is sortable if !IsSortableType(fieldSchema.DataType) { - return nil, fmt.Errorf("order_by field '%s' has type %s which is not sortable", + return nil, merr.WrapErrParameterInvalidMsg("order_by field '%s' has type %s which is not sortable", fieldName, fieldSchema.DataType.String()) } @@ -196,7 +197,7 @@ func ConvertFromPlanOrderByFields(planFields []*planpb.OrderByField, schema *sch for i, pf := range planFields { fs, ok := fieldIDToSchema[pf.GetFieldId()] if !ok { - return nil, fmt.Errorf("ORDER BY field with ID %d not found in schema", pf.GetFieldId()) + return nil, merr.WrapErrParameterInvalidMsg("ORDER BY field with ID %d not found in schema", pf.GetFieldId()) } result[i] = &OrderByField{ FieldID: pf.GetFieldId(), diff --git a/internal/util/segcore/collection.go b/internal/util/segcore/collection.go index 732b2b36bf..38de0d5585 100644 --- a/internal/util/segcore/collection.go +++ b/internal/util/segcore/collection.go @@ -12,7 +12,6 @@ import "C" import ( "unsafe" - "github.com/cockroachdb/errors" "go.uber.org/zap" "google.golang.org/protobuf/proto" @@ -35,13 +34,13 @@ type CreateCCollectionRequest struct { func CreateCCollection(req *CreateCCollectionRequest) (*CCollection, error) { schemaBlob, err := proto.Marshal(req.Schema) if err != nil { - return nil, errors.New("marshal schema failed") + return nil, merr.WrapErrSegcoreMsg("marshal schema failed") } var indexMetaBlob []byte if req.IndexMeta != nil { indexMetaBlob, err = proto.Marshal(req.IndexMeta) if err != nil { - return nil, errors.New("marshal index meta failed") + return nil, merr.WrapErrSegcoreMsg("marshal index meta failed") } } var ptr C.CCollection diff --git a/internal/util/segcore/plan.go b/internal/util/segcore/plan.go index 19aefb2fea..bbf2b41fe0 100644 --- a/internal/util/segcore/plan.go +++ b/internal/util/segcore/plan.go @@ -29,8 +29,6 @@ import "C" import ( "unsafe" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus/pkg/v3/proto/querypb" "github.com/milvus-io/milvus/pkg/v3/util/merr" @@ -49,12 +47,12 @@ func deletePlaceholderGroup(group unsafe.Pointer) { func createSearchPlanByExpr(col *CCollection, expr []byte) (*SearchPlan, error) { if len(expr) == 0 { - return nil, errors.New("empty expression plan") + return nil, merr.WrapErrParameterInvalidMsg("empty expression plan") } var cPlan C.CSearchPlan status := C.CreateSearchPlanByExpr(col.rawPointer(), unsafe.Pointer(&expr[0]), (C.int64_t)(len(expr)), &cPlan) if err := ConsumeCStatusIntoError(&status); err != nil { - return nil, errors.Wrap(err, "Create Plan by expr failed") + return nil, merr.Wrap(err, "Create Plan by expr failed") } return &SearchPlan{cSearchPlan: cPlan}, nil } @@ -104,7 +102,7 @@ func NewSearchRequest(collection *CCollection, req *querypb.SearchRequest, place if len(placeholderGrp) == 0 { plan.delete() - return nil, errors.New("empty search request") + return nil, merr.WrapErrParameterInvalidMsg("empty search request") } metricTypeInPlan := plan.GetMetricType() @@ -117,7 +115,7 @@ func NewSearchRequest(collection *CCollection, req *querypb.SearchRequest, place status := C.GetFieldID(plan.cSearchPlan, &fieldID) if err := ConsumeCStatusIntoError(&status); err != nil { plan.delete() - return nil, errors.Wrap(err, "get fieldID from plan failed") + return nil, merr.Wrap(err, "get fieldID from plan failed") } blobPtr := unsafe.Pointer(&placeholderGrp[0]) @@ -126,7 +124,7 @@ func NewSearchRequest(collection *CCollection, req *querypb.SearchRequest, place status = C.ParsePlaceholderGroup(plan.cSearchPlan, blobPtr, blobSize, &cPlaceholderGroup) if err := ConsumeCStatusIntoError(&status); err != nil { plan.delete() - return nil, errors.Wrap(err, "parser searchRequest failed") + return nil, merr.Wrap(err, "parser searchRequest failed") } cl := req.GetReq().GetConsistencyLevel() @@ -202,12 +200,12 @@ func NewRetrievePlan(col *CCollection, entityTTLPhysicalTime typeutil.Timestamp, ) (*RetrievePlan, error) { if col.rawPointer() == nil { - return nil, errors.New("collection is released") + return nil, merr.WrapErrServiceInternalMsg("collection is released") } var cPlan C.CRetrievePlan status := C.CreateRetrievePlanByExpr(col.rawPointer(), unsafe.Pointer(&expr[0]), (C.int64_t)(len(expr)), &cPlan) if err := ConsumeCStatusIntoError(&status); err != nil { - return nil, errors.Wrap(err, "Create retrieve plan by expr failed") + return nil, merr.Wrap(err, "Create retrieve plan by expr failed") } maxLimitSize := paramtable.Get().QuotaConfig.MaxOutputSize.GetAsInt64() return &RetrievePlan{ diff --git a/internal/util/segcore/requests.go b/internal/util/segcore/requests.go index 09d977a002..c781bde7a4 100644 --- a/internal/util/segcore/requests.go +++ b/internal/util/segcore/requests.go @@ -12,8 +12,6 @@ import "C" import ( "unsafe" - "github.com/cockroachdb/errors" - "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/internal/storage" @@ -21,6 +19,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/proto/datapb" "github.com/milvus-io/milvus/pkg/v3/proto/querypb" "github.com/milvus-io/milvus/pkg/v3/proto/segcorepb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -58,7 +57,7 @@ func (req *LoadFieldDataRequest) getCLoadFieldDataRequest() (result *cLoadFieldD var cLoadFieldDataInfo C.CLoadFieldDataInfo status := C.NewLoadFieldDataInfo(&cLoadFieldDataInfo, C.int64_t(req.StorageVersion)) if err := ConsumeCStatusIntoError(&status); err != nil { - return nil, errors.Wrap(err, "NewLoadFieldDataInfo failed") + return nil, merr.Wrap(err, "NewLoadFieldDataInfo failed") } defer func() { if err != nil { @@ -71,7 +70,7 @@ func (req *LoadFieldDataRequest) getCLoadFieldDataRequest() (result *cLoadFieldD cFieldID := C.int64_t(field.Field.GetFieldID()) status = C.AppendLoadFieldInfo(cLoadFieldDataInfo, cFieldID, rowCount) if err := ConsumeCStatusIntoError(&status); err != nil { - return nil, errors.Wrapf(err, "AppendLoadFieldInfo failed at fieldID, %d", field.Field.GetFieldID()) + return nil, merr.Wrapf(err, "AppendLoadFieldInfo failed at fieldID, %d", field.Field.GetFieldID()) } for _, binlog := range field.Field.Binlogs { cEntriesNum := C.int64_t(binlog.GetEntriesNum()) @@ -81,7 +80,7 @@ func (req *LoadFieldDataRequest) getCLoadFieldDataRequest() (result *cLoadFieldD status = C.AppendLoadFieldDataPath(cLoadFieldDataInfo, cFieldID, cEntriesNum, cMemorySize, cFile) if err := ConsumeCStatusIntoError(&status); err != nil { - return nil, errors.Wrapf(err, "AppendLoadFieldDataPath failed at binlog, %d, %s", field.Field.GetFieldID(), binlog.GetLogPath()) + return nil, merr.Wrapf(err, "AppendLoadFieldDataPath failed at binlog, %d, %s", field.Field.GetFieldID(), binlog.GetLogPath()) } } @@ -89,7 +88,7 @@ func (req *LoadFieldDataRequest) getCLoadFieldDataRequest() (result *cLoadFieldD if len(childField) > 0 { status = C.SetLoadFieldInfoChildFields(cLoadFieldDataInfo, cFieldID, (*C.int64_t)(unsafe.Pointer(&childField[0])), C.int64_t(len(childField))) if err := ConsumeCStatusIntoError(&status); err != nil { - return nil, errors.Wrapf(err, "SetLoadFieldInfoChildFields failed at binlog, %d", field.Field.GetFieldID()) + return nil, merr.Wrapf(err, "SetLoadFieldInfoChildFields failed at binlog, %d", field.Field.GetFieldID()) } } @@ -99,7 +98,7 @@ func (req *LoadFieldDataRequest) getCLoadFieldDataRequest() (result *cLoadFieldD if len(field.WarmupPolicy) > 0 { fieldWarmupPolicy, err := initcore.ConvertCacheWarmupPolicy(field.WarmupPolicy) if err != nil { - return nil, errors.Wrapf(err, "ConvertCacheWarmupPolicy failed at field %d", field.Field.GetFieldID()) + return nil, merr.Wrapf(err, "ConvertCacheWarmupPolicy failed at field %d", field.Field.GetFieldID()) } C.SetFieldWarmupPolicy(cLoadFieldDataInfo, cFieldID, C.CacheWarmupPolicy(fieldWarmupPolicy)) } diff --git a/internal/util/segcore/segment.go b/internal/util/segcore/segment.go index 6e55f90ab8..b6cd2e1e7a 100644 --- a/internal/util/segcore/segment.go +++ b/internal/util/segcore/segment.go @@ -18,7 +18,6 @@ import ( "strings" "unsafe" - "github.com/cockroachdb/errors" "go.uber.org/zap" "google.golang.org/protobuf/proto" @@ -91,7 +90,7 @@ func CreateCSegment(req *CreateCSegmentRequest) (CSegment, error) { if commitTs := req.LoadInfo.GetCommitTimestamp(); commitTs != 0 { if err := seg.SetCommitTimestamp(commitTs); err != nil { C.DeleteSegment(ptr) - return nil, errors.Wrap(err, "failed to set commit timestamp on segment") + return nil, merr.Wrap(err, "failed to set commit timestamp on segment") } } } @@ -257,7 +256,7 @@ func (s *cSegmentImpl) Insert(ctx context.Context, request *InsertRequest) (*Ins insertRecordBlob, err := proto.Marshal(request.Record) if err != nil { - return nil, fmt.Errorf("failed to marshal insert record: %s", err) + return nil, merr.Wrap(err, "failed to marshal insert record") } numOfRow := len(request.RowIDs) @@ -299,7 +298,7 @@ func (s *cSegmentImpl) Delete(ctx context.Context, request *DeleteRequest) (*Del dataBlob, err := proto.Marshal(ids) if err != nil { - return nil, fmt.Errorf("failed to marshal ids: %s", err) + return nil, merr.Wrap(err, "failed to marshal ids") } status := C.Delete(s.ptr, cSize, @@ -320,7 +319,7 @@ func (s *cSegmentImpl) LoadFieldData(ctx context.Context, request *LoadFieldData status := C.LoadFieldData(s.ptr, creq.cLoadFieldDataInfo) if err := ConsumeCStatusIntoError(&status); err != nil { - return nil, errors.Wrap(err, "failed to load field data") + return nil, merr.Wrap(err, "failed to load field data") } return &LoadFieldDataResult{}, nil } @@ -345,13 +344,13 @@ func (s *cSegmentImpl) Load(ctx context.Context) error { func (s *cSegmentImpl) Reopen(ctx context.Context, req *ReopenRequest) error { if req == nil { - return errors.New("reopen request is nil") + return merr.WrapErrParameterInvalidMsg("reopen request is nil") } if req.LoadInfo == nil { - return errors.New("reopen load info is nil") + return merr.WrapErrParameterInvalidMsg("reopen load info is nil") } if req.Schema == nil { - return errors.New("reopen schema is nil") + return merr.WrapErrParameterInvalidMsg("reopen schema is nil") } traceCtx := ParseCTraceContext(ctx) @@ -364,7 +363,7 @@ func (s *cSegmentImpl) Reopen(ctx context.Context, req *ReopenRequest) error { return err } if len(loadInfoBlob) == 0 { - return errors.New("reopen load info blob is empty") + return merr.WrapErrServiceInternalMsg("reopen load info blob is empty") } schemaBlob, err := proto.Marshal(req.Schema) @@ -372,7 +371,7 @@ func (s *cSegmentImpl) Reopen(ctx context.Context, req *ReopenRequest) error { return err } if len(schemaBlob) == 0 { - return errors.New("reopen schema blob is empty") + return merr.WrapErrServiceInternalMsg("reopen schema blob is empty") } defer runtime.KeepAlive(schemaBlob) @@ -398,7 +397,7 @@ func (s *cSegmentImpl) Reopen(ctx context.Context, req *ReopenRequest) error { func (s *cSegmentImpl) DropIndex(ctx context.Context, fieldID int64) error { status := C.DropSealedSegmentIndex(s.ptr, C.int64_t(fieldID)) if err := ConsumeCStatusIntoError(&status); err != nil { - return errors.Wrap(err, "failed to drop index") + return merr.Wrap(err, "failed to drop index") } return nil } @@ -406,7 +405,7 @@ func (s *cSegmentImpl) DropIndex(ctx context.Context, fieldID int64) error { func (s *cSegmentImpl) DropJSONIndex(ctx context.Context, fieldID int64, nestedPath string) error { status := C.DropSealedSegmentJSONIndex(s.ptr, C.int64_t(fieldID), C.CString(nestedPath)) if err := ConsumeCStatusIntoError(&status); err != nil { - return errors.Wrap(err, "failed to drop json index") + return merr.Wrap(err, "failed to drop json index") } return nil } diff --git a/internal/util/sessionutil/session_util.go b/internal/util/sessionutil/session_util.go index b17bf44185..2f165ae94e 100644 --- a/internal/util/sessionutil/session_util.go +++ b/internal/util/sessionutil/session_util.go @@ -63,6 +63,12 @@ const ( var errSessionVersionCheckFailure = errors.New("session version check failure") +// errSessionExpiredAtClientSide is a keepalive deadline cause compared by +// identity via errors.Is(context.Cause(ctx), ...). It must stay a bare +// package-level sentinel rather than a merr error: merr's errors.Is walks the +// wrappedMilvusError/code chain and would not preserve this identity check. +var errSessionExpiredAtClientSide = errors.New("session expired at client side") + // isNotSessionVersionCheckFailure checks if the error is not a session version check failure. func isNotSessionVersionCheckFailure(err error) bool { return !errors.Is(err, errSessionVersionCheckFailure) @@ -520,7 +526,7 @@ func (s *Session) registerService() error { return err } if txnResp != nil && !txnResp.Succeeded { - return fmt.Errorf("function CompareAndSwap error for compare is false for key: %s", s.ServerName) + return merr.WrapErrServiceUnavailableMsg("CompareAndSwap failed for session key %s: compare is false", s.ServerName) } if !s.enableActiveStandBy { s.registeredRevision.Store(txnResp.Header.GetRevision()) @@ -614,7 +620,7 @@ func (s *Session) processKeepAliveResponse() { newCH, err := s.etcdCli.KeepAlive(s.ctx, *s.LeaseID) if err != nil { s.Logger().Error("failed to keep alive with etcd", zap.Error(err)) - lastErr = errors.Wrap(err, "failed to keep alive") + lastErr = merr.Wrap(err, "failed to keep alive") continue } s.Logger().Info("keep alive...", zap.Int64("leaseID", int64(*s.LeaseID))) @@ -636,7 +642,6 @@ func (s *Session) processKeepAliveResponse() { // checkKeepaliveTTL checks the TTL of the lease and returns the error if the lease is not found or expired. func (s *Session) checkKeepaliveTTL(nextKeepaliveInstant time.Time) error { - errSessionExpiredAtClientSide := errors.New("session expired at client side") ctx, cancel := context.WithDeadlineCause(s.ctx, nextKeepaliveInstant, errSessionExpiredAtClientSide) defer cancel() @@ -652,7 +657,7 @@ func (s *Session) checkKeepaliveTTL(nextKeepaliveInstant time.Time) error { log.Cleanup() os.Exit(ExitCodeEtcd) } - return errors.Wrap(err, "failed to check TTL") + return merr.Wrap(err, "failed to check TTL") } if ttlResp.TTL <= 0 { s.Logger().Error("confirm the lease is expired, the session is expired without activing closing", zap.Error(err)) @@ -726,11 +731,11 @@ func (s *Session) GetSessionsWithVersionRange(prefix string, r semver.Range) (ma func (s *Session) GoingStop() error { if s == nil || s.etcdCli == nil || s.LeaseID == nil { - return errors.New("the session hasn't been init") + return merr.WrapErrServiceInternalMsg("the session hasn't been init") } if s.Disconnected() { - return errors.New("this session has disconnected") + return merr.WrapErrServiceUnavailable("this session has disconnected") } completeKey := s.getCompleteKey() diff --git a/internal/util/streamingutil/service/balancer/balancer.go b/internal/util/streamingutil/service/balancer/balancer.go index 57fbe73c3f..76c9168f93 100644 --- a/internal/util/streamingutil/service/balancer/balancer.go +++ b/internal/util/streamingutil/service/balancer/balancer.go @@ -23,8 +23,6 @@ package balancer import ( - "fmt" - "github.com/cockroachdb/errors" "google.golang.org/grpc/balancer" "google.golang.org/grpc/balancer/base" @@ -38,6 +36,11 @@ var ( _ balancer.Builder = (*baseBuilder)(nil) ) +// errProducedZeroAddresses is reported to the grpc ClientConn (via ResolverError) +// to trigger a re-resolve when the resolver produces no addresses. It stays a +// grpc-framework-facing error, deliberately not a milvus merr. +var errProducedZeroAddresses = errors.New("produced zero addresses") + type baseBuilder struct { name string pickerBuilder PickerBuilder @@ -136,7 +139,7 @@ func (b *baseBalancer) UpdateClientConnState(s balancer.ClientConnState) error { // the overall state turns transient failure, the error message will have // the zero address information. if len(s.ResolverState.Addresses) == 0 { - b.ResolverError(errors.New("produced zero addresses")) + b.ResolverError(errProducedZeroAddresses) return balancer.ErrBadResolverState } @@ -151,12 +154,12 @@ func (b *baseBalancer) mergeErrors() error { // connErr must always be non-nil unless there are no SubConns, in which // case resolverErr must be non-nil. if b.connErr == nil { - return fmt.Errorf("last resolver error: %v", b.resolverErr) + return errors.Wrap(b.resolverErr, "last resolver error") } if b.resolverErr == nil { - return fmt.Errorf("last connection error: %v", b.connErr) + return errors.Wrap(b.connErr, "last connection error") } - return fmt.Errorf("last connection error: %v; last resolver error: %v", b.connErr, b.resolverErr) + return errors.Wrapf(b.connErr, "last connection error; last resolver error: %v", b.resolverErr) } // regeneratePicker takes a snapshot of the balancer, and generates a picker diff --git a/internal/util/streamingutil/service/contextutil/cluster_id.go b/internal/util/streamingutil/service/contextutil/cluster_id.go index a43b94ee7d..4c129aa142 100644 --- a/internal/util/streamingutil/service/contextutil/cluster_id.go +++ b/internal/util/streamingutil/service/contextutil/cluster_id.go @@ -3,8 +3,9 @@ package contextutil import ( "context" - "github.com/cockroachdb/errors" "google.golang.org/grpc/metadata" + + "github.com/milvus-io/milvus/internal/util/streamingutil/status" ) const clusterIDKey = "cluster-id" @@ -18,14 +19,14 @@ func WithClusterID(ctx context.Context, clusterID string) context.Context { func GetClusterID(ctx context.Context) (string, error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { - return "", errors.New("cluster id not found from context") + return "", status.NewInvalidArgument("cluster id not found from context") } msg := md.Get(clusterIDKey) if len(msg) == 0 { - return "", errors.New("cluster id not found in context") + return "", status.NewInvalidArgument("cluster id not found in context") } if msg[0] == "" { - return "", errors.New("cluster id is empty") + return "", status.NewInvalidArgument("cluster id is empty") } return msg[0], nil } diff --git a/internal/util/streamingutil/service/contextutil/create_consumer.go b/internal/util/streamingutil/service/contextutil/create_consumer.go index 9381ed2acb..3947b03abc 100644 --- a/internal/util/streamingutil/service/contextutil/create_consumer.go +++ b/internal/util/streamingutil/service/contextutil/create_consumer.go @@ -9,6 +9,7 @@ import ( "google.golang.org/grpc/metadata" "google.golang.org/protobuf/proto" + "github.com/milvus-io/milvus/internal/util/streamingutil/status" "github.com/milvus-io/milvus/pkg/v3/proto/streamingpb" ) @@ -31,11 +32,11 @@ func WithCreateConsumer(ctx context.Context, req *streamingpb.CreateConsumerRequ func GetCreateConsumer(ctx context.Context) (*streamingpb.CreateConsumerRequest, error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { - return nil, errors.New("create consumer metadata not found from incoming context") + return nil, status.NewInvalidArgument("create consumer metadata not found from incoming context") } msg := md.Get(createConsumerKey) if len(msg) == 0 { - return nil, errors.New("create consumer metadata not found") + return nil, status.NewInvalidArgument("create consumer metadata not found") } bytes, err := base64.StdEncoding.DecodeString(msg[0]) diff --git a/internal/util/streamingutil/service/contextutil/create_producer.go b/internal/util/streamingutil/service/contextutil/create_producer.go index 120ca0d469..7346a2e8fe 100644 --- a/internal/util/streamingutil/service/contextutil/create_producer.go +++ b/internal/util/streamingutil/service/contextutil/create_producer.go @@ -9,6 +9,7 @@ import ( "google.golang.org/grpc/metadata" "google.golang.org/protobuf/proto" + "github.com/milvus-io/milvus/internal/util/streamingutil/status" "github.com/milvus-io/milvus/pkg/v3/proto/streamingpb" ) @@ -29,11 +30,11 @@ func WithCreateProducer(ctx context.Context, req *streamingpb.CreateProducerRequ func GetCreateProducer(ctx context.Context) (*streamingpb.CreateProducerRequest, error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { - return nil, errors.New("create producer metadata not found from incoming context") + return nil, status.NewInvalidArgument("create producer metadata not found from incoming context") } msg := md.Get(createProducerKey) if len(msg) == 0 { - return nil, errors.New("create consumer metadata not found") + return nil, status.NewInvalidArgument("create producer metadata not found") } bytes, err := base64.StdEncoding.DecodeString(msg[0]) diff --git a/internal/util/streamingutil/service/discoverer/session_discoverer.go b/internal/util/streamingutil/service/discoverer/session_discoverer.go index db3f7a7570..1138f74ac4 100644 --- a/internal/util/streamingutil/service/discoverer/session_discoverer.go +++ b/internal/util/streamingutil/service/discoverer/session_discoverer.go @@ -14,6 +14,7 @@ import ( "github.com/milvus-io/milvus/internal/json" "github.com/milvus-io/milvus/internal/util/sessionutil" "github.com/milvus-io/milvus/internal/util/streamingutil/service/attributes" + "github.com/milvus-io/milvus/internal/util/streamingutil/status" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -120,7 +121,7 @@ func (sw *sessionDiscoverer) watch(ctx context.Context, cb func(VersionedState) case event, ok := <-eventCh: // Break the loop if the watch is failed. if !ok { - return errors.New("etcd watch channel closed unexpectedly") + return status.NewInner("etcd watch channel closed unexpectedly") } if err := sw.handleETCDEvent(event); err != nil { return err diff --git a/internal/util/streamingutil/service/resolver/builder.go b/internal/util/streamingutil/service/resolver/builder.go index 70c4185b96..353cb42aaa 100644 --- a/internal/util/streamingutil/service/resolver/builder.go +++ b/internal/util/streamingutil/service/resolver/builder.go @@ -3,7 +3,6 @@ package resolver import ( "time" - "github.com/cockroachdb/errors" clientv3 "go.etcd.io/etcd/client/v3" "go.uber.org/zap" "google.golang.org/grpc/resolver" @@ -69,7 +68,7 @@ type builderImpl struct { // So build operation just register a new watcher into the existed resolver to share the resolver result. func (b *builderImpl) Build(_ resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (resolver.Resolver, error) { if !b.lifetime.Add(typeutil.LifetimeStateWorking) { - return nil, errors.New("builder is closed") + return nil, errBuilderClosed } defer b.lifetime.Done() diff --git a/internal/util/streamingutil/service/resolver/resolver.go b/internal/util/streamingutil/service/resolver/resolver.go index 9221de445e..1c0a93cc2f 100644 --- a/internal/util/streamingutil/service/resolver/resolver.go +++ b/internal/util/streamingutil/service/resolver/resolver.go @@ -14,6 +14,12 @@ type VersionedState = discoverer.VersionedState var ( ErrCanceled = errors.New("canceled") ErrInterrupted = errors.New("interrupted") + + // errBuilderClosed / errResolverClosed are grpc-resolver-framework-facing + // errors returned when the builder/resolver is used after close. They stay + // cockroachdb errors (not milvus merr), consistent with the balancer layer. + errBuilderClosed = errors.New("builder is closed") + errResolverClosed = errors.New("resolver is closed") ) // Builder is the interface for the grpc resolver builder. diff --git a/internal/util/streamingutil/service/resolver/watch_based_grpc_resolver.go b/internal/util/streamingutil/service/resolver/watch_based_grpc_resolver.go index 27244afabe..91b6c14437 100644 --- a/internal/util/streamingutil/service/resolver/watch_based_grpc_resolver.go +++ b/internal/util/streamingutil/service/resolver/watch_based_grpc_resolver.go @@ -1,7 +1,6 @@ package resolver import ( - "github.com/cockroachdb/errors" "go.uber.org/zap" "google.golang.org/grpc/resolver" @@ -45,7 +44,7 @@ func (r *watchBasedGRPCResolver) Close() { // Return error if the resolver is closed. func (r *watchBasedGRPCResolver) Update(state VersionedState) error { if !r.lifetime.Add(typeutil.LifetimeStateWorking) { - return errors.New("resolver is closed") + return errResolverClosed } defer r.lifetime.Done() diff --git a/internal/util/streamingutil/status/streaming_error.go b/internal/util/streamingutil/status/streaming_error.go index 5ed191e85c..693812efc0 100644 --- a/internal/util/streamingutil/status/streaming_error.go +++ b/internal/util/streamingutil/status/streaming_error.go @@ -141,8 +141,9 @@ func NewInner(format string, args ...interface{}) *StreamingError { return New(streamingpb.StreamingCode_STREAMING_CODE_INNER, format, args...) } -// NewInvaildArgument creates a new StreamingError with code STREAMING_CODE_INVAILD_ARGUMENT. -func NewInvaildArgument(format string, args ...interface{}) *StreamingError { +// NewInvalidArgument creates a new StreamingError with code STREAMING_CODE_INVAILD_ARGUMENT. +// (The proto enum still carries the historical typo INVAILD; only the Go factory uses the correct spelling.) +func NewInvalidArgument(format string, args ...interface{}) *StreamingError { return New(streamingpb.StreamingCode_STREAMING_CODE_INVAILD_ARGUMENT, format, args...) } diff --git a/internal/util/streamingutil/util/wal_selector.go b/internal/util/streamingutil/util/wal_selector.go index d36bad9c32..bf5c170dd1 100644 --- a/internal/util/streamingutil/util/wal_selector.go +++ b/internal/util/streamingutil/util/wal_selector.go @@ -12,6 +12,11 @@ const ( walTypeDefault = "default" ) +// errInvalidWALConfig is the sentinel for invalid/unselectable WAL configuration +// detected at startup. All such failures terminate the process (panic), so this +// stays a cockroachdb error rather than a client-facing merr. +var errInvalidWALConfig = errors.New("invalid wal config") + type walEnable struct { Rocksmq bool Pulsar bool @@ -66,30 +71,30 @@ func mustSelectWALName(standalone bool, mqType string, enable walEnable) message // woodpecker with local storage cannot work in cluster mode, // because local storage is not shared across nodes. if !standalone && paramtable.Get().WoodpeckerCfg.StorageType.GetValue() == "local" && !paramtable.Get().WoodpeckerCfg.ForceLocalStorage.GetAsBool() { - panic(errors.Newf("woodpecker with local storage is not supported in cluster mode, set woodpecker.storage.forceLocalStorage to true to override")) + panic(errors.Wrap(errInvalidWALConfig, "woodpecker with local storage is not supported in cluster mode, set woodpecker.storage.forceLocalStorage to true to override")) } return message.WALNameWoodpecker } - panic(errors.Errorf("no available wal config found, %s, enable: %+v", mqType, enable)) + panic(errors.Wrapf(errInvalidWALConfig, "no available wal config found, %s, enable: %+v", mqType, enable)) } // Validate mq type. func validateWALName(standalone bool, mqType string) (message.WALName, error) { mqName := message.NewWALName(mqType) if mqName == message.WALNameUnknown || mqName == message.WALNameTest { - return mqName, errors.Errorf("mq %s is not valid", mqType) + return mqName, errors.Wrapf(errInvalidWALConfig, "mq %s is not valid", mqType) } // we may register more mq type by plugin. // so we should not check all mq type here. // only check standalone type. if !standalone && mqName == message.WALNameRocksmq { - return mqName, errors.Newf("mq %s is only valid in standalone mode", mqType) + return mqName, errors.Wrapf(errInvalidWALConfig, "mq %s is only valid in standalone mode", mqType) } // woodpecker with local storage cannot work in cluster mode, // because local storage is not shared across nodes. if !standalone && mqName == message.WALNameWoodpecker && paramtable.Get().WoodpeckerCfg.StorageType.GetValue() == "local" && !paramtable.Get().WoodpeckerCfg.ForceLocalStorage.GetAsBool() { - return mqName, errors.Newf("woodpecker with local storage is not supported in cluster mode, set woodpecker.storage.forceLocalStorage to true to override") + return mqName, errors.Wrapf(errInvalidWALConfig, "woodpecker with local storage is not supported in cluster mode, set woodpecker.storage.forceLocalStorage to true to override") } return mqName, nil } diff --git a/internal/util/testutil/test_util.go b/internal/util/testutil/test_util.go index 8eca46280a..1126e09cfa 100644 --- a/internal/util/testutil/test_util.go +++ b/internal/util/testutil/test_util.go @@ -132,7 +132,7 @@ func CreateInsertData(schema *schemapb.CollectionSchema, rows int, nullPercent . validData[i] = true } } else { - return nil, merr.WrapErrParameterInvalidMsg(fmt.Sprintf("not support the number of nullPercent(%d)", nullPercent)) + return nil, merr.WrapErrParameterInvalidMsg("not support the number of nullPercent(%d)", nullPercent) } validDataMap[f.FieldID] = validData } @@ -935,7 +935,7 @@ func BuildArrayData(schema *schemapb.CollectionSchema, insertData *storage.Inser }) fixedSizeBuilder, ok := listBuilder.ValueBuilder().(*array.FixedSizeBinaryBuilder) if !ok { - return nil, fmt.Errorf("unexpected list value builder for VectorArray field %s: %T", field.GetName(), listBuilder.ValueBuilder()) + return nil, merr.WrapErrParameterInvalidMsg("unexpected list value builder for VectorArray field %s: %T", field.GetName(), listBuilder.ValueBuilder()) } vectorArrayData.Dim = dim @@ -944,10 +944,10 @@ func BuildArrayData(schema *schemapb.CollectionSchema, insertData *storage.Inser appendBinarySlice := func(data []byte, stride int) error { if stride == 0 { - return fmt.Errorf("zero stride for VectorArray field %s", field.GetName()) + return merr.WrapErrParameterInvalidMsg("zero stride for VectorArray field %s", field.GetName()) } if len(data)%stride != 0 { - return fmt.Errorf("vector array data length %d is not divisible by stride %d for field %s", len(data), stride, field.GetName()) + return merr.WrapErrParameterInvalidMsg("vector array data length %d is not divisible by stride %d for field %s", len(data), stride, field.GetName()) } for offset := 0; offset < len(data); offset += stride { fixedSizeBuilder.Append(data[offset : offset+stride]) @@ -967,14 +967,14 @@ func BuildArrayData(schema *schemapb.CollectionSchema, insertData *storage.Inser case schemapb.DataType_FloatVector: floatArray := vectorField.GetFloatVector() if floatArray == nil { - return nil, fmt.Errorf("expected FloatVector data for field %s", field.GetName()) + return nil, merr.WrapErrParameterInvalidMsg("expected FloatVector data for field %s", field.GetName()) } data := floatArray.GetData() if len(data) == 0 { continue } if len(data)%int(dim) != 0 { - return nil, fmt.Errorf("float vector data length %d is not divisible by dim %d for field %s", len(data), dim, field.GetName()) + return nil, merr.WrapErrParameterInvalidMsg("float vector data length %d is not divisible by dim %d for field %s", len(data), dim, field.GetName()) } for offset := 0; offset < len(data); offset += int(dim) { vectorBytes := make([]byte, bytesPerVector) @@ -1017,7 +1017,7 @@ func BuildArrayData(schema *schemapb.CollectionSchema, insertData *storage.Inser return nil, err } default: - return nil, fmt.Errorf("unsupported element type in VectorArray: %s", elementType.String()) + return nil, merr.WrapErrParameterInvalidMsg("unsupported element type in VectorArray: %s", elementType.String()) } } diff --git a/internal/util/textmatch/phrase_match.go b/internal/util/textmatch/phrase_match.go index 3b7ba7db2a..857075f9f7 100644 --- a/internal/util/textmatch/phrase_match.go +++ b/internal/util/textmatch/phrase_match.go @@ -16,12 +16,6 @@ import ( "github.com/milvus-io/milvus/pkg/v3/util/merr" ) -// Error codes from C++ segcore (internal/core/output/include/common/EasyAssert.h) -const ( - errCodeUnsupported = 2003 // Unsupported operation - errCodeClusterSkip = 2033 // ClusterSkip - pretend finished -) - // ComputePhraseMatchSlop computes the minimum slop required for a phrase match // between query and data texts using the specified analyzer. // Returns the slop value if match is possible, or error if terms are missing. @@ -56,12 +50,10 @@ func handleCStatus(status *C.CStatus, extraInfo string) error { logMsg := fmt.Sprintf("%s, C Runtime Exception: %s\n", extraInfo, errorMsg) log.Warn(logMsg) - if errorCode == 2003 { - return merr.WrapErrSegcoreUnsupported(int32(errorCode), logMsg) - } - if errorCode == 2033 { + if merr.IsSegcoreSignal(int32(errorCode)) { log.Info("fake finished the task") - return merr.ErrSegcorePretendFinished } - return merr.WrapErrSegcore(int32(errorCode), logMsg) + // Pass the raw errorMsg (not the polluted logMsg) so the merr reason stays + // clean; the extraInfo breadcrumb lives in the log above. + return merr.SegcoreError(int32(errorCode), errorMsg) } diff --git a/internal/util/typeutil/hash.go b/internal/util/typeutil/hash.go index 1b3abb7bf7..cc08c1d6d4 100644 --- a/internal/util/typeutil/hash.go +++ b/internal/util/typeutil/hash.go @@ -3,10 +3,9 @@ package typeutil import ( "strconv" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/pkg/v3/proto/planpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -22,7 +21,7 @@ func HashKey2Partitions(fieldSchema *schemapb.FieldSchema, keys []*planpb.Generi partitionName := partitionNames[value%numPartitions] selectedPartitions[partitionName] = struct{}{} } else { - return nil, errors.New("the data type of the data and the schema do not match") + return nil, merr.WrapErrParameterInvalidMsg("the data type of the data and the schema do not match") } } case schemapb.DataType_VarChar: @@ -32,11 +31,11 @@ func HashKey2Partitions(fieldSchema *schemapb.FieldSchema, keys []*planpb.Generi partitionName := partitionNames[value%numPartitions] selectedPartitions[partitionName] = struct{}{} } else { - return nil, errors.New("the data type of the data and the schema do not match") + return nil, merr.WrapErrParameterInvalidMsg("the data type of the data and the schema do not match") } } default: - return nil, errors.New("currently only support DataType Int64 or VarChar as partition keys") + return nil, merr.WrapErrParameterInvalidMsg("currently only support DataType Int64 or VarChar as partition keys") } result := make([]string, 0) diff --git a/internal/util/typeutil/json_util.go b/internal/util/typeutil/json_util.go index 0f4157b0e0..db7338d14b 100644 --- a/internal/util/typeutil/json_util.go +++ b/internal/util/typeutil/json_util.go @@ -18,12 +18,12 @@ package typeutil import ( "strings" - "github.com/cockroachdb/errors" "github.com/samber/lo" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/internal/parser/planparserv2" "github.com/milvus-io/milvus/pkg/v3/proto/planpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -42,7 +42,7 @@ func ParseAndVerifyNestedPath(identifier string, schema *schemapb.CollectionSche return "", err } if identifierExpr.GetColumnExpr().GetInfo().GetFieldId() != fieldID { - return "", errors.New("fieldID not match with field name") + return "", merr.WrapErrParameterInvalidMsg("fieldID not match with field name") } nestedPath := identifierExpr.GetColumnExpr().GetInfo().GetNestedPath() diff --git a/pkg/common/common.go b/pkg/common/common.go index 506c1a6008..239d475e72 100644 --- a/pkg/common/common.go +++ b/pkg/common/common.go @@ -18,13 +18,11 @@ package common import ( "encoding/binary" - "fmt" "math/bits" "strconv" "strings" "time" - "github.com/cockroachdb/errors" "github.com/samber/lo" "github.com/twpayne/go-geom/encoding/wkb" "github.com/twpayne/go-geom/encoding/wkbcommon" @@ -32,6 +30,7 @@ import ( "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: @@ -425,7 +424,7 @@ func IsCollectionWarmupKey(key string) bool { // ValidateWarmupPolicy validates that the warmup policy value is valid func ValidateWarmupPolicy(value string) error { if value != WarmupDisable && value != WarmupSync && value != WarmupAsync { - return fmt.Errorf("invalid warmup policy: %s, must be '%s', '%s' or '%s'", value, WarmupDisable, WarmupSync, WarmupAsync) + return merr.WrapErrParameterInvalidMsg("invalid warmup policy: %s, must be '%s', '%s' or '%s'", value, WarmupDisable, WarmupSync, WarmupAsync) } return nil } @@ -517,7 +516,7 @@ func IsPartitionKeyIsolationKvEnabled(kvs ...*commonpb.KeyValuePair) (bool, erro if kv.Key == PartitionKeyIsolationKey { val, err := strconv.ParseBool(strings.ToLower(kv.Value)) if err != nil { - return false, errors.Wrap(err, "failed to parse partition key isolation") + return false, merr.WrapErrParameterInvalidMsg("failed to parse partition key isolation: %v", err) } return val, nil } @@ -554,12 +553,12 @@ func ValidateQueryMode(kvs ...*commonpb.KeyValuePair) error { if kv.Key == QueryModeKey { mode := kv.Value if mode != QueryModeLargeTopK { - return fmt.Errorf("invalid query_mode value %q, valid values: [%s]", kv.Value, ValidQueryModes) + return merr.WrapErrParameterInvalidMsg("invalid query_mode value %q, valid values: [%s]", kv.Value, ValidQueryModes) } return nil } if strings.EqualFold(kv.Key, QueryModeKey) { - return fmt.Errorf("invalid property key %q, did you mean %q?", kv.Key, QueryModeKey) + return merr.WrapErrParameterInvalidMsg("invalid property key %q, did you mean %q?", kv.Key, QueryModeKey) } } return nil @@ -575,7 +574,7 @@ func IsDisableFuncRuntimeCheck(kvs ...*commonpb.KeyValuePair) (bool, error) { if kv.Key == DisableFuncRuntimeCheck { val, err := strconv.ParseBool(strings.ToLower(kv.Value)) if err != nil { - return false, errors.Wrap(err, "failed to parse disable_func_runtime_check param") + return false, merr.WrapErrParameterInvalidMsg("failed to parse disable_func_runtime_check param: %v", err) } return val, nil } @@ -590,7 +589,7 @@ func IsPartitionKeyIsolationPropEnabled(props map[string]string) (bool, error) { } iso, parseErr := strconv.ParseBool(val) if parseErr != nil { - return false, errors.Wrap(parseErr, "failed to parse partition key isolation property") + return false, merr.WrapErrParameterInvalidMsg("failed to parse partition key isolation property: %v", parseErr) } return iso, nil } @@ -605,20 +604,20 @@ func DatabaseLevelReplicaNumber(kvs []*commonpb.KeyValuePair) (int64, error) { if kv.Key == DatabaseReplicaNumber { replicaNum, err := strconv.ParseInt(kv.Value, 10, 64) if err != nil { - return 0, fmt.Errorf("invalid database property: [key=%s] [value=%s]", kv.Key, kv.Value) + return 0, merr.WrapErrParameterInvalidMsg("invalid database property: [key=%s] [value=%s]", kv.Key, kv.Value) } return replicaNum, nil } } - return 0, fmt.Errorf("database property not found: %s", DatabaseReplicaNumber) + 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 := fmt.Errorf("invalid database property: [key=%s] [value=%s]", kv.Key, kv.Value) + invalidPropValue := merr.WrapErrParameterInvalidMsg("invalid database property: [key=%s] [value=%s]", kv.Key, kv.Value) if len(kv.Value) == 0 { return nil, invalidPropValue } @@ -632,7 +631,7 @@ func DatabaseLevelResourceGroups(kvs []*commonpb.KeyValuePair) ([]string, error) } } - return nil, fmt.Errorf("database property not found: %s", DatabaseResourceGroups) + return nil, merr.WrapErrParameterInvalidMsg("database property not found: %s", DatabaseResourceGroups) } func CollectionLevelReplicaNumber(kvs []*commonpb.KeyValuePair) (int64, error) { @@ -640,20 +639,20 @@ func CollectionLevelReplicaNumber(kvs []*commonpb.KeyValuePair) (int64, error) { if kv.Key == CollectionReplicaNumber { replicaNum, err := strconv.ParseInt(kv.Value, 10, 64) if err != nil { - return 0, fmt.Errorf("invalid collection property: [key=%s] [value=%s]", kv.Key, kv.Value) + return 0, merr.WrapErrParameterInvalidMsg("invalid collection property: [key=%s] [value=%s]", kv.Key, kv.Value) } return replicaNum, nil } } - return 0, fmt.Errorf("collection property not found: %s", CollectionReplicaNumber) + 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 := fmt.Errorf("invalid collection property: [key=%s] [value=%s]", kv.Key, kv.Value) + invalidPropValue := merr.WrapErrParameterInvalidMsg("invalid collection property: [key=%s] [value=%s]", kv.Key, kv.Value) if len(kv.Value) == 0 { return nil, invalidPropValue } @@ -667,7 +666,7 @@ func CollectionLevelResourceGroups(kvs []*commonpb.KeyValuePair) ([]string, erro } } - return nil, fmt.Errorf("collection property not found: %s", CollectionReplicaNumber) + return nil, merr.WrapErrParameterInvalidMsg("collection property not found: %s", CollectionReplicaNumber) } // GetCollectionLoadFields returns the load field ids according to the type params. @@ -731,7 +730,7 @@ func ValidateAutoIndexMmapConfig(autoIndexConfigEnable, isVectorField bool, inde _, ok := indexParams[MmapEnabledKey] if ok && isVectorField { - return errors.New("mmap index is not supported to config for the collection in auto index mode") + return merr.WrapErrParameterInvalidMsg("mmap index is not supported to config for the collection in auto index mode") } return nil } @@ -825,9 +824,9 @@ func CheckNamespace(schema *schemapb.CollectionSchema, namespace *string) error namespaceIsSet := namespace != nil if enabled != namespaceIsSet { if namespaceIsSet { - return fmt.Errorf("namespace data is set but namespace disabled") + return merr.WrapErrParameterInvalidMsg("namespace data is set but namespace disabled") } - return fmt.Errorf("namespace data is not set but namespace enabled") + return merr.WrapErrParameterInvalidMsg("namespace data is not set but namespace enabled") } return nil } diff --git a/pkg/common/error.go b/pkg/common/error.go index 128151e9dc..aac9843a5a 100644 --- a/pkg/common/error.go +++ b/pkg/common/error.go @@ -27,7 +27,7 @@ var ErrNodeIDNotMatch = errors.New("target node id not match") // WrapNodeIDNotMatchError wraps `ErrNodeIDNotMatch` with targetID and sessionID. func WrapNodeIDNotMatchError(targetID, nodeID int64) error { - return fmt.Errorf("%w target id = %d, node id = %d", ErrNodeIDNotMatch, targetID, nodeID) + return errors.Wrapf(ErrNodeIDNotMatch, "target id = %d, node id = %d", targetID, nodeID) } // WrapNodeIDNotMatchMsg fmt error msg with `ErrNodeIDNotMatch`, targetID and sessionID. @@ -49,7 +49,20 @@ func NewIgnorableError(err error) error { } } +// NewIgnorableErrorf creates an IgnorableError from a format string. Prefer +// this over NewIgnorableError(fmt.Errorf(...)) so the linter does not flag a +// fmt.Errorf inside a function body — IgnorableError only ever surfaces the +// message string anyway. +func NewIgnorableErrorf(format string, args ...any) error { + return &IgnorableError{ + msg: fmt.Sprintf(format, args...), + } +} + +// IsIgnorableError reports whether err is (or wraps) an *IgnorableError. +// Uses errors.As so intermediate wrappers (e.g. merr.Wrap, errors.Wrap) +// do not hide the IgnorableError marker. func IsIgnorableError(err error) bool { - _, ok := err.(*IgnorableError) - return ok + var ie *IgnorableError + return errors.As(err, &ie) } diff --git a/pkg/config/config.go b/pkg/config/config.go index f055a5c531..aceede4ef3 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -33,6 +33,19 @@ var ( ErrNotInitial = errors.New("config is not initialized") ErrIgnoreChange = errors.New("ignore change") ErrKeyNotFound = errors.New("key not found") + + // config source management + ErrSourceDuplicate = errors.New("duplicate config source") + ErrSourceInvalid = errors.New("invalid config source or source not added") + + // etcd config read/write + ErrEtcdClientUnavailable = errors.New("etcd client is not available") + ErrImmutableConfigSaveFailed = errors.New("failed to save immutable configs to etcd") + ErrNoConfigsToAlter = errors.New("no configs to alter") + + // config file parsing + ErrUnsupportedConfigType = errors.New("unsupported config file type") + ErrAllConfigFilesNotExist = errors.New("all config files not exist") ) const ( diff --git a/pkg/config/file_source.go b/pkg/config/file_source.go index 344717a069..c649b65e65 100644 --- a/pkg/config/file_source.go +++ b/pkg/config/file_source.go @@ -17,7 +17,6 @@ package config import ( - "fmt" "os" "path/filepath" "sync" @@ -135,7 +134,7 @@ func (fs *FileSource) loadFromFile() error { ext := filepath.Ext(configFile) if len(ext) == 0 || (ext[1:] != "yaml" && ext[1:] != "yml") { - return fmt.Errorf("unsupported Config Type: %s", ext) + return errors.Wrapf(ErrUnsupportedConfigType, "%s", ext) } data, err := os.ReadFile(configFile) @@ -153,7 +152,7 @@ func (fs *FileSource) loadFromFile() error { } // not allow all config files missing, return error for this case if notExistsNum == len(configFiles) { - return errors.Newf("all config files not exists, files: %v", configFiles) + return errors.Wrapf(ErrAllConfigFilesNotExist, "files: %v", configFiles) } return fs.update(newConfig) diff --git a/pkg/config/manager.go b/pkg/config/manager.go index cad43288d9..8daebb0266 100644 --- a/pkg/config/manager.go +++ b/pkg/config/manager.go @@ -272,8 +272,7 @@ func (m *Manager) AddSource(source Source) error { sourceName := source.GetSourceName() _, ok := m.sources.Get(sourceName) if ok { - err := errors.New("duplicate source supplied") - return err + return ErrSourceDuplicate } source.SetManager(m) @@ -281,8 +280,7 @@ func (m *Manager) AddSource(source Source) error { err := m.pullSourceConfigs(sourceName) if err != nil { - err = fmt.Errorf("failed to load %s cause: %x", sourceName, err) - return err + return errors.Wrapf(err, "failed to load source %s", sourceName) } source.SetEventHandler(m) @@ -341,7 +339,7 @@ func (m *Manager) UpdateSourceOptions(opts ...Option) { func (m *Manager) pullSourceConfigs(source string) error { configSource, ok := m.sources.Get(source) if !ok { - return errors.New("invalid source or source not added") + return ErrSourceInvalid } configs, err := configSource.GetConfigurations() @@ -551,14 +549,14 @@ func (m *Manager) ProcessImmutableConfigs() error { } if len(saveErrors) > 0 { - return fmt.Errorf("failed to save %d immutable configs to etcd", len(saveErrors)) + return errors.Wrapf(ErrImmutableConfigSaveFailed, "%d config(s) failed", len(saveErrors)) } return nil } func (m *Manager) SaveConfigToEtcd(etcdSource *EtcdSource, key, value string) error { if etcdSource == nil || etcdSource.etcdCli == nil { - return errors.New("etcd client is not available") + return ErrEtcdClientUnavailable } etcdKey := fmt.Sprintf("%s/config/%s", etcdSource.keyPrefix, key) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -568,7 +566,7 @@ func (m *Manager) SaveConfigToEtcd(etcdSource *EtcdSource, key, value string) er Then(clientv3.OpPut(etcdKey, value)). Commit() if err != nil { - return fmt.Errorf("failed to put config to etcd: %w", err) + return errors.Wrap(err, "failed to put config to etcd") } if !resp.Succeeded { log.Info("config already exists in etcd, skip writing", @@ -591,11 +589,11 @@ func (m *Manager) UpdateConfigInEtcd(etcdSource *EtcdSource, key, value string) // Both updates (put) and deletes are executed in a single etcd transaction. func (m *Manager) AlterConfigsInEtcd(etcdSource *EtcdSource, updates map[string]string, deletes []string) error { if etcdSource == nil || etcdSource.etcdCli == nil { - return errors.New("etcd client is not available") + return ErrEtcdClientUnavailable } if len(updates) == 0 && len(deletes) == 0 { - return errors.New("no configs to alter") + return ErrNoConfigsToAlter } // Build transaction operations @@ -618,7 +616,7 @@ func (m *Manager) AlterConfigsInEtcd(etcdSource *EtcdSource, updates map[string] Then(ops...). Commit() if err != nil { - return fmt.Errorf("failed to atomically alter configs in etcd: %w", err) + return errors.Wrap(err, "failed to atomically alter configs in etcd") } // Proactively refresh local EtcdSource so the write is immediately visible in this process, diff --git a/pkg/kv/rocksdb/rocksdb_kv.go b/pkg/kv/rocksdb/rocksdb_kv.go index 0d1e5d4e09..a87c1cc8f2 100644 --- a/pkg/kv/rocksdb/rocksdb_kv.go +++ b/pkg/kv/rocksdb/rocksdb_kv.go @@ -18,9 +18,7 @@ package rocksdbkv import ( "context" - "fmt" - "github.com/cockroachdb/errors" "github.com/samber/lo" "github.com/tecbot/gorocksdb" @@ -50,7 +48,7 @@ const ( func NewRocksdbKV(name string) (*RocksdbKV, error) { // TODO we should use multiple column family of rocks db rather than init multiple db instance if name == "" { - return nil, errors.New("rocksdb name is nil") + return nil, merr.WrapErrServiceInternalMsg("rocksdb name is nil") } bbto := gorocksdb.NewDefaultBlockBasedTableOptions() bbto.SetCacheIndexAndFilterBlocks(true) @@ -101,10 +99,10 @@ func (kv *RocksdbKV) GetName() string { // Load returns the value of specified key func (kv *RocksdbKV) Load(ctx context.Context, key string) (string, error) { if kv.DB == nil { - return "", fmt.Errorf("rocksdb instance is nil when load %s", key) + return "", merr.WrapErrServiceInternalMsg("rocksdb instance is nil when load %s", key) } if key == "" { - return "", errors.New("rocksdb kv does not support load empty key") + return "", merr.WrapErrParameterInvalidMsg("rocksdb kv does not support load empty key") } option := gorocksdb.NewDefaultReadOptions() defer option.Destroy() @@ -118,10 +116,10 @@ func (kv *RocksdbKV) Load(ctx context.Context, key string) (string, error) { func (kv *RocksdbKV) LoadBytes(ctx context.Context, key string) ([]byte, error) { if kv.DB == nil { - return nil, fmt.Errorf("rocksdb instance is nil when load %s", key) + return nil, merr.WrapErrServiceInternalMsg("rocksdb instance is nil when load %s", key) } if key == "" { - return nil, errors.New("rocksdb kv does not support load empty key") + return nil, merr.WrapErrParameterInvalidMsg("rocksdb kv does not support load empty key") } option := gorocksdb.NewDefaultReadOptions() @@ -143,7 +141,7 @@ func (kv *RocksdbKV) LoadBytes(ctx context.Context, key string) ([]byte, error) // if prefix is "", then load every thing from the database func (kv *RocksdbKV) LoadWithPrefix(ctx context.Context, prefix string) ([]string, []string, error) { if kv.DB == nil { - return nil, nil, fmt.Errorf("rocksdb instance is nil when load %s", prefix) + return nil, nil, merr.WrapErrServiceInternalMsg("rocksdb instance is nil when load %s", prefix) } option := gorocksdb.NewDefaultReadOptions() defer option.Destroy() @@ -168,7 +166,7 @@ func (kv *RocksdbKV) LoadWithPrefix(ctx context.Context, prefix string) ([]strin func (kv *RocksdbKV) Has(ctx context.Context, key string) (bool, error) { if kv.DB == nil { - return false, fmt.Errorf("rocksdb instance is nil when check if has %s", key) + return false, merr.WrapErrServiceInternalMsg("rocksdb instance is nil when check if has %s", key) } option := gorocksdb.NewDefaultReadOptions() @@ -184,7 +182,7 @@ func (kv *RocksdbKV) Has(ctx context.Context, key string) (bool, error) { func (kv *RocksdbKV) HasPrefix(ctx context.Context, prefix string) (bool, error) { if kv.DB == nil { - return false, fmt.Errorf("rocksdb instance is nil when check if has prefix %s", prefix) + return false, merr.WrapErrServiceInternalMsg("rocksdb instance is nil when check if has prefix %s", prefix) } option := gorocksdb.NewDefaultReadOptions() @@ -198,7 +196,7 @@ func (kv *RocksdbKV) HasPrefix(ctx context.Context, prefix string) (bool, error) func (kv *RocksdbKV) LoadBytesWithPrefix(ctx context.Context, prefix string) ([]string, [][]byte, error) { if kv.DB == nil { - return nil, nil, fmt.Errorf("rocksdb instance is nil when load %s", prefix) + return nil, nil, merr.WrapErrServiceInternalMsg("rocksdb instance is nil when load %s", prefix) } option := gorocksdb.NewDefaultReadOptions() @@ -233,7 +231,7 @@ func (kv *RocksdbKV) LoadBytesWithPrefix(ctx context.Context, prefix string) ([] // MultiLoad load a batch of values by keys func (kv *RocksdbKV) MultiLoad(ctx context.Context, keys []string) ([]string, error) { if kv.DB == nil { - return nil, errors.New("rocksdb instance is nil when do MultiLoad") + return nil, merr.WrapErrServiceInternalMsg("rocksdb instance is nil when do MultiLoad") } keyInBytes := make([][]byte, 0, len(keys)) @@ -257,7 +255,7 @@ func (kv *RocksdbKV) MultiLoad(ctx context.Context, keys []string) ([]string, er func (kv *RocksdbKV) MultiLoadBytes(ctx context.Context, keys []string) ([][]byte, error) { if kv.DB == nil { - return nil, errors.New("rocksdb instance is nil when do MultiLoad") + return nil, merr.WrapErrServiceInternalMsg("rocksdb instance is nil when do MultiLoad") } keyInBytes := make([][]byte, 0, len(keys)) @@ -285,13 +283,13 @@ func (kv *RocksdbKV) MultiLoadBytes(ctx context.Context, keys []string) ([][]byt // Save a pair of key-value func (kv *RocksdbKV) Save(ctx context.Context, key, value string) error { if kv.DB == nil { - return errors.New("rocksdb instance is nil when do save") + return merr.WrapErrServiceInternalMsg("rocksdb instance is nil when do save") } if key == "" { - return errors.New("rocksdb kv does not support empty key") + return merr.WrapErrParameterInvalidMsg("rocksdb kv does not support empty key") } if value == "" { - return errors.New("rocksdb kv does not support empty value") + return merr.WrapErrParameterInvalidMsg("rocksdb kv does not support empty value") } return kv.DB.Put(kv.WriteOptions, []byte(key), []byte(value)) @@ -299,13 +297,13 @@ func (kv *RocksdbKV) Save(ctx context.Context, key, value string) error { func (kv *RocksdbKV) SaveBytes(ctx context.Context, key string, value []byte) error { if kv.DB == nil { - return errors.New("rocksdb instance is nil when do save") + return merr.WrapErrServiceInternalMsg("rocksdb instance is nil when do save") } if key == "" { - return errors.New("rocksdb kv does not support empty key") + return merr.WrapErrParameterInvalidMsg("rocksdb kv does not support empty key") } if len(value) == 0 { - return errors.New("rocksdb kv does not support empty value") + return merr.WrapErrParameterInvalidMsg("rocksdb kv does not support empty value") } return kv.DB.Put(kv.WriteOptions, []byte(key), value) @@ -314,7 +312,7 @@ func (kv *RocksdbKV) SaveBytes(ctx context.Context, key string, value []byte) er // MultiSave a batch of key-values func (kv *RocksdbKV) MultiSave(ctx context.Context, kvs map[string]string) error { if kv.DB == nil { - return errors.New("rocksdb instance is nil when do MultiSave") + return merr.WrapErrServiceInternalMsg("rocksdb instance is nil when do MultiSave") } writeBatch := gorocksdb.NewWriteBatch() @@ -329,7 +327,7 @@ func (kv *RocksdbKV) MultiSave(ctx context.Context, kvs map[string]string) error func (kv *RocksdbKV) MultiSaveBytes(ctx context.Context, kvs map[string][]byte) error { if kv.DB == nil { - return errors.New("rocksdb instance is nil when do MultiSave") + return merr.WrapErrServiceInternalMsg("rocksdb instance is nil when do MultiSave") } writeBatch := gorocksdb.NewWriteBatch() @@ -346,7 +344,7 @@ func (kv *RocksdbKV) MultiSaveBytes(ctx context.Context, kvs map[string][]byte) // If prefix is "", then all data in the rocksdb kv will be deleted func (kv *RocksdbKV) RemoveWithPrefix(ctx context.Context, prefix string) error { if kv.DB == nil { - return errors.New("rocksdb instance is nil when do RemoveWithPrefix") + return merr.WrapErrServiceInternalMsg("rocksdb instance is nil when do RemoveWithPrefix") } if len(prefix) == 0 { // better to use drop column family, but as we use default column family, we just delete ["",lastKey+1) @@ -369,10 +367,10 @@ func (kv *RocksdbKV) RemoveWithPrefix(ctx context.Context, prefix string) error // Remove is used to remove a pair of key-value func (kv *RocksdbKV) Remove(ctx context.Context, key string) error { if kv.DB == nil { - return errors.New("rocksdb instance is nil when do Remove") + return merr.WrapErrServiceInternalMsg("rocksdb instance is nil when do Remove") } if key == "" { - return errors.New("rocksdb kv does not support empty key") + return merr.WrapErrParameterInvalidMsg("rocksdb kv does not support empty key") } err := kv.DB.Delete(kv.WriteOptions, []byte(key)) return err @@ -381,7 +379,7 @@ func (kv *RocksdbKV) Remove(ctx context.Context, key string) error { // MultiRemove is used to remove a batch of key-values func (kv *RocksdbKV) MultiRemove(ctx context.Context, keys []string) error { if kv.DB == nil { - return errors.New("rocksdb instance is nil when do MultiRemove") + return merr.WrapErrServiceInternalMsg("rocksdb instance is nil when do MultiRemove") } writeBatch := gorocksdb.NewWriteBatch() defer writeBatch.Destroy() @@ -398,7 +396,7 @@ func (kv *RocksdbKV) MultiSaveAndRemove(ctx context.Context, saves map[string]st return merr.WrapErrServiceUnavailable("predicates not supported") } if kv.DB == nil { - return errors.New("Rocksdb instance is nil when do MultiSaveAndRemove") + return merr.WrapErrServiceInternalMsg("Rocksdb instance is nil when do MultiSaveAndRemove") } writeBatch := gorocksdb.NewWriteBatch() defer writeBatch.Destroy() @@ -421,10 +419,10 @@ func (kv *RocksdbKV) MultiSaveAndRemove(ctx context.Context, saves map[string]st // DeleteRange remove a batch of key-values from startKey to endKey func (kv *RocksdbKV) DeleteRange(ctx context.Context, startKey, endKey string) error { if kv.DB == nil { - return errors.New("Rocksdb instance is nil when do DeleteRange") + return merr.WrapErrServiceInternalMsg("Rocksdb instance is nil when do DeleteRange") } if startKey >= endKey { - return fmt.Errorf("rockskv delete range startkey must < endkey, startkey %s, endkey %s", startKey, endKey) + return merr.WrapErrParameterInvalidMsg("rockskv delete range startkey must < endkey, startkey %s, endkey %s", startKey, endKey) } writeBatch := gorocksdb.NewWriteBatch() defer writeBatch.Destroy() @@ -439,7 +437,7 @@ func (kv *RocksdbKV) MultiSaveAndRemoveWithPrefix(ctx context.Context, saves map return merr.WrapErrServiceUnavailable("predicates not supported") } if kv.DB == nil { - return errors.New("Rocksdb instance is nil when do MultiSaveAndRemove") + return merr.WrapErrServiceInternalMsg("Rocksdb instance is nil when do MultiSaveAndRemove") } writeBatch := gorocksdb.NewWriteBatch() defer writeBatch.Destroy() diff --git a/pkg/log/log.go b/pkg/log/log.go index 83cc478c59..a25e7e1aa0 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -30,7 +30,6 @@ package log import ( - "fmt" "os" "path/filepath" "strings" @@ -115,12 +114,15 @@ func InitTestLogger(t zaptest.TestingT, cfg *Config, opts ...zap.Option) (*zap.L return InitLoggerWithWriteSyncer(cfg, writer, opts...) } +// errLogFileIsDir is returned when the configured log file path points to a directory. +var errLogFileIsDir = errors.New("can't use directory as log file name") + // InitLoggerWithWriteSyncer initializes a zap logger with specified write syncer. func InitLoggerWithWriteSyncer(cfg *Config, output zapcore.WriteSyncer, opts ...zap.Option) (*zap.Logger, *ZapProperties, error) { level := zap.NewAtomicLevel() err := level.UnmarshalText([]byte(cfg.Level)) if err != nil { - return nil, nil, fmt.Errorf("initLoggerWithWriteSyncer UnmarshalText cfg.Level err:%w", err) + return nil, nil, errors.Wrapf(err, "initLoggerWithWriteSyncer UnmarshalText cfg.Level err") } var core zapcore.Core if cfg.AsyncWriteEnable { @@ -144,7 +146,7 @@ func initFileLog(cfg *FileLogConfig) (*lumberjack.Logger, error) { logPath := strings.Join([]string{cfg.RootPath, cfg.Filename}, string(filepath.Separator)) if st, err := os.Stat(logPath); err == nil { if st.IsDir() { - return nil, errors.New("can't use directory as log file name") + return nil, errLogFileIsDir } } if cfg.MaxSize == 0 { diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index 16f40a1c67..71098ac81c 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -34,6 +34,22 @@ const ( RetryLabel = "retry" RejectedLabel = "rejected" + // Fine-grained hard-failure labels (composite status values, not a new + // dimension label, to keep Prometheus cardinality additive rather than + // multiplicative). They split the coarse "fail"/"rejected" into the + // responsible party so monitoring/alerting can tell a user-input error + // (the caller must fix the request) apart from an internal system error + // (operators must intervene). MIGRATION: old queries on status="fail" must + // move to status=~"fail_.*" (fail_input/fail_system), and old queries on + // status="rejected" must move to status=~"rejected_.*" (rejected_user for + // caller-side auth/privilege/bad-arg rejections, rejected_system otherwise). + // Dashboards still matching the bare "fail"/"rejected" values return nothing + // after this split. + FailInputLabel = "fail_input" + FailSystemLabel = "fail_system" + RejectedSystemLabel = "rejected_system" + RejectedUserLabel = "rejected_user" + HybridSearchLabel = "hybrid_search" InsertLabel = "insert" diff --git a/pkg/mq/common/message.go b/pkg/mq/common/message.go index 63756017bc..b2b0030c2e 100644 --- a/pkg/mq/common/message.go +++ b/pkg/mq/common/message.go @@ -17,12 +17,10 @@ package common import ( - "fmt" - - "github.com/cockroachdb/errors" "google.golang.org/protobuf/proto" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // ProducerOptions contains the options of a producer @@ -99,14 +97,14 @@ func GetMsgTypeFromRaw(payload []byte, properties map[string]string) (commonpb.M if msgType == commonpb.MsgType_Undefined { header := commonpb.MsgHeader{} if payload == nil { - return msgType, errors.New("failed to unmarshal message header, payload is empty") + return msgType, merr.WrapErrDataIntegrityMsg("failed to unmarshal message header, payload is empty") } err := proto.Unmarshal(payload, &header) if err != nil { - return msgType, fmt.Errorf("failed to unmarshal message header, err %s", err.Error()) + return msgType, merr.WrapErrDataIntegrity(err, "failed to unmarshal message header") } if header.Base == nil { - return msgType, errors.New("failed to unmarshal message, header is uncomplete") + return msgType, merr.WrapErrDataIntegrityMsg("failed to unmarshal message, header is uncomplete") } msgType = header.Base.MsgType } diff --git a/pkg/mq/mqimpl/rocksmq/client/client_impl.go b/pkg/mq/mqimpl/rocksmq/client/client_impl.go index 6cd63fa0f8..e793820245 100644 --- a/pkg/mq/mqimpl/rocksmq/client/client_impl.go +++ b/pkg/mq/mqimpl/rocksmq/client/client_impl.go @@ -23,6 +23,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/mq/common" "github.com/milvus-io/milvus/pkg/v3/mq/mqimpl/rocksmq/server" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) const ( @@ -185,10 +186,10 @@ func (c *client) consume(consumer *consumer) { func (c *client) blockUntilInitDone(consumer *consumer) error { select { case <-c.closeCh: - return errors.New("client is closed") + return merr.WrapErrServiceUnavailable("client is closed") case _, ok := <-consumer.initCh: if !ok { - return errors.New("consumer init failure") + return merr.WrapErrServiceUnavailable("consumer init failure") } return nil } diff --git a/pkg/mq/mqimpl/rocksmq/client/util.go b/pkg/mq/mqimpl/rocksmq/client/util.go index 8989cfc8cd..89b21fcc9e 100644 --- a/pkg/mq/mqimpl/rocksmq/client/util.go +++ b/pkg/mq/mqimpl/rocksmq/client/util.go @@ -12,10 +12,10 @@ package client import ( - "github.com/cockroachdb/errors" "google.golang.org/protobuf/proto" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) func MarshalHeader(header *commonpb.MsgHeader) ([]byte, error) { @@ -29,14 +29,14 @@ func MarshalHeader(header *commonpb.MsgHeader) ([]byte, error) { func UnmarshalHeader(headerbyte []byte) (*commonpb.MsgHeader, error) { header := commonpb.MsgHeader{} if headerbyte == nil { - return &header, errors.New("failed to unmarshal message header, payload is empty") + return &header, merr.WrapErrDataIntegrityMsg("failed to unmarshal message header, payload is empty") } err := proto.Unmarshal(headerbyte, &header) if err != nil { return &header, err } if header.Base == nil { - return nil, errors.New("failed to unmarshal message, header is uncomplete") + return nil, merr.WrapErrDataIntegrityMsg("failed to unmarshal message, header is uncomplete") } return &header, nil } diff --git a/pkg/mq/mqimpl/rocksmq/server/global_rmq.go b/pkg/mq/mqimpl/rocksmq/server/global_rmq.go index d00b9bca4e..bbc8013002 100644 --- a/pkg/mq/mqimpl/rocksmq/server/global_rmq.go +++ b/pkg/mq/mqimpl/rocksmq/server/global_rmq.go @@ -21,10 +21,10 @@ import ( "os" "sync" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // Rmq is global rocksmq instance that will be initialized only once @@ -48,7 +48,7 @@ func InitRocksMQ(path string) error { } else { if !fi.IsDir() { errMsg := "can't create a directory because there exists a file with the same name" - finalErr = errors.New(errMsg) + finalErr = merr.WrapErrServiceInternal(errMsg) return } } diff --git a/pkg/mq/mqimpl/rocksmq/server/rocksmq_impl.go b/pkg/mq/mqimpl/rocksmq/server/rocksmq_impl.go index 208a4581f9..a4eae43718 100644 --- a/pkg/mq/mqimpl/rocksmq/server/rocksmq_impl.go +++ b/pkg/mq/mqimpl/rocksmq/server/rocksmq_impl.go @@ -13,7 +13,6 @@ package server import ( "context" - "fmt" "path" "strconv" "strings" @@ -21,7 +20,6 @@ import ( "sync/atomic" "time" - "github.com/cockroachdb/errors" "github.com/samber/lo" "github.com/tecbot/gorocksdb" "go.uber.org/zap" @@ -101,7 +99,7 @@ func constructKey(metaName, topic string) string { func parsePageID(key string) (int64, error) { stringSlice := strings.Split(key, "/") if len(stringSlice) != 3 { - return 0, fmt.Errorf("invalid page id %s ", key) + return 0, merr.WrapErrMqInternalMsg("invalid page id %s ", key) } return strconv.ParseInt(stringSlice[2], 10, 64) } @@ -226,12 +224,12 @@ func parseCompressionType(params *paramtable.ComponentParam) ([]gorocksdb.Compre return lo.Map(params.RocksmqCfg.CompressionTypes.GetAsStrings(), func(sType string, _ int) gorocksdb.CompressionType { iType, err := strconv.Atoi(sType) if err != nil { - tError = fmt.Errorf("invalid rocksmq compression type: %s", err.Error()) + tError = merr.WrapErrParameterInvalidErr(err, "invalid rocksmq compression type") return 0 } if !lo.Contains(validType, iType) { - tError = fmt.Errorf("invalid rocksmq compression type, should in %v", validType) + tError = merr.WrapErrParameterInvalidMsg("invalid rocksmq compression type, should in %v", validType) return 0 } return gorocksdb.CompressionType(iType) @@ -491,7 +489,7 @@ func (rmq *rocksmq) stopRetention() { // CreateTopic writes initialized messages for topic in rocksdb func (rmq *rocksmq) CreateTopic(topicName string) error { if rmq.isClosed() { - return errors.New(RmqNotServingErrMsg) + return merr.WrapErrServiceUnavailable(RmqNotServingErrMsg) } start := time.Now() @@ -499,7 +497,7 @@ func (rmq *rocksmq) CreateTopic(topicName string) error { // Check if topicName contains "/" if strings.Contains(topicName, "/") { log.Warn("rocksmq failed to create topic for topic name contains \"/\"", zap.String("topic", topicName)) - return retry.Unrecoverable(fmt.Errorf("topic name = %s contains \"/\"", topicName)) + return retry.Unrecoverable(merr.WrapErrParameterInvalidMsg("topic name = %s contains \"/\"", topicName)) } // topicIDKey is the only identifier of a topic @@ -543,11 +541,11 @@ func (rmq *rocksmq) DestroyTopic(topicName string) error { start := time.Now() ll, ok := topicMu.Load(topicName) if !ok { - return fmt.Errorf("topic name = %s not exist", topicName) + return merr.WrapErrMqTopicNotFound(topicName) } lock, ok := ll.(*sync.Mutex) if !ok { - return fmt.Errorf("get mutex failed, topic name = %s", topicName) + return merr.WrapErrMqInternalMsg("get mutex failed, topic name = %s", topicName) } lock.Lock() defer lock.Unlock() @@ -619,13 +617,13 @@ func (rmq *rocksmq) ExistConsumerGroup(topicName, groupName string) (bool, *Cons // CreateConsumerGroup creates an nonexistent consumer group for topic func (rmq *rocksmq) CreateConsumerGroup(topicName, groupName string) error { if rmq.isClosed() { - return errors.New(RmqNotServingErrMsg) + return merr.WrapErrServiceUnavailable(RmqNotServingErrMsg) } start := time.Now() key := constructCurrentID(topicName, groupName) _, ok := rmq.consumersID.Load(key) if ok { - return fmt.Errorf("RMQ CreateConsumerGroup key already exists, key = %s", key) + return merr.WrapErrMqInternalMsg("RMQ CreateConsumerGroup key already exists, key = %s", key) } rmq.consumersID.Store(key, DefaultMessageID) log.Ctx(rmq.ctx).Debug("Rocksmq create consumer group successfully ", zap.String("topic", topicName), @@ -637,7 +635,7 @@ func (rmq *rocksmq) CreateConsumerGroup(topicName, groupName string) error { // RegisterConsumer registers a consumer in rocksmq consumers func (rmq *rocksmq) RegisterConsumer(consumer *Consumer) error { if rmq.isClosed() { - return errors.New(RmqNotServingErrMsg) + return merr.WrapErrServiceUnavailable(RmqNotServingErrMsg) } ll, _ := topicMu.LoadOrStore(consumer.Topic, new(sync.Mutex)) mu, _ := ll.(*sync.Mutex) @@ -658,7 +656,7 @@ func (rmq *rocksmq) RegisterConsumer(consumer *Consumer) error { func (rmq *rocksmq) GetLatestMsg(topicName string) (int64, error) { if rmq.isClosed() { - return DefaultMessageID, errors.New(RmqNotServingErrMsg) + return DefaultMessageID, merr.WrapErrServiceUnavailable(RmqNotServingErrMsg) } msgID, err := rmq.getLatestMsg(topicName) if err != nil { @@ -671,7 +669,7 @@ func (rmq *rocksmq) GetLatestMsg(topicName string) (int64, error) { // DestroyConsumerGroup removes a consumer group from rocksdb_kv func (rmq *rocksmq) DestroyConsumerGroup(topicName, groupName string) error { if rmq.isClosed() { - return errors.New(RmqNotServingErrMsg) + return merr.WrapErrServiceUnavailable(RmqNotServingErrMsg) } return rmq.destroyConsumerGroupInternal(topicName, groupName) } @@ -681,11 +679,11 @@ func (rmq *rocksmq) destroyConsumerGroupInternal(topicName, groupName string) er start := time.Now() ll, ok := topicMu.Load(topicName) if !ok { - return fmt.Errorf("topic name = %s not exist", topicName) + return merr.WrapErrMqTopicNotFound(topicName) } lock, ok := ll.(*sync.Mutex) if !ok { - return fmt.Errorf("get mutex failed, topic name = %s", topicName) + return merr.WrapErrMqInternalMsg("get mutex failed, topic name = %s", topicName) } lock.Lock() defer lock.Unlock() @@ -709,19 +707,19 @@ func (rmq *rocksmq) destroyConsumerGroupInternal(topicName, groupName string) er // Produce produces messages for topic and updates page infos for retention func (rmq *rocksmq) Produce(topicName string, messages []ProducerMessage) ([]UniqueID, error) { if messages == nil { - return []UniqueID{}, errors.New("messages are empty") + return []UniqueID{}, merr.WrapErrParameterInvalidMsg("messages are empty") } if rmq.isClosed() { - return nil, errors.New(RmqNotServingErrMsg) + return nil, merr.WrapErrServiceUnavailable(RmqNotServingErrMsg) } start := time.Now() ll, ok := topicMu.Load(topicName) if !ok { - return []UniqueID{}, fmt.Errorf("topic name = %s not exist", topicName) + return []UniqueID{}, merr.WrapErrMqTopicNotFound(topicName) } lock, ok := ll.(*sync.Mutex) if !ok { - return []UniqueID{}, fmt.Errorf("get mutex failed, topic name = %s", topicName) + return []UniqueID{}, merr.WrapErrMqInternalMsg("get mutex failed, topic name = %s", topicName) } lock.Lock() defer lock.Unlock() @@ -735,7 +733,7 @@ func (rmq *rocksmq) Produce(topicName string, messages []ProducerMessage) ([]Uni } allocTime := time.Since(start).Milliseconds() if UniqueID(msgLen) != idEnd-idStart { - return []UniqueID{}, errors.New("Obtained id length is not equal that of message") + return []UniqueID{}, merr.WrapErrMqInternalMsg("Obtained id length is not equal that of message") } // Insert data to store system batch := gorocksdb.NewWriteBatch() @@ -840,24 +838,24 @@ func (rmq *rocksmq) getLastID(topicName string) (int64, bool) { // 3. Update ack informations in rocksdb func (rmq *rocksmq) Consume(topicName string, groupName string, n int) ([]ConsumerMessage, error) { if rmq.isClosed() { - return nil, errors.New(RmqNotServingErrMsg) + return nil, merr.WrapErrServiceUnavailable(RmqNotServingErrMsg) } start := time.Now() ll, ok := topicMu.Load(topicName) if !ok { - return nil, fmt.Errorf("topic name = %s not exist", topicName) + return nil, merr.WrapErrMqTopicNotFound(topicName) } lock, ok := ll.(*sync.Mutex) if !ok { - return nil, fmt.Errorf("get mutex failed, topic name = %s", topicName) + return nil, merr.WrapErrMqInternalMsg("get mutex failed, topic name = %s", topicName) } lock.Lock() defer lock.Unlock() currentID, ok := rmq.getCurrentID(topicName, groupName) if !ok { - return nil, fmt.Errorf("currentID of topicName=%s, groupName=%s not exist", topicName, groupName) + return nil, merr.WrapErrMqInternalMsg("currentID of topicName=%s, groupName=%s not exist", topicName, groupName) } // return if don't have new message @@ -951,7 +949,7 @@ func (rmq *rocksmq) seek(topicName string, groupName string, msgID UniqueID) err key := constructCurrentID(topicName, groupName) _, ok := rmq.consumersID.Load(key) if !ok { - return fmt.Errorf("ConsumerGroup %s, channel %s not exists", groupName, topicName) + return merr.WrapErrMqInternalMsg("ConsumerGroup %s, channel %s not exists", groupName, topicName) } storeKey := path.Join(topicName, strconv.FormatInt(msgID, 10)) @@ -977,7 +975,7 @@ func (rmq *rocksmq) seek(topicName string, groupName string, msgID UniqueID) err func (rmq *rocksmq) moveConsumePos(topicName string, groupName string, msgID UniqueID) error { oldPos, ok := rmq.getCurrentID(topicName, groupName) if !ok { - return errors.New("move unknown consumer") + return merr.WrapErrMqInternalMsg("move unknown consumer") } log := log.Ctx(rmq.ctx) @@ -1002,7 +1000,7 @@ func (rmq *rocksmq) moveConsumePos(topicName string, groupName string, msgID Uni // Seek updates the current id to the given msgID func (rmq *rocksmq) Seek(topicName string, groupName string, msgID UniqueID) error { if rmq.isClosed() { - return errors.New(RmqNotServingErrMsg) + return merr.WrapErrServiceUnavailable(RmqNotServingErrMsg) } /* Step I: Check if key exists */ ll, ok := topicMu.Load(topicName) @@ -1011,7 +1009,7 @@ func (rmq *rocksmq) Seek(topicName string, groupName string, msgID UniqueID) err } lock, ok := ll.(*sync.Mutex) if !ok { - return fmt.Errorf("get mutex failed, topic name = %s", topicName) + return merr.WrapErrMqInternalMsg("get mutex failed, topic name = %s", topicName) } lock.Lock() defer lock.Unlock() @@ -1029,7 +1027,7 @@ func (rmq *rocksmq) ForceSeek(topicName string, groupName string, msgID UniqueID log := log.Ctx(rmq.ctx) log.Warn("Use method ForceSeek that only for test") if rmq.isClosed() { - return errors.New(RmqNotServingErrMsg) + return merr.WrapErrServiceUnavailable(RmqNotServingErrMsg) } /* Step I: Check if key exists */ ll, ok := topicMu.Load(topicName) @@ -1038,7 +1036,7 @@ func (rmq *rocksmq) ForceSeek(topicName string, groupName string, msgID UniqueID } lock, ok := ll.(*sync.Mutex) if !ok { - return fmt.Errorf("get mutex failed, topic name = %s", topicName) + return merr.WrapErrMqInternalMsg("get mutex failed, topic name = %s", topicName) } lock.Lock() defer lock.Unlock() @@ -1048,7 +1046,7 @@ func (rmq *rocksmq) ForceSeek(topicName string, groupName string, msgID UniqueID key := constructCurrentID(topicName, groupName) _, ok = rmq.consumersID.Load(key) if !ok { - return fmt.Errorf("ConsumerGroup %s, channel %s not exists", groupName, topicName) + return merr.WrapErrMqInternalMsg("ConsumerGroup %s, channel %s not exists", groupName, topicName) } rmq.consumersID.Store(key, msgID) @@ -1061,7 +1059,7 @@ func (rmq *rocksmq) ForceSeek(topicName string, groupName string, msgID UniqueID // SeekToLatest updates current id to the msg id of latest message + 1 func (rmq *rocksmq) SeekToLatest(topicName, groupName string) error { if rmq.isClosed() { - return errors.New(RmqNotServingErrMsg) + return merr.WrapErrServiceUnavailable(RmqNotServingErrMsg) } rmq.storeMu.Lock() defer rmq.storeMu.Unlock() @@ -1069,7 +1067,7 @@ func (rmq *rocksmq) SeekToLatest(topicName, groupName string) error { key := constructCurrentID(topicName, groupName) _, ok := rmq.consumersID.Load(key) if !ok { - return fmt.Errorf("ConsumerGroup %s, channel %s not exists", groupName, topicName) + return merr.WrapErrMqInternalMsg("ConsumerGroup %s, channel %s not exists", groupName, topicName) } msgID, err := rmq.getLatestMsg(topicName) @@ -1185,7 +1183,7 @@ func (rmq *rocksmq) updateAckedInfo(topicName, groupName string, firstID UniqueI if c.GroupName != groupName { beginID, ok := rmq.getCurrentID(c.Topic, c.GroupName) if !ok { - err = fmt.Errorf("currentID of topicName=%s, groupName=%s not exist", c.Topic, c.GroupName) + err = merr.WrapErrMqInternalMsg("currentID of topicName=%s, groupName=%s not exist", c.Topic, c.GroupName) return false } if beginID < minBeginID { diff --git a/pkg/mq/mqimpl/rocksmq/server/rocksmq_retention.go b/pkg/mq/mqimpl/rocksmq/server/rocksmq_retention.go index f3759db3c1..3e6c5b21b0 100644 --- a/pkg/mq/mqimpl/rocksmq/server/rocksmq_retention.go +++ b/pkg/mq/mqimpl/rocksmq/server/rocksmq_retention.go @@ -13,7 +13,6 @@ package server import ( "context" - "fmt" "path" "strconv" "sync" @@ -24,6 +23,7 @@ import ( rocksdbkv "github.com/milvus-io/milvus/pkg/v3/kv/rocksdb" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -316,11 +316,11 @@ func (ri *retentionInfo) cleanData(topic string, pageEndID UniqueID) error { ll, ok := topicMu.Load(topic) if !ok { - return fmt.Errorf("topic name = %s not exist", topic) + return merr.WrapErrMqTopicNotFound(topic) } lock, ok := ll.(*sync.Mutex) if !ok { - return fmt.Errorf("get mutex failed, topic name = %s", topic) + return merr.WrapErrMqInternalMsg("get mutex failed, topic name = %s", topic) } lock.Lock() defer lock.Unlock() diff --git a/pkg/mq/msgdispatcher/dispatcher.go b/pkg/mq/msgdispatcher/dispatcher.go index 6852fd0513..7d7bc5f4a5 100644 --- a/pkg/mq/msgdispatcher/dispatcher.go +++ b/pkg/mq/msgdispatcher/dispatcher.go @@ -32,6 +32,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/mq/common" "github.com/milvus-io/milvus/pkg/v3/mq/msgstream" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/syncutil" "github.com/milvus-io/milvus/pkg/v3/util/tsoutil" @@ -173,7 +174,7 @@ func (d *Dispatcher) GetTarget(vchannel string) (*target, error) { if t, ok := d.targets.Get(vchannel); ok { return t, nil } - return nil, fmt.Errorf("cannot find target, vchannel=%s", vchannel) + return nil, merr.WrapErrMqInternalMsg("cannot find target, vchannel=%s", vchannel) } func (d *Dispatcher) GetTargets() []*target { diff --git a/pkg/mq/msgdispatcher/manager.go b/pkg/mq/msgdispatcher/manager.go index 3db57a7957..940babcd49 100644 --- a/pkg/mq/msgdispatcher/manager.go +++ b/pkg/mq/msgdispatcher/manager.go @@ -31,6 +31,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/metrics" "github.com/milvus-io/milvus/pkg/v3/mq/msgstream" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/timerecord" "github.com/milvus-io/milvus/pkg/v3/util/tsoutil" @@ -86,7 +87,7 @@ func NewDispatcherManager(pchannel string, role string, nodeID int64, factory ms func (c *dispatcherManager) Add(ctx context.Context, streamConfig *StreamConfig) (<-chan *MsgPack, error) { t := newTarget(streamConfig, c.includeSkipWhenSplit) if _, ok := c.registeredTargets.GetOrInsert(t.vchannel, t); ok { - return nil, fmt.Errorf("vchannel %s already exists in the dispatcher", t.vchannel) + return nil, merr.WrapErrMqInternalMsg("vchannel %s already exists in the dispatcher", t.vchannel) } log.Ctx(ctx).Info("target register done", zap.String("vchannel", t.vchannel)) return t.ch, nil diff --git a/pkg/mq/msgdispatcher/target.go b/pkg/mq/msgdispatcher/target.go index 279efb2295..69e9677724 100644 --- a/pkg/mq/msgdispatcher/target.go +++ b/pkg/mq/msgdispatcher/target.go @@ -17,7 +17,6 @@ package msgdispatcher import ( - "fmt" "sync" "time" @@ -25,6 +24,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/util/lifetime" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -112,7 +112,7 @@ func (t *target) send(pack *MsgPack) error { return nil case <-t.timer.C: t.isLagged = true - return fmt.Errorf("send target timeout, vchannel=%s, timeout=%s, beginTs=%d, endTs=%d, latestTimeTick=%d", t.vchannel, t.maxLag, pack.BeginTs, pack.EndTs, t.latestTimeTick) + return merr.WrapErrMqInternalMsg("send target timeout, vchannel=%s, timeout=%s, beginTs=%d, endTs=%d, latestTimeTick=%d", t.vchannel, t.maxLag, pack.BeginTs, pack.EndTs, t.latestTimeTick) case t.ch <- pack: if len(pack.EndPositions) > 0 { t.latestTimeTick = pack.EndPositions[0].GetTimestamp() diff --git a/pkg/mq/msgstream/mq_factory.go b/pkg/mq/msgstream/mq_factory.go index 5899230e6b..3e22b03ae3 100644 --- a/pkg/mq/msgstream/mq_factory.go +++ b/pkg/mq/msgstream/mq_factory.go @@ -24,7 +24,6 @@ import ( "github.com/apache/pulsar-client-go/pulsar" "github.com/apache/pulsar-client-go/pulsaradmin/pkg/rest" "github.com/apache/pulsar-client-go/pulsaradmin/pkg/utils" - "github.com/cockroachdb/errors" "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" @@ -36,6 +35,7 @@ import ( pulsarmqwrapper "github.com/milvus-io/milvus/pkg/v3/mq/msgstream/mqwrapper/pulsar" "github.com/milvus-io/milvus/pkg/v3/mq/msgstream/mqwrapper/rmq" "github.com/milvus-io/milvus/pkg/v3/streaming/walimpls/impls/pulsar/pulsarlog" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/retry" ) @@ -82,7 +82,7 @@ func (f *PmsFactory) NewMsgStream(ctx context.Context) (MsgStream, error) { if deadline, ok := ctx.Deadline(); ok { if deadline.Before(time.Now()) { - return nil, errors.New("context timeout when NewMsgStream") + return nil, merr.WrapErrServiceUnavailable("context timeout when NewMsgStream") } timeout = time.Until(deadline) } @@ -111,7 +111,7 @@ func (f *PmsFactory) NewTtMsgStream(ctx context.Context) (MsgStream, error) { timeout := f.RequestTimeout if deadline, ok := ctx.Deadline(); ok { if deadline.Before(time.Now()) { - return nil, errors.New("context timeout when NewTtMsgStream") + return nil, merr.WrapErrServiceUnavailable("context timeout when NewTtMsgStream") } timeout = time.Until(deadline) } @@ -141,7 +141,7 @@ func (f *PmsFactory) getAuthentication() (pulsar.Authentication, error) { log.Error("build authencation from config failed, please check it!", zap.String("authPlugin", f.PulsarAuthPlugin), zap.Error(err)) - return nil, errors.New("build authencation from config failed") + return nil, merr.WrapErrParameterInvalidMsg("build authencation from config failed") } return auth, nil } diff --git a/pkg/mq/msgstream/mq_msgstream.go b/pkg/mq/msgstream/mq_msgstream.go index 711e0cc0c1..e09955c147 100644 --- a/pkg/mq/msgstream/mq_msgstream.go +++ b/pkg/mq/msgstream/mq_msgstream.go @@ -116,7 +116,7 @@ func (ms *mqMsgStream) AsProducer(ctx context.Context, channels []string) { return err } if pp == nil { - return errors.New("Producer is nil") + return merr.WrapErrMqInternalMsg("Producer is nil") } ms.producerLock.Lock() @@ -136,8 +136,7 @@ func (ms *mqMsgStream) AsProducer(ctx context.Context, channels []string) { func (ms *mqMsgStream) GetLatestMsgID(channel string) (MessageID, error) { lastMsg, err := ms.consumers[channel].GetLatestMsgID() if err != nil { - errMsg := "Failed to get latest MsgID from channel: " + channel + ", error = " + err.Error() - return nil, errors.New(errMsg) + return nil, merr.WrapErrMqInternal(err, "Failed to get latest MsgID from channel: "+channel) } return lastMsg, nil } @@ -168,7 +167,7 @@ func (ms *mqMsgStream) AsConsumer(ctx context.Context, channels []string, subNam return err } if pc == nil { - return errors.New("Consumer is nil") + return merr.WrapErrMqInternalMsg("Consumer is nil") } ms.consumerLock.Lock() @@ -253,7 +252,7 @@ func (ms *mqMsgStream) Produce(ctx context.Context, msgPack *MsgPack) error { return nil } if len(ms.producers) <= 0 { - return errors.New("nil producer in msg stream") + return merr.WrapErrServiceInternal("nil producer in msg stream") } tsMsgs := msgPack.Msgs reBucketValues := ms.ComputeProduceChannelIndexes(msgPack.Msgs) @@ -285,7 +284,7 @@ func (ms *mqMsgStream) Produce(ctx context.Context, msgPack *MsgPack) error { producer, ok := ms.producers[channel] ms.producerLock.RUnlock() if !ok { - return errors.New("producer not found for channel: " + channel) + return merr.WrapErrMqInternalMsg("producer not found for channel: %s", channel) } for i := 0; i < len(v.Msgs); i++ { @@ -321,7 +320,7 @@ func (ms *mqMsgStream) Produce(ctx context.Context, msgPack *MsgPack) error { func (ms *mqMsgStream) Broadcast(ctx context.Context, msgPack *MsgPack) (map[string][]MessageID, error) { ids := make(map[string][]MessageID) if msgPack == nil || len(msgPack.Msgs) <= 0 { - return ids, errors.New("empty msgs") + return ids, merr.WrapErrParameterInvalidMsg("empty msgs") } for _, v := range msgPack.Msgs { @@ -367,7 +366,7 @@ func GetTsMsgFromConsumerMsg(unmarshalDispatcher UnmarshalDispatcher, msg common } tsMsg, err := unmarshalDispatcher.Unmarshal(msg.Payload(), msgType) if err != nil { - return nil, fmt.Errorf("failed to unmarshal tsMsg, err %s", err.Error()) + return nil, merr.WrapErrDataIntegrity(err, "failed to unmarshal tsMsg") } tsMsg.SetPosition(&MsgPosition{ @@ -460,7 +459,7 @@ func (ms *mqMsgStream) Seek(ctx context.Context, msgPositions []*MsgPosition, in for _, mp := range msgPositions { consumer, ok := ms.consumers[mp.ChannelName] if !ok { - return fmt.Errorf("channel %s not subscribed", mp.ChannelName) + return merr.WrapErrMqInternalMsg("channel %s not subscribed", mp.ChannelName) } messageID, err := ms.client.BytesToMsgID(mp.MsgID) if err != nil { @@ -566,7 +565,7 @@ func (ms *MqTtMsgStream) AsConsumer(ctx context.Context, channels []string, subN return err } if pc == nil { - return errors.New("Consumer is nil") + return merr.WrapErrMqInternalMsg("Consumer is nil") } ms.consumerLock.Lock() @@ -858,11 +857,11 @@ func (ms *MqTtMsgStream) Seek(ctx context.Context, msgPositions []*MsgPosition, var ok bool consumer, ok = ms.consumers[mp.ChannelName] if !ok { - return false, fmt.Errorf("please subcribe the channel, channel name =%s", mp.ChannelName) + return false, merr.WrapErrMqInternalMsg("please subcribe the channel, channel name =%s", mp.ChannelName) } if consumer == nil { - return false, errors.New("consumer is nil") + return false, merr.WrapErrMqInternalMsg("consumer is nil") } seekMsgID, err := ms.client.BytesToMsgID(mp.MsgID) @@ -903,12 +902,12 @@ func (ms *MqTtMsgStream) Seek(ctx context.Context, msgPositions []*MsgPosition, for idx := range msgPositions { mp = msgPositions[idx] if len(mp.MsgID) == 0 { - return errors.New("when msgID's length equal to 0, please use AsConsumer interface") + return merr.WrapErrParameterInvalidMsg("when msgID's length equal to 0, please use AsConsumer interface") } err = retry.Handle(ctx, fn, retry.Attempts(20), retry.Sleep(time.Millisecond*200), retry.MaxSleepTime(5*time.Second)) // err = retry.Do(ctx, fn, retry.Attempts(20), retry.Sleep(time.Millisecond*200), retry.MaxSleepTime(5*time.Second)) if err != nil { - return fmt.Errorf("failed to seek, error %s", err.Error()) + return merr.WrapErrMqInternal(err, "failed to seek") } ms.addConsumer(consumer, mp.ChannelName) ms.chanMsgPos[consumer] = (proto.Clone(mp)).(*MsgPosition) @@ -927,7 +926,7 @@ func (ms *MqTtMsgStream) Seek(ctx context.Context, msgPositions []*MsgPosition, log.Info("seek loop tick", zap.Int("loopMsgCnt", loopMsgCnt), zap.String("channel", mp.ChannelName)) case msg, ok := <-consumer.Chan(): if !ok { - return errors.New("consumer closed") + return merr.WrapErrServiceUnavailable("consumer closed") } loopMsgCnt++ consumer.Ack(msg) diff --git a/pkg/mq/msgstream/mqwrapper/kafka/kafka_client.go b/pkg/mq/msgstream/mqwrapper/kafka/kafka_client.go index 1341a70051..88b6ba0800 100644 --- a/pkg/mq/msgstream/mqwrapper/kafka/kafka_client.go +++ b/pkg/mq/msgstream/mqwrapper/kafka/kafka_client.go @@ -7,7 +7,6 @@ import ( "sync" "time" - "github.com/cockroachdb/errors" "github.com/confluentinc/confluent-kafka-go/kafka" "go.uber.org/atomic" "go.uber.org/zap" @@ -17,6 +16,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/mq/common" "github.com/milvus-io/milvus/pkg/v3/mq/msgstream/mqwrapper" "github.com/milvus-io/milvus/pkg/v3/util/conc" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/timerecord" ) @@ -112,7 +112,7 @@ func NewKafkaClientInstanceWithConfig(ctx context.Context, config *paramtable.Ka // connection setup timeout, default as 30000ms, available range is [1000, 2147483647] if deadline, ok := ctx.Deadline(); ok { if deadline.Before(time.Now()) { - return nil, errors.New("context timeout when new kafka client") + return nil, merr.WrapErrServiceUnavailable("context timeout when new kafka client") } // timeout := time.Until(deadline).Milliseconds() // kafkaConfig.SetKey("socket.connection.setup.timeout.ms", strconv.FormatInt(timeout, 10)) diff --git a/pkg/mq/msgstream/mqwrapper/kafka/kafka_consumer.go b/pkg/mq/msgstream/mqwrapper/kafka/kafka_consumer.go index aef9a2eae1..687f0d34ae 100644 --- a/pkg/mq/msgstream/mqwrapper/kafka/kafka_consumer.go +++ b/pkg/mq/msgstream/mqwrapper/kafka/kafka_consumer.go @@ -4,7 +4,6 @@ import ( "sync" "time" - "github.com/cockroachdb/errors" "github.com/confluentinc/confluent-kafka-go/kafka" "go.uber.org/zap" @@ -158,7 +157,7 @@ func (kc *Consumer) Chan() <-chan common.Message { func (kc *Consumer) Seek(id common.MessageID, inclusive bool) error { if kc.hasAssign { - return errors.New("kafka consumer is already assigned, can not seek again") + return merr.WrapErrMqInternalMsg("kafka consumer is already assigned, can not seek again") } offset := kafka.Offset(id.(*KafkaID).MessageID) diff --git a/pkg/mq/msgstream/mqwrapper/kafka/kafka_producer.go b/pkg/mq/msgstream/mqwrapper/kafka/kafka_producer.go index 7c65f2ca2e..89943e24a1 100644 --- a/pkg/mq/msgstream/mqwrapper/kafka/kafka_producer.go +++ b/pkg/mq/msgstream/mqwrapper/kafka/kafka_producer.go @@ -5,7 +5,6 @@ import ( "sync" "time" - "github.com/cockroachdb/errors" "github.com/confluentinc/confluent-kafka-go/kafka" "go.uber.org/zap" @@ -36,7 +35,7 @@ func (kp *kafkaProducer) Send(ctx context.Context, message *mqcommon.ProducerMes if kp.isClosed { metrics.MsgStreamOpCounter.WithLabelValues(metrics.SendMsgLabel, metrics.FailLabel).Inc() log.Error("kafka produce message fail because the producer has been closed", zap.String("topic", kp.topic)) - return nil, common.NewIgnorableError(errors.New("kafka producer is closed")) + return nil, common.NewIgnorableErrorf("kafka producer is closed") } headers := make([]kafka.Header, 0, len(message.Properties)) @@ -61,7 +60,7 @@ func (kp *kafkaProducer) Send(ctx context.Context, message *mqcommon.ProducerMes case <-kp.stopCh: metrics.MsgStreamOpCounter.WithLabelValues(metrics.SendMsgLabel, metrics.FailLabel).Inc() log.Error("kafka produce message fail because of kafka producer is closed", zap.String("topic", kp.topic)) - return nil, common.NewIgnorableError(errors.New("kafka producer is closed")) + return nil, common.NewIgnorableErrorf("kafka producer is closed") case e := <-resultCh: m = e.(*kafka.Message) } diff --git a/pkg/mq/msgstream/mqwrapper/pulsar/pulsar_client.go b/pkg/mq/msgstream/mqwrapper/pulsar/pulsar_client.go index 59e8a8ca05..2e832d5f50 100644 --- a/pkg/mq/msgstream/mqwrapper/pulsar/pulsar_client.go +++ b/pkg/mq/msgstream/mqwrapper/pulsar/pulsar_client.go @@ -25,13 +25,13 @@ import ( "github.com/apache/pulsar-client-go/pulsar" "github.com/apache/pulsar-client-go/pulsaradmin/pkg/admin" "github.com/apache/pulsar-client-go/pulsaradmin/pkg/admin/config" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/metrics" mqcommon "github.com/milvus-io/milvus/pkg/v3/mq/common" "github.com/milvus-io/milvus/pkg/v3/mq/msgstream/mqwrapper" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/timerecord" ) @@ -93,7 +93,7 @@ func (pc *pulsarClient) CreateProducer(ctx context.Context, options mqcommon.Pro } if pp == nil { metrics.MsgStreamOpCounter.WithLabelValues(metrics.CreateProducerLabel, metrics.FailLabel).Inc() - return nil, errors.New("pulsar is not ready, producer is nil") + return nil, merr.WrapErrServiceUnavailable("pulsar is not ready, producer is nil") } elapsed := start.ElapseSpan() metrics.MsgStreamRequestLatency.WithLabelValues(metrics.CreateProducerLabel).Observe(float64(elapsed.Milliseconds())) @@ -144,7 +144,7 @@ func GetFullTopicName(tenant string, namespace string, topic string) (string, er zap.String("tenant", tenant), zap.String("namesapce", namespace), zap.String("topic", topic)) - return "", errors.New("build full topic name failed") + return "", merr.WrapErrMqInternalMsg("build full topic name failed") } return fmt.Sprintf("%s/%s/%s", tenant, namespace, topic), nil @@ -158,7 +158,7 @@ func NewAdminClient(address, authPlugin, authParams string) (admin.Client, error } adminClient, err := admin.New(&cfg) if err != nil { - return nil, fmt.Errorf("failed to build pulsar admin client due to %s", err.Error()) + return nil, merr.WrapErrMqInternal(err, "failed to build pulsar admin client") } return adminClient, nil diff --git a/pkg/mq/msgstream/mqwrapper/rmq/rmq_client.go b/pkg/mq/msgstream/mqwrapper/rmq/rmq_client.go index b440b29c9d..2bfa19328c 100644 --- a/pkg/mq/msgstream/mqwrapper/rmq/rmq_client.go +++ b/pkg/mq/msgstream/mqwrapper/rmq/rmq_client.go @@ -20,7 +20,6 @@ import ( "context" "strconv" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus/pkg/v3/log" @@ -29,6 +28,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/mq/mqimpl/rocksmq/client" "github.com/milvus-io/milvus/pkg/v3/mq/mqimpl/rocksmq/server" "github.com/milvus-io/milvus/pkg/v3/mq/msgstream/mqwrapper" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/timerecord" ) @@ -83,7 +83,7 @@ func (rc *rmqClient) Subscribe(ctx context.Context, options mqwrapper.ConsumerOp if options.BufSize == 0 { metrics.MsgStreamOpCounter.WithLabelValues(metrics.CreateConsumerLabel, metrics.FailLabel).Inc() - err := errors.New("subscription bufSize of rmq should never be zero") + err := merr.WrapErrParameterInvalidMsg("subscription bufSize of rmq should never be zero") log.Warn("unexpected subscription consumer options", zap.Error(err)) return nil, err } diff --git a/pkg/mq/msgstream/msg.go b/pkg/mq/msgstream/msg.go index 386767a2cb..f6c75d97f5 100644 --- a/pkg/mq/msgstream/msg.go +++ b/pkg/mq/msgstream/msg.go @@ -18,10 +18,8 @@ package msgstream import ( "context" - "fmt" "sync" - "github.com/cockroachdb/errors" "google.golang.org/protobuf/proto" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" @@ -29,6 +27,7 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/pkg/v3/util/commonpbutil" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -134,7 +133,7 @@ func convertToByteArray(input interface{}) ([]byte, error) { case []byte: return output, nil default: - return nil, errors.New("Cannot convert interface{} to []byte") + return nil, merr.WrapErrParameterInvalidMsg("Cannot convert interface{} to []byte") } } @@ -234,7 +233,7 @@ func (it *InsertMsg) NRows() uint64 { func (it *InsertMsg) CheckAligned() error { numRowsOfFieldDataMismatch := func(fieldName string, fieldNumRows, passedNumRows uint64) error { - return fmt.Errorf("the num_rows(%d) of %sth field is not equal to passed NumRows(%d)", fieldNumRows, fieldName, passedNumRows) + return merr.WrapErrDataIntegrityMsg("the num_rows(%d) of %sth field is not equal to passed NumRows(%d)", fieldNumRows, fieldName, passedNumRows) } rowNums := it.NRows() if it.IsColumnBased() { @@ -250,11 +249,11 @@ func (it *InsertMsg) CheckAligned() error { } if len(it.GetRowIDs()) != len(it.GetTimestamps()) { - return fmt.Errorf("the num_rows(%d) of rowIDs is not equal to the num_rows(%d) of timestamps", len(it.GetRowIDs()), len(it.GetTimestamps())) + return merr.WrapErrDataIntegrityMsg("the num_rows(%d) of rowIDs is not equal to the num_rows(%d) of timestamps", len(it.GetRowIDs()), len(it.GetTimestamps())) } if uint64(len(it.GetRowIDs())) != it.NRows() { - return fmt.Errorf("the num_rows(%d) of rowIDs is not equal to passed NumRows(%d)", len(it.GetRowIDs()), it.NRows()) + return merr.WrapErrDataIntegrityMsg("the num_rows(%d) of rowIDs is not equal to passed NumRows(%d)", len(it.GetRowIDs()), it.NRows()) } return nil @@ -431,12 +430,12 @@ func (dt *DeleteMsg) CheckAligned() error { numRows := dt.GetNumRows() if numRows != int64(len(dt.GetTimestamps())) { - return fmt.Errorf("the num_rows(%d) of pks is not equal to the num_rows(%d) of timestamps", numRows, len(dt.GetTimestamps())) + return merr.WrapErrDataIntegrityMsg("the num_rows(%d) of pks is not equal to the num_rows(%d) of timestamps", numRows, len(dt.GetTimestamps())) } numPks := int64(typeutil.GetSizeOfIDs(dt.PrimaryKeys)) if numRows != numPks { - return fmt.Errorf("the num_rows(%d) of pks is not equal to passed NumRows(%d)", numPks, numRows) + return merr.WrapErrDataIntegrityMsg("the num_rows(%d) of pks is not equal to passed NumRows(%d)", numPks, numRows) } return nil diff --git a/pkg/mq/msgstream/msgstream.go b/pkg/mq/msgstream/msgstream.go index c7c0b42298..9b67860d89 100644 --- a/pkg/mq/msgstream/msgstream.go +++ b/pkg/mq/msgstream/msgstream.go @@ -22,13 +22,13 @@ import ( "strconv" "sync" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus-proto/go-api/v3/msgpb" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/mq/common" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -210,39 +210,39 @@ func NewMarshaledMsg(msg common.Message, group string) (ConsumeMsg, error) { properties := msg.Properties() vchannel, ok := properties[common.ChannelTypeKey] if !ok { - return nil, errors.New("get channel name from msg properties failed") + return nil, merr.WrapErrDataIntegrityMsg("get channel name from msg properties failed") } collID, ok := properties[common.CollectionIDTypeKey] if !ok { - return nil, errors.New("get collection ID from msg properties failed") + return nil, merr.WrapErrDataIntegrityMsg("get collection ID from msg properties failed") } tsStr, ok := properties[common.TimestampTypeKey] if !ok { - return nil, errors.New("get minTs from msg properties failed") + return nil, merr.WrapErrDataIntegrityMsg("get minTs from msg properties failed") } timestamp, err := strconv.ParseUint(tsStr, 10, 64) if err != nil { log.Warn("parse message properties minTs failed, unknown message", zap.Error(err)) - return nil, errors.New("parse minTs from msg properties failed") + return nil, merr.WrapErrDataIntegrityMsg("parse minTs from msg properties failed") } idStr, ok := properties[common.MsgIdTypeKey] if !ok { - return nil, errors.New("get msgID from msg properties failed") + return nil, merr.WrapErrDataIntegrityMsg("get msgID from msg properties failed") } id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { log.Warn("parse message properties minTs failed, unknown message", zap.Error(err)) - return nil, errors.New("parse minTs from msg properties failed") + return nil, merr.WrapErrDataIntegrityMsg("parse minTs from msg properties failed") } val, ok := properties[common.MsgTypeKey] if !ok { - return nil, errors.New("get msgType from msg properties failed") + return nil, merr.WrapErrDataIntegrityMsg("get msgType from msg properties failed") } msgType := commonpb.MsgType(commonpb.MsgType_value[val]) diff --git a/pkg/mq/msgstream/repack_func.go b/pkg/mq/msgstream/repack_func.go index 9e2132a035..20ffb5c47e 100644 --- a/pkg/mq/msgstream/repack_func.go +++ b/pkg/mq/msgstream/repack_func.go @@ -17,11 +17,8 @@ package msgstream import ( - "fmt" - - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // InsertRepackFunc is used to repack messages after hash by primary key @@ -29,7 +26,7 @@ func InsertRepackFunc(tsMsgs []TsMsg, hashKeys [][]int32) (map[int32]*MsgPack, e result := make(map[int32]*MsgPack) for i, request := range tsMsgs { if request.Type() != commonpb.MsgType_Insert { - return nil, errors.New("msg's must be Insert") + return nil, merr.WrapErrParameterInvalidMsg("msgs must be Insert") } insertRequest := request.(*InsertMsg) keys := hashKeys[i] @@ -40,7 +37,7 @@ func InsertRepackFunc(tsMsgs []TsMsg, hashKeys [][]int32) (map[int32]*MsgPack, e return nil, err } if insertRequest.NRows() != uint64(keysLen) { - return nil, errors.New("the length of hashValue, timestamps, rowIDs, RowData are not equal") + return nil, merr.WrapErrParameterInvalidMsg("the length of hashValue, timestamps, rowIDs, RowData are not equal") } for index, key := range keys { _, ok := result[key] @@ -61,7 +58,7 @@ func DeleteRepackFunc(tsMsgs []TsMsg, hashKeys [][]int32) (map[int32]*MsgPack, e result := make(map[int32]*MsgPack) for i, request := range tsMsgs { if request.Type() != commonpb.MsgType_Delete { - return nil, errors.New("msg's must be Delete") + return nil, merr.WrapErrParameterInvalidMsg("msgs must be Delete") } deleteRequest := request.(*DeleteMsg) keys := hashKeys[i] @@ -70,7 +67,7 @@ func DeleteRepackFunc(tsMsgs []TsMsg, hashKeys [][]int32) (map[int32]*MsgPack, e keysLen := len(keys) if keysLen != timestampLen || int64(keysLen) != deleteRequest.NumRows { - return nil, errors.New("the length of hashValue, timestamps, primaryKeys are not equal") + return nil, merr.WrapErrParameterInvalidMsg("the length of hashValue, timestamps, primaryKeys are not equal") } key := keys[0] @@ -86,7 +83,7 @@ func DeleteRepackFunc(tsMsgs []TsMsg, hashKeys [][]int32) (map[int32]*MsgPack, e // DefaultRepackFunc is used to repack messages after hash by primary key func DefaultRepackFunc(tsMsgs []TsMsg, hashKeys [][]int32) (map[int32]*MsgPack, error) { if len(hashKeys) < len(tsMsgs) { - return nil, fmt.Errorf( + return nil, merr.WrapErrParameterInvalidMsg( "the length of hash keys (%d) is less than the length of messages (%d)", len(hashKeys), len(tsMsgs), @@ -97,7 +94,7 @@ func DefaultRepackFunc(tsMsgs []TsMsg, hashKeys [][]int32) (map[int32]*MsgPack, pack := make(map[int32]*MsgPack) for idx, msg := range tsMsgs { if len(hashKeys[idx]) <= 0 { - return nil, fmt.Errorf("no hash key for %dth message", idx) + return nil, merr.WrapErrParameterInvalidMsg("no hash key for %dth message", idx) } key := hashKeys[idx][0] _, ok := pack[key] diff --git a/pkg/mq/msgstream/unmarshal.go b/pkg/mq/msgstream/unmarshal.go index b10f8910d8..3a0496f8b3 100644 --- a/pkg/mq/msgstream/unmarshal.go +++ b/pkg/mq/msgstream/unmarshal.go @@ -17,9 +17,8 @@ package msgstream import ( - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // UnmarshalFunc is an interface that has been implemented by each Msg @@ -44,7 +43,7 @@ type ProtoUnmarshalDispatcher struct { func (p *ProtoUnmarshalDispatcher) Unmarshal(input interface{}, msgType commonpb.MsgType) (TsMsg, error) { unmarshalFunc, ok := p.TempMap[msgType] if !ok { - return nil, errors.New("not set unmarshalFunc for this messageType") + return nil, merr.WrapErrMqInternalMsg("not set unmarshalFunc for this messageType") } return unmarshalFunc(input) } diff --git a/pkg/objectstorage/huawei/huawei.go b/pkg/objectstorage/huawei/huawei.go index 326b8a0fef..69eb9ec815 100644 --- a/pkg/objectstorage/huawei/huawei.go +++ b/pkg/objectstorage/huawei/huawei.go @@ -36,6 +36,7 @@ import ( "go.uber.org/zap" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) const ( @@ -186,7 +187,7 @@ func (p *HuaweiCredentialProvider) Retrieve() (minioCred.Value, error) { return p.credentials, nil } log.Warn("HuaweiCloud credential provider: in cooldown after failure, no valid cached credentials available") - return minioCred.Value{}, errors.New("STS refresh in cooldown, no valid cached credentials available") + return minioCred.Value{}, merr.WrapErrServiceInternalMsg("STS refresh in cooldown, no valid cached credentials available") } durationSeconds := int32(2 * 60 * 60) // 2 hours, matching C++ layer's duration @@ -238,7 +239,7 @@ func (p *HuaweiCredentialProvider) Retrieve() (minioCred.Value, error) { log.Warn("HuaweiCloud credential provider: STS returned nil or incomplete credentials", zap.Int64("sts_success", p.stsSuccessCount.Load()), zap.Int64("sts_failure", p.stsFailureCount.Load())) - return minioCred.Value{}, errors.New("incomplete credential returned from Huawei Cloud (missing ak/sk/token)") + return minioCred.Value{}, merr.WrapErrServiceInternalMsg("incomplete credential returned from Huawei Cloud (missing ak/sk/token)") } expiration, err := time.Parse(time.RFC3339, response.Credential.ExpiresAt) diff --git a/pkg/objectstorage/util.go b/pkg/objectstorage/util.go index b9cef8ee1f..24a6f9d706 100644 --- a/pkg/objectstorage/util.go +++ b/pkg/objectstorage/util.go @@ -169,7 +169,7 @@ func NewMinioClient(ctx context.Context, c *Config) (*minio.Client, error) { return err } } else { - return fmt.Errorf("bucket %s not Existed", c.BucketName) + return merr.WrapErrParameterInvalidMsg("bucket %s not Existed", c.BucketName) } } return nil @@ -390,16 +390,16 @@ func newTLSHTTPClient(minVersion string) (*http.Client, error) { func getProjectId(gcpCredentialJSON string) (string, error) { if gcpCredentialJSON == "" { - return "", errors.New("the JSON string is empty") + return "", merr.WrapErrParameterInvalidMsg("the JSON string is empty") } var data map[string]interface{} if err := json.Unmarshal([]byte(gcpCredentialJSON), &data); err != nil { - return "", errors.New("failed to parse Google Cloud credentials as JSON") + return "", merr.WrapErrParameterInvalidMsg("failed to parse Google Cloud credentials as JSON") } propertyValue, ok := data["project_id"] projectId := fmt.Sprintf("%v", propertyValue) if !ok { - return "", errors.New("projectId doesn't exist") + return "", merr.WrapErrParameterInvalidMsg("projectId doesn't exist") } return projectId, nil } diff --git a/pkg/rules.go b/pkg/rules.go index 5bc3422c9b..0c56dbe66f 100644 --- a/pkg/rules.go +++ b/pkg/rules.go @@ -407,3 +407,61 @@ func badlock(m dsl.Matcher) { m.Match(`$mu.Lock(); defer $mu.RUnlock()`).Report(`maybe $mu.RLock() was intended?`) m.Match(`$mu.RLock(); defer $mu.Unlock()`).Report(`maybe $mu.Lock() was intended?`) } + +// rawmerrerror forbids returning a raw error from a function body — originate +// through merr (merr.WrapErr*/merr.Wrap) instead. Mirrors the core module rule. +func rawmerrerror(m dsl.Matcher) { + // fmt.Errorf resolves unambiguously to the stdlib, so the literal selector matches. + m.Match( + `return fmt.Errorf($*_)`, + `return $*_, fmt.Errorf($*_)`, + `return fmt.Errorf($*_), $*_`, + ). + Where(!m.File().Name.Matches(`_test\.go$`) && + !m.File().Name.Matches(`test_streaming\.go$`) && + !m.File().PkgPath.Matches(`/milvus/cmd/`) && + !m.File().PkgPath.Matches(`/milvus/tests/`) && + !m.File().PkgPath.Matches(`codegen`) && + !m.File().PkgPath.Matches(`walimplstest`) && + !m.File().PkgPath.Matches(`/mocks/`)). + Report(`raw error returned from function body; originate through the merr framework (merr.WrapErr*/merr.Wrap) instead of fmt.Errorf/errors.New/errors.Errorf`) + + // errors.New/Newf/Errorf: the repo uses github.com/cockroachdb/errors, not the + // stdlib, so a literal `errors.New` selector — which ruleguard type-resolves to + // the stdlib package — never matches. Match by the function name instead, limited + // to the raw constructors; errors.Wrap/Wrapf/Is/As/Cause are intentionally allowed. + m.Match( + `return errors.$fn($*_)`, + `return $*_, errors.$fn($*_)`, + `return errors.$fn($*_), $*_`, + ). + Where(m["fn"].Text.Matches(`^(New|Newf|Errorf)$`) && + !m.File().Name.Matches(`_test\.go$`) && + !m.File().Name.Matches(`test_streaming\.go$`) && + !m.File().PkgPath.Matches(`/milvus/cmd/`) && + !m.File().PkgPath.Matches(`/milvus/tests/`) && + !m.File().PkgPath.Matches(`codegen`) && + !m.File().PkgPath.Matches(`walimplstest`) && + !m.File().PkgPath.Matches(`/mocks/`)). + Report(`raw error returned from function body; originate through the merr framework (merr.WrapErr*/merr.Wrap) instead of fmt.Errorf/errors.New/errors.Errorf`) +} + +// merrsentinel forbids parking merr-typed errors in variables that look like +// sentinels. milvusError.Is compares error codes only, so errors.Is against a +// merr-typed "sentinel" silently matches ANY error sharing the code — a guard +// like errors.Is(err, ErrIgnoredFoo) then treats unrelated internal failures +// as the special case (real incident: stale-meta / already-loaded guards +// acknowledged etcd write failures as success). Identity sentinels must stay +// plain errors.New; merr errors must be created at the return site. +func merrsentinel(m dsl.Matcher) { + m.Match( + `var $x = merr.$f($*_)`, + `var $x = merr.$v`, + ). + Where(!m.File().Name.Matches(`_test\.go$`) && + !m.File().PkgPath.Matches(`/milvus/cmd/`) && + !m.File().PkgPath.Matches(`/milvus/tests/`) && + !m.File().PkgPath.Matches(`/mocks/`) && + !m.File().PkgPath.Matches(`util/merr`)). + Report(`don't store a merr error in a sentinel-like variable: errors.Is on merr compares codes, not identity, so any same-code error would match. Use errors.New for identity sentinels, or create the merr error at the return site`) +} diff --git a/pkg/streaming/util/message/adaptor/broadcast_message.go b/pkg/streaming/util/message/adaptor/broadcast_message.go index f67fffc0f3..53a5eef99d 100644 --- a/pkg/streaming/util/message/adaptor/broadcast_message.go +++ b/pkg/streaming/util/message/adaptor/broadcast_message.go @@ -5,11 +5,12 @@ import ( "github.com/milvus-io/milvus/pkg/v3/mq/msgstream" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) func NewMsgPackFromMutableMessageV1(msg message.MutableMessage) (msgstream.TsMsg, error) { if msg.Version() != message.VersionV1 { - return nil, errors.New("Invalid message version") + return nil, merr.WrapErrParameterInvalidMsg("Invalid message version") } tsMsg, err := UnmashalerDispatcher.Unmarshal(msg.Payload(), MustGetCommonpbMsgTypeFromMessageType(msg.MessageType())) diff --git a/pkg/streaming/util/message/adaptor/message_id.go b/pkg/streaming/util/message/adaptor/message_id.go index 44bdd69c95..4cc96690af 100644 --- a/pkg/streaming/util/message/adaptor/message_id.go +++ b/pkg/streaming/util/message/adaptor/message_id.go @@ -19,6 +19,7 @@ import ( msgpulsar "github.com/milvus-io/milvus/pkg/v3/streaming/walimpls/impls/pulsar" "github.com/milvus-io/milvus/pkg/v3/streaming/walimpls/impls/rmq" msgwoodpecker "github.com/milvus-io/milvus/pkg/v3/streaming/walimpls/impls/wp" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // MustGetMQWrapperIDFromMessage converts message.MessageID to common.MessageID @@ -88,7 +89,7 @@ func DeserializeToMQWrapperID(msgID []byte, walName string) (common.MessageID, e } return mqwoodpecker.NewWoodpeckerID(wID), nil default: - return nil, fmt.Errorf("unsupported mq type %s", walName) + return nil, merr.WrapErrParameterInvalidMsg("unsupported mq type %s", walName) } } diff --git a/pkg/streaming/util/message/builder.go b/pkg/streaming/util/message/builder.go index 435f76a0f4..c101237cdc 100644 --- a/pkg/streaming/util/message/builder.go +++ b/pkg/streaming/util/message/builder.go @@ -12,6 +12,7 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus/pkg/v3/proto/messagespb" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/tsoutil" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -69,7 +70,7 @@ func NewReplicateMessage(clustrID string, im *commonpb.ImmutableMessage) (Replic messageID := MustUnmarshalMessageID(im.GetId()) msg := NewImmutableMesasge(messageID, im.GetPayload(), im.GetProperties()).(*immutableMessageImpl) if msg.ReplicateHeader() != nil { - return nil, errors.New("message is already a replicate message") + return nil, merr.WrapErrParameterInvalidMsg("message is already a replicate message") } m := &messageImpl{ diff --git a/pkg/streaming/util/message/specialized_message.go b/pkg/streaming/util/message/specialized_message.go index 64b7b74252..635fe75848 100644 --- a/pkg/streaming/util/message/specialized_message.go +++ b/pkg/streaming/util/message/specialized_message.go @@ -8,6 +8,7 @@ import ( "google.golang.org/protobuf/proto" "github.com/milvus-io/milvus/pkg/v3/proto/messagespb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // mustAsSpecializedMutableMessage converts a MutableMessage to a specialized MutableMessage. @@ -39,13 +40,13 @@ func asSpecializedMutableMessage[H proto.Message, B proto.Message](msg BasicMess msgType := MustGetMessageTypeWithVersion[H, B]() if underlying.MessageType() != msgType.MessageType { // The message type do not match the specialized header. - return nil, errors.New("message type do not match specialized header") + return nil, merr.WrapErrParameterInvalidMsg("message type do not match specialized header") } // Get the specialized header from the message. val, ok := underlying.properties.Get(messageHeader) if !ok { - return nil, errors.Errorf("lost specialized header, %s", msgType.String()) + return nil, merr.WrapErrServiceInternalMsg("lost specialized header, %s", msgType.String()) } // Decode the specialized header. @@ -92,20 +93,20 @@ func asSpecializedImmutableMessage[H proto.Message, B proto.Message](msg Immutab underlying, ok := msg.(*immutableMessageImpl) if !ok { // maybe a txn message. - return nil, errors.New("not a specialized immutable message, txn message maybe") + return nil, merr.WrapErrParameterInvalidMsg("not a specialized immutable message, txn message maybe") } var header H msgType := MustGetMessageTypeWithVersion[H, B]() if underlying.MessageType() != msgType.MessageType { // The message type do not match the specialized header. - return nil, errors.New("message type do not match specialized header") + return nil, merr.WrapErrParameterInvalidMsg("message type do not match specialized header") } // Get the specialized header from the message. val, ok := underlying.properties.Get(messageHeader) if !ok { - return nil, errors.Errorf("lost specialized header, %s", msgType.String()) + return nil, merr.WrapErrServiceInternalMsg("lost specialized header, %s", msgType.String()) } // Decode the specialized header. diff --git a/pkg/streaming/walimpls/impls/pulsar/builder.go b/pkg/streaming/walimpls/impls/pulsar/builder.go index 6935b35c4a..5c943ae44c 100644 --- a/pkg/streaming/walimpls/impls/pulsar/builder.go +++ b/pkg/streaming/walimpls/impls/pulsar/builder.go @@ -11,6 +11,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/streaming/walimpls" "github.com/milvus-io/milvus/pkg/v3/streaming/walimpls/impls/pulsar/pulsarlog" "github.com/milvus-io/milvus/pkg/v3/streaming/walimpls/registry" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -50,7 +51,7 @@ func (b *builderImpl) getPulsarClientOptions() (pulsar.ClientOptions, tenant, er cfg := ¶mtable.Get().PulsarCfg auth, err := pulsar.NewAuthentication(cfg.AuthPlugin.GetValue(), cfg.AuthParams.GetValue()) if err != nil { - return pulsar.ClientOptions{}, tenant{}, errors.New("build authencation from config failed") + return pulsar.ClientOptions{}, tenant{}, merr.WrapErrParameterInvalidMsg("build authencation from config failed") } options := pulsar.ClientOptions{ URL: cfg.Address.GetValue(), diff --git a/pkg/streaming/walimpls/impls/rmq/scanner.go b/pkg/streaming/walimpls/impls/rmq/scanner.go index 9311dd60f3..9615466ad9 100644 --- a/pkg/streaming/walimpls/impls/rmq/scanner.go +++ b/pkg/streaming/walimpls/impls/rmq/scanner.go @@ -1,13 +1,12 @@ package rmq import ( - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus/pkg/v3/mq/mqimpl/rocksmq/client" "github.com/milvus-io/milvus/pkg/v3/mq/mqimpl/rocksmq/server" "github.com/milvus-io/milvus/pkg/v3/streaming/util/message" "github.com/milvus-io/milvus/pkg/v3/streaming/walimpls" "github.com/milvus-io/milvus/pkg/v3/streaming/walimpls/helper" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) var _ walimpls.ScannerImpls = (*scannerImpl)(nil) @@ -62,7 +61,7 @@ func (s *scannerImpl) executeConsume() (err error) { return nil case msg, ok := <-s.consumer.Chan(): if !ok { - return errors.New("mq consumer unexpected channel closed") + return merr.WrapErrServiceUnavailable("mq consumer unexpected channel closed") } msgID := rmqID(msg.ID().(*server.RmqID).MessageID) // record the last message id to avoid repeated consume message. diff --git a/pkg/streaming/walimpls/opener.go b/pkg/streaming/walimpls/opener.go index c6b8f93b7a..a3283a7059 100644 --- a/pkg/streaming/walimpls/opener.go +++ b/pkg/streaming/walimpls/opener.go @@ -3,9 +3,8 @@ package walimpls import ( "context" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus/pkg/v3/streaming/util/types" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // OpenOption is the option for allocating wal impls instance. @@ -16,13 +15,13 @@ type OpenOption struct { // Validate validates the OpenOption. func (oo OpenOption) Validate() error { if oo.Channel.Name == "" { - return errors.New("channel name is empty") + return merr.WrapErrParameterInvalidMsg("channel name is empty") } if oo.Channel.Term < 0 { - return errors.New("channel term is negative") + return merr.WrapErrParameterInvalidMsg("channel term is negative") } if oo.Channel.AccessMode != types.AccessModeRO && oo.Channel.AccessMode != types.AccessModeRW { - return errors.New("undefined access mode") + return merr.WrapErrParameterInvalidMsg("undefined access mode") } return nil } diff --git a/pkg/taskcommon/properties.go b/pkg/taskcommon/properties.go index eaf9e2cc17..f87ab051ee 100644 --- a/pkg/taskcommon/properties.go +++ b/pkg/taskcommon/properties.go @@ -21,6 +21,7 @@ import ( "strconv" "github.com/milvus-io/milvus/pkg/v3/proto/indexpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // properties keys @@ -50,7 +51,10 @@ func NewProperties(properties map[string]string) Properties { } func WrapErrTaskPropertyLack(lackProperty string, taskID any) error { - return fmt.Errorf("cannot find property '%s' for task '%v'", lackProperty, taskID) + // Task properties are populated by the coordinator, never by user input, so a + // missing property is an internal protocol violation (e.g. a mixed-version + // rolling upgrade), not a user error. + return merr.WrapErrServiceInternalMsg("cannot find property '%s' for task '%v'", lackProperty, taskID) } func (p Properties) AppendClusterID(clusterID string) { @@ -106,7 +110,10 @@ func (p Properties) GetTaskType() (Type, error) { case PreImport, Import, Compaction, Index, Stats, Analyze, RefreshExternalCollection, CopySegment: return p[TypeKey], nil default: - return p[TypeKey], fmt.Errorf("unrecognized task type '%s', taskID=%s", p[TypeKey], p[TaskIDKey]) + // Task types are assigned by the coordinator; an unrecognized one means a + // protocol mismatch (e.g. a newer coordinator scheduling onto an older + // node), which is system blame, not user input. + return p[TypeKey], merr.WrapErrServiceInternalMsg("unrecognized task type '%s', taskID=%s", p[TypeKey], p[TaskIDKey]) } } @@ -151,7 +158,7 @@ func (p Properties) GetTaskState() (State, error) { } stateStr := p[StateKey] if _, ok := indexpb.JobState_value[stateStr]; !ok { - return None, fmt.Errorf("invalid task state '%v', taskID=%s", stateStr, p[TaskIDKey]) + return None, merr.WrapErrParameterInvalidMsg("invalid task state '%v', taskID=%s", stateStr, p[TaskIDKey]) } return State(indexpb.JobState_value[stateStr]), nil } diff --git a/pkg/tracer/tracer.go b/pkg/tracer/tracer.go index be4d05c148..94f10edd7d 100644 --- a/pkg/tracer/tracer.go +++ b/pkg/tracer/tracer.go @@ -38,6 +38,9 @@ import ( "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) +// ErrTracerConfigInvalid stands for an invalid tracer exporter configuration. +var ErrTracerConfigInvalid = errors.New("invalid tracer config") + func Init() error { params := paramtable.Get() @@ -142,14 +145,14 @@ func CreateTracerExporter(params *paramtable.ComponentParam) (sdk.SpanExporter, } exp, err = otlptracehttp.New(context.Background(), opts...) default: - return nil, errors.Newf("otlp method not supported: %s", params.TraceCfg.OtlpMethod.GetValue()) + return nil, errors.Wrapf(ErrTracerConfigInvalid, "otlp method not supported: %s", params.TraceCfg.OtlpMethod.GetValue()) } case "stdout": exp, err = stdout.New() case "noop": return nil, nil default: - err = errors.New("Empty Trace") + err = errors.Wrapf(ErrTracerConfigInvalid, "unsupported trace exporter: %s", params.TraceCfg.Exporter.GetValue()) } return exp, err diff --git a/pkg/util/conc/pool.go b/pkg/util/conc/pool.go index 5ef39830fe..52cc434bd7 100644 --- a/pkg/util/conc/pool.go +++ b/pkg/util/conc/pool.go @@ -17,7 +17,6 @@ package conc import ( - "fmt" "strconv" "sync" "time" @@ -71,7 +70,7 @@ func (pool *Pool[T]) Submit(method func() (T, error)) *Future[T] { defer close(future.ch) defer func() { if x := recover(); x != nil { - future.err = fmt.Errorf("panicked with error: %v", x) + future.err = merr.WrapErrServiceInternalMsg("panicked with error: %v", x) panic(x) // throw panic out } }() diff --git a/pkg/util/contextutil/context_util.go b/pkg/util/contextutil/context_util.go index 41c4df5723..96758c6b10 100644 --- a/pkg/util/contextutil/context_util.go +++ b/pkg/util/contextutil/context_util.go @@ -22,12 +22,12 @@ import ( "strings" "time" - "github.com/cockroachdb/errors" "google.golang.org/grpc/metadata" "github.com/milvus-io/milvus/pkg/v3/metrics" "github.com/milvus-io/milvus/pkg/v3/util" "github.com/milvus-io/milvus/pkg/v3/util/crypto" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type ctxTenantKey struct{} @@ -91,20 +91,20 @@ func GetCurUserFromContext(ctx context.Context) (string, error) { func GetAuthInfoFromContext(ctx context.Context) (string, string, error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { - return "", "", errors.New("fail to get md from the context") + return "", "", merr.WrapErrParameterInvalidMsg("fail to get md from the context") } authorization, ok := md[strings.ToLower(util.HeaderAuthorize)] if !ok || len(authorization) < 1 { - return "", "", fmt.Errorf("fail to get authorization from the md, %s:[token]", strings.ToLower(util.HeaderAuthorize)) + return "", "", merr.WrapErrParameterInvalidMsg("fail to get authorization from the md, %s:[token]", strings.ToLower(util.HeaderAuthorize)) } token := authorization[0] rawToken, err := crypto.Base64Decode(token) if err != nil { - return "", "", fmt.Errorf("fail to decode the token, token: %s", token) + return "", "", merr.WrapErrParameterInvalidMsg("fail to decode the token, token: %s", token) } secrets := strings.SplitN(rawToken, util.CredentialSeparator, 2) if len(secrets) < 2 { - return "", "", fmt.Errorf("fail to get user info from the raw token, raw token: %s", rawToken) + return "", "", merr.WrapErrParameterInvalidMsg("fail to get user info from the raw token, raw token: %s", rawToken) } // username: secrets[0] // password: secrets[1] diff --git a/pkg/util/distance/calc_distance.go b/pkg/util/distance/calc_distance.go index 21cacb98e5..bdce51b5b7 100644 --- a/pkg/util/distance/calc_distance.go +++ b/pkg/util/distance/calc_distance.go @@ -5,7 +5,7 @@ import ( "strings" "sync" - "github.com/cockroachdb/errors" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) /** @@ -62,7 +62,7 @@ var ( // ValidateMetricType returns metric text or error func ValidateMetricType(metric string) (string, error) { if metric == "" { - err := errors.New("metric type is empty") + err := merr.WrapErrParameterInvalidMsg("metric type is empty") return "", err } @@ -71,14 +71,14 @@ func ValidateMetricType(metric string) (string, error) { return m, nil } - err := errors.New("invalid metric type") + err := merr.WrapErrParameterInvalidMsg("invalid metric type") return metric, err } // ValidateFloatArrayLength is used validate float vector length func ValidateFloatArrayLength(dim int64, length int) error { if length == 0 || int64(length)%dim != 0 { - err := errors.New("invalid float vector length") + err := merr.WrapErrParameterInvalidMsg("invalid float vector length") return err } @@ -106,13 +106,13 @@ func CalcFFBatch(dim int64, left []float32, lIndex int64, right []float32, metri // it will checks input, and calculate the distance concurrently func CalcFloatDistance(dim int64, left, right []float32, metric string) ([]float32, error) { if dim <= 0 { - err := errors.New("invalid dimension") + err := merr.WrapErrParameterInvalidMsg("invalid dimension") return nil, err } metricUpper := strings.ToUpper(metric) if metricUpper != L2 && metricUpper != IP && metricUpper != COSINE { - err := errors.New("invalid metric type") + err := merr.WrapErrParameterInvalidMsg("invalid metric type") return nil, err } diff --git a/pkg/util/etcd/etcd_util.go b/pkg/util/etcd/etcd_util.go index 2c8c013926..fdc5cb650c 100644 --- a/pkg/util/etcd/etcd_util.go +++ b/pkg/util/etcd/etcd_util.go @@ -36,6 +36,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/common" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/util" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type ClientOption func(*clientv3.Config) @@ -123,11 +124,11 @@ func GetRemoteEtcdSSLClientWithCfg(endpoints []string, certFile string, keyFile cfg.DialTimeout = 5 * time.Second cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { - return nil, errors.Wrap(err, "load etcd cert key pair error") + return nil, merr.Wrap(err, "load etcd cert key pair error") } caCert, err := os.ReadFile(caCertFile) if err != nil { - return nil, errors.Wrapf(err, "load etcd CACert file error, filename = %s", caCertFile) + return nil, merr.Wrapf(err, "load etcd CACert file error, filename = %s", caCertFile) } caCertPool := x509.NewCertPool() @@ -153,7 +154,7 @@ func GetRemoteEtcdSSLClientWithCfg(endpoints []string, certFile string, keyFile } if cfg.TLS.MinVersion == 0 { - return nil, errors.Errorf("unknown TLS version,%s", minVersion) + return nil, merr.WrapErrParameterInvalidMsg("unknown TLS version,%s", minVersion) } cfg.DialOptions = append(cfg.DialOptions, grpc.WithBlock()) @@ -240,13 +241,13 @@ func RemoveByBatchWithLimit(removals []string, limit int, op func(partialKeys [] func buildKvGroup(keys, values []string) (map[string]string, error) { if len(keys) != len(values) { - return nil, fmt.Errorf("length of keys (%d) and values (%d) are not equal", len(keys), len(values)) + return nil, merr.WrapErrParameterInvalidMsg("length of keys (%d) and values (%d) are not equal", len(keys), len(values)) } ret := make(map[string]string, len(keys)) for i, k := range keys { _, ok := ret[k] if ok { - return nil, fmt.Errorf("duplicated key was found: %s", k) + return nil, merr.WrapErrParameterInvalidMsg("duplicated key was found: %s", k) } ret[k] = values[i] } diff --git a/pkg/util/etcd/etcd_util_test.go b/pkg/util/etcd/etcd_util_test.go index aa94d49dcb..1163b58537 100644 --- a/pkg/util/etcd/etcd_util_test.go +++ b/pkg/util/etcd/etcd_util_test.go @@ -18,15 +18,51 @@ package etcd import ( "context" + "fmt" + "net" + "os" "path" "testing" "github.com/cockroachdb/errors" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +// freePort grabs a random unused TCP port. There's a small TOCTOU window +// between Close and the subsequent bind, but for unit tests it's acceptable. +func freePort(t *testing.T) int { + ln, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + port := ln.Addr().(*net.TCPAddr).Port + require.NoError(t, ln.Close()) + return port +} + func TestEtcd(t *testing.T) { - err := InitEtcdServer(true, "", "/tmp/data", "stdout", "info") + // Use random free ports so the embedded etcd does not collide with any + // already-running etcd on the dev machine (e.g. docker-compose etcd + // holding 2379/2380). + clientPort, peerPort := freePort(t), freePort(t) + dataDir, err := os.MkdirTemp("", "test-etcd-*") + require.NoError(t, err) + defer os.RemoveAll(dataDir) + cfgFile, err := os.CreateTemp("", "test-etcd-*.yaml") + require.NoError(t, err) + defer os.Remove(cfgFile.Name()) + _, err = fmt.Fprintf(cfgFile, `name: default +data-dir: %s +listen-client-urls: http://127.0.0.1:%d +advertise-client-urls: http://127.0.0.1:%d +listen-peer-urls: http://127.0.0.1:%d +initial-advertise-peer-urls: http://127.0.0.1:%d +initial-cluster: default=http://127.0.0.1:%d +initial-cluster-state: new +`, dataDir, clientPort, clientPort, peerPort, peerPort, peerPort) + require.NoError(t, err) + require.NoError(t, cfgFile.Close()) + + err = InitEtcdServer(true, cfgFile.Name(), dataDir, "stdout", "info") assert.NoError(t, err) defer StopEtcdServer() diff --git a/pkg/util/expr/expr.go b/pkg/util/expr/expr.go index 30bd30d635..b84f6db86d 100644 --- a/pkg/util/expr/expr.go +++ b/pkg/util/expr/expr.go @@ -23,13 +23,13 @@ import ( "fmt" "unsafe" - "github.com/cockroachdb/errors" "github.com/expr-lang/expr" "github.com/expr-lang/expr/vm" "go.uber.org/zap" "google.golang.org/protobuf/proto" "github.com/milvus-io/milvus/pkg/v3/log" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -79,21 +79,21 @@ func HasRegistered(key string) bool { func Exec(code, auth string) (res string, err error) { defer func() { if e := recover(); e != nil { - err = fmt.Errorf("panic: %v", e) + err = merr.WrapErrParameterInvalidMsg("panic: %v", e) } }() if v == nil { - return "", errors.New("the expr isn't inited") + return "", merr.WrapErrParameterInvalidMsg("the expr isn't inited") } if code == "" { - return "", errors.New("the expr code is empty") + return "", merr.WrapErrParameterInvalidMsg("the expr code is empty") } if auth == "" { - return "", errors.New("the expr auth is empty") + return "", merr.WrapErrParameterInvalidMsg("the expr auth is empty") } // Allow bypass when authentication has been verified externally (e.g., by HTTP handler) if auth != AuthBypass && authKey != auth { - return "", errors.New("the expr auth is invalid") + return "", merr.WrapErrParameterInvalidMsg("the expr auth is invalid") } program, err := expr.Compile(code, expr.Env(env), expr.WithContext("ctx")) if err != nil { diff --git a/pkg/util/externalspec/external_spec.go b/pkg/util/externalspec/external_spec.go index 3641073fec..f3a9c9c04a 100644 --- a/pkg/util/externalspec/external_spec.go +++ b/pkg/util/externalspec/external_spec.go @@ -18,7 +18,6 @@ package externalspec import ( "encoding/json" - "fmt" "net/url" "sort" "strconv" @@ -278,25 +277,25 @@ func sortedKeys(m map[string]bool) []string { // minio:////. func ValidateExternalSource(source string) error { if source == "" { - return fmt.Errorf("external_source is empty") + return merr.WrapErrParameterInvalidMsg("external_source is empty") } u, err := url.Parse(source) if err != nil { - return fmt.Errorf("invalid external_source URL: %w", err) + return merr.Wrap(err, "invalid external_source URL") } scheme := strings.ToLower(u.Scheme) if scheme == "" { - return fmt.Errorf("external_source must have an explicit scheme (e.g. s3://, aws://, gs://)") + return merr.WrapErrParameterInvalidMsg("external_source must have an explicit scheme (e.g. s3://, aws://, gs://)") } if !allowedExternalSourceSchemes[scheme] { - return fmt.Errorf("external_source scheme %q is not allowed; allowed schemes: %s", + return merr.WrapErrParameterInvalidMsg("external_source scheme %q is not allowed; allowed schemes: %s", scheme, strings.Join(sortedKeys(allowedExternalSourceSchemes), ", ")) } if u.User != nil { - return fmt.Errorf("external_source must not embed credentials in the URL (use extfs.access_key_id / extfs.access_key_value instead)") + return merr.WrapErrParameterInvalidMsg("external_source must not embed credentials in the URL (use extfs.access_key_id / extfs.access_key_value instead)") } if u.Host == "" { - return fmt.Errorf("external_source must have a non-empty host (e.g. s3://bucket/key, s3://endpoint/bucket/key, or minio://endpoint/bucket/key)") + return merr.WrapErrParameterInvalidMsg("external_source must have a non-empty host (e.g. s3://bucket/key, s3://endpoint/bucket/key, or minio://endpoint/bucket/key)") } return nil } @@ -340,13 +339,13 @@ func ValidateExtfsComplete(externalSource string, extfs map[string]string) error cp = CloudProviderMinIO } if cp == "" { - return fmt.Errorf("extfs.cloud_provider is required: one of [aws, gcp, aliyun, tencent, huawei, azure, minio]; inferring from URI scheme is ambiguous (e.g. s3:// matches both AWS S3 and self-hosted MinIO)") + return merr.WrapErrParameterMissingMsg("extfs.cloud_provider is required: one of [aws, gcp, aliyun, tencent, huawei, azure, minio]; inferring from URI scheme is ambiguous (e.g. s3:// matches both AWS S3 and self-hosted MinIO)") } if !validCloudProviders[cp] { - return fmt.Errorf("extfs.cloud_provider=%q is not supported: must be one of [aws, gcp, aliyun, tencent, huawei, azure, minio]", cp) + return merr.WrapErrParameterInvalidMsg("extfs.cloud_provider=%q is not supported: must be one of [aws, gcp, aliyun, tencent, huawei, azure, minio]", cp) } if scheme == SchemeMinIO && cp != CloudProviderMinIO { - return fmt.Errorf("scheme=minio requires extfs.cloud_provider=%q, got %q", CloudProviderMinIO, cp) + return merr.WrapErrParameterInvalidMsg("scheme=minio requires extfs.cloud_provider=%q, got %q", CloudProviderMinIO, cp) } hasAKSK := extfs[ExtfsKeyAccessKeyID] != "" && extfs[ExtfsKeyAccessKeyValue] != "" @@ -357,7 +356,7 @@ func ValidateExtfsComplete(externalSource string, extfs map[string]string) error hasAnonymous := extfs[ExtfsKeyAnonymous] == "true" if hasAKOnly { - return fmt.Errorf("extfs.access_key_id and extfs.access_key_value must be set together (found one without the other)") + return merr.WrapErrParameterMissingMsg("extfs.access_key_id and extfs.access_key_value must be set together (found one without the other)") } modes := 0 @@ -367,34 +366,34 @@ func ValidateExtfsComplete(externalSource string, extfs map[string]string) error } } if modes == 0 { - return fmt.Errorf("extfs credential mode missing: set exactly one of {access_key_id+access_key_value}, role_arn, use_iam=true, gcp_target_service_account, or anonymous=true") + return merr.WrapErrParameterInvalidMsg("extfs credential mode missing: set exactly one of {access_key_id+access_key_value}, role_arn, use_iam=true, gcp_target_service_account, or anonymous=true") } if modes > 1 { - return fmt.Errorf("extfs credential modes are mutually exclusive: set exactly one of AK/SK, role_arn, use_iam=true, gcp_target_service_account, or anonymous=true") + return merr.WrapErrParameterInvalidMsg("extfs credential modes are mutually exclusive: set exactly one of AK/SK, role_arn, use_iam=true, gcp_target_service_account, or anonymous=true") } if hasGCPImpersonation { if scheme != "" && scheme != SchemeGS && scheme != SchemeGCS && cp != CloudProviderGCP { - return fmt.Errorf("extfs.gcp_target_service_account is only valid for GCP (scheme=gs/gcs or cloud_provider=gcp), got scheme=%q cloud_provider=%q", scheme, cp) + return merr.WrapErrParameterInvalidMsg("extfs.gcp_target_service_account is only valid for GCP (scheme=gs/gcs or cloud_provider=gcp), got scheme=%q cloud_provider=%q", scheme, cp) } sa := extfs[ExtfsKeyGCPTargetServiceAccount] if !strings.Contains(sa, "@") || !strings.HasSuffix(sa, ".gserviceaccount.com") { - return fmt.Errorf("extfs.gcp_target_service_account must be a GCP service account email ending in .gserviceaccount.com, got %q", sa) + return merr.WrapErrParameterInvalidMsg("extfs.gcp_target_service_account must be a GCP service account email ending in .gserviceaccount.com, got %q", sa) } } if awsFamilyScheme[scheme] && extfs[ExtfsKeyRegion] == "" { - return fmt.Errorf("extfs.region is required for scheme %q (AWS-family schemes need region for SigV4 signing)", scheme) + return merr.WrapErrParameterMissingMsg("extfs.region is required for scheme %q (AWS-family schemes need region for SigV4 signing)", scheme) } // Azure consistency: scheme=azure requires cloud_provider=azure, and // cloud_provider=azure requires scheme=azure. Pairing guards against // misconfigured dispatch to a non-Azure storage backend. if scheme == SchemeAzure && cp != CloudProviderAzure { - return fmt.Errorf("scheme=azure requires extfs.cloud_provider=%q, got %q", CloudProviderAzure, cp) + return merr.WrapErrParameterInvalidMsg("scheme=azure requires extfs.cloud_provider=%q, got %q", CloudProviderAzure, cp) } if cp == CloudProviderAzure && scheme != "" && scheme != SchemeAzure { - return fmt.Errorf("extfs.cloud_provider=azure requires scheme=azure, got scheme=%q", scheme) + return merr.WrapErrParameterInvalidMsg("extfs.cloud_provider=azure requires scheme=azure, got scheme=%q", scheme) } // Two-form URI contract: Milvus-form (scheme://endpoint/bucket/key) has @@ -421,7 +420,7 @@ func ValidateExtfsComplete(externalSource string, extfs map[string]string) error // cloud_provider + region. cp=minio has no canonical endpoint // and must use Milvus-form URI to embed the host explicitly. if DeriveEndpoint(cp, extfs[ExtfsKeyRegion]) == "" { - return fmt.Errorf("cannot resolve endpoint for %q with cloud_provider=%q: set extfs.region, or use Milvus-form URI scheme:////", externalSource, cp) + return merr.WrapErrParameterInvalidMsg("cannot resolve endpoint for %q with cloud_provider=%q: set extfs.region, or use Milvus-form URI scheme:////", externalSource, cp) } } } diff --git a/pkg/util/funcutil/func.go b/pkg/util/funcutil/func.go index f8e06ea733..03ec6dc367 100644 --- a/pkg/util/funcutil/func.go +++ b/pkg/util/funcutil/func.go @@ -40,6 +40,7 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/util" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -80,7 +81,7 @@ func GetIP(ip string) string { } } } - panic(errors.New(`Network port does not have an IP address that falls within the given CIDR range`)) + panic(merr.WrapErrParameterInvalidMsg(`Network port does not have an IP address that falls within the given CIDR range`)) } netIP := net.ParseIP(ip) @@ -91,10 +92,10 @@ func GetIP(ip string) string { } // only localhost or unicast is acceptable if netIP.IsUnspecified() { - panic(errors.Newf(`"%s" in param table is Unspecified IP address and cannot be used`)) + panic(merr.WrapErrParameterInvalidMsg(`"%s" in param table is Unspecified IP address and cannot be used`)) } if netIP.IsMulticast() || netIP.IsLinkLocalMulticast() || netIP.IsInterfaceLocalMulticast() { - panic(errors.Newf(`"%s" in param table is Multicast IP address and cannot be used`)) + panic(merr.WrapErrParameterInvalidMsg(`"%s" in param table is Multicast IP address and cannot be used`)) } return ip } @@ -215,7 +216,7 @@ func JSONToMap(mStr string) (map[string]string, error) { buffer := make(map[string]any) err := json.Unmarshal([]byte(mStr), &buffer) if err != nil { - return nil, fmt.Errorf("unmarshal params failed, %w", err) + return nil, merr.Wrap(err, "unmarshal params failed") } ret := make(map[string]string) for key, value := range buffer { @@ -238,7 +239,7 @@ func JSONToRoleDetails(mStr string) (map[string](map[string]([](map[string]strin buffer := make(map[string](map[string]([](map[string]string))), 0) err := json.Unmarshal([]byte(mStr), &buffer) if err != nil { - return nil, fmt.Errorf("unmarshal `builtinRoles.Roles` failed, %w", err) + return nil, merr.Wrap(err, "unmarshal `builtinRoles.Roles` failed") } ret := make(map[string](map[string]([](map[string]string))), 0) for role, privilegesJSON := range buffer { @@ -275,7 +276,7 @@ func GetAttrByKeyFromRepeatedKV(key string, kvs []*commonpb.KeyValuePair) (strin } } - return "", fmt.Errorf("key %s not found", key) + return "", merr.WrapErrParameterInvalidMsg("key %s not found", key) } // TryGetAttrByKeyFromRepeatedKV return the value corresponding to key in kv pair @@ -413,10 +414,10 @@ func GetVirtualChannel(pchannel string, collectionID int64, idx int) string { // ConvertChannelName assembles channel name according to parameters. func ConvertChannelName(chanName string, tokenFrom string, tokenTo string) (string, error) { if tokenFrom == "" { - return "", errors.New("the tokenFrom is empty") + return "", merr.WrapErrParameterInvalidMsg("the tokenFrom is empty") } if !strings.Contains(chanName, tokenFrom) { - return "", fmt.Errorf("cannot find token '%s' in '%s'", tokenFrom, chanName) + return "", merr.WrapErrParameterInvalidMsg("cannot find token '%s' in '%s'", tokenFrom, chanName) } return strings.Replace(chanName, tokenFrom, tokenTo, 1), nil } @@ -445,60 +446,60 @@ func getNumRowsOfArrayVectorField(datas interface{}) uint64 { func GetNumRowsOfFloatVectorField(fDatas []float32, dim int64) (uint64, error) { if dim <= 0 { - return 0, fmt.Errorf("dim(%d) should be greater than 0", dim) + return 0, merr.WrapErrParameterInvalidMsg("dim(%d) should be greater than 0", dim) } l := len(fDatas) if int64(l)%dim != 0 { - return 0, fmt.Errorf("the length(%d) of float data should divide the dim(%d)", l, dim) + return 0, merr.WrapErrParameterInvalidMsg("the length(%d) of float data should divide the dim(%d)", l, dim) } return uint64(int64(l) / dim), nil } func GetNumRowsOfBinaryVectorField(bDatas []byte, dim int64) (uint64, error) { if dim <= 0 { - return 0, fmt.Errorf("dim(%d) should be greater than 0", dim) + return 0, merr.WrapErrParameterInvalidMsg("dim(%d) should be greater than 0", dim) } if dim%8 != 0 { - return 0, fmt.Errorf("dim(%d) should divide 8", dim) + return 0, merr.WrapErrParameterInvalidMsg("dim(%d) should divide 8", dim) } l := len(bDatas) if (8*int64(l))%dim != 0 { - return 0, fmt.Errorf("the num(%d) of all bits should divide the dim(%d)", 8*l, dim) + return 0, merr.WrapErrParameterInvalidMsg("the num(%d) of all bits should divide the dim(%d)", 8*l, dim) } return uint64((8 * int64(l)) / dim), nil } func GetNumRowsOfFloat16VectorField(f16Datas []byte, dim int64) (uint64, error) { if dim <= 0 { - return 0, fmt.Errorf("dim(%d) should be greater than 0", dim) + return 0, merr.WrapErrParameterInvalidMsg("dim(%d) should be greater than 0", dim) } l := len(f16Datas) rowWidth := dim * 2 if int64(l)%rowWidth != 0 { - return 0, fmt.Errorf("the length(%d) of float16 data should divide the row width(%d)", l, rowWidth) + return 0, merr.WrapErrParameterInvalidMsg("the length(%d) of float16 data should divide the row width(%d)", l, rowWidth) } return uint64(int64(l) / rowWidth), nil } func GetNumRowsOfBFloat16VectorField(bf16Datas []byte, dim int64) (uint64, error) { if dim <= 0 { - return 0, fmt.Errorf("dim(%d) should be greater than 0", dim) + return 0, merr.WrapErrParameterInvalidMsg("dim(%d) should be greater than 0", dim) } l := len(bf16Datas) rowWidth := dim * 2 if int64(l)%rowWidth != 0 { - return 0, fmt.Errorf("the length(%d) of bfloat data should divide the row width(%d)", l, rowWidth) + return 0, merr.WrapErrParameterInvalidMsg("the length(%d) of bfloat data should divide the row width(%d)", l, rowWidth) } return uint64(int64(l) / rowWidth), nil } func GetNumRowsOfInt8VectorField(iDatas []byte, dim int64) (uint64, error) { if dim <= 0 { - return 0, fmt.Errorf("dim(%d) should be greater than 0", dim) + return 0, merr.WrapErrParameterInvalidMsg("dim(%d) should be greater than 0", dim) } l := len(iDatas) if int64(l)%dim != 0 { - return 0, fmt.Errorf("the length(%d) of int8 data should divide the dim(%d)", l, dim) + return 0, merr.WrapErrParameterInvalidMsg("the length(%d) of int8 data should divide the dim(%d)", l, dim) } return uint64(int64(l) / dim), nil } @@ -515,7 +516,7 @@ func CountValidRows(validData []bool) uint64 { func GetVectorFieldPhysicalRows(fieldName string, dataType schemapb.DataType, vectors *schemapb.VectorField) (uint64, error) { if vectors == nil { - return 0, fmt.Errorf("nullable vector field %s requires vector data", fieldName) + return 0, merr.WrapErrParameterInvalidMsg("nullable vector field %s requires vector data", fieldName) } return getVectorFieldPhysicalRowsWithDim(fieldName, dataType, vectors, vectors.GetDim()) @@ -539,23 +540,23 @@ func getVectorFieldPhysicalRowsWithDim(fieldName string, dataType schemapb.DataT case schemapb.DataType_Int8Vector: return GetNumRowsOfInt8VectorField(vectors.GetInt8Vector(), dim) default: - return 0, fmt.Errorf("unsupported nullable vector type %s", dataType) + return 0, merr.WrapErrParameterInvalidMsg("unsupported nullable vector type %s", dataType) } } func ValidateNullableVectorCompactRows(fieldName string, validData []bool, physicalRows uint64, logicalRows uint64, requireValidData bool) error { if len(validData) == 0 { if requireValidData { - return fmt.Errorf("nullable vector field %s requires valid_data", fieldName) + return merr.WrapErrParameterInvalidMsg("nullable vector field %s requires valid_data", fieldName) } return nil } if logicalRows > 0 && uint64(len(validData)) != logicalRows { - return fmt.Errorf("nullable vector field %s valid_data length mismatch: valid_data=%d, logical rows=%d", fieldName, len(validData), logicalRows) + return merr.WrapErrParameterInvalidMsg("nullable vector field %s valid_data length mismatch: valid_data=%d, logical rows=%d", fieldName, len(validData), logicalRows) } validRows := CountValidRows(validData) if physicalRows != validRows { - return fmt.Errorf("nullable vector field %s has %d valid rows, but compact physical payload rows is %d", fieldName, validRows, physicalRows) + return merr.WrapErrParameterInvalidMsg("nullable vector field %s has %d valid rows, but compact physical payload rows is %d", fieldName, validRows, physicalRows) } return nil } @@ -702,7 +703,7 @@ func GetNumRowOfFieldDataWithSchema(fieldData *schemapb.FieldData, helper *typeu fieldNumRows = getNumRowsOfArrayVectorField(fieldData.GetVectors().GetVectorArray().GetData()) } default: - return 0, fmt.Errorf("%s is not supported now", fieldSchema.GetDataType()) + return 0, merr.WrapErrParameterInvalidMsg("%s is not supported now", fieldSchema.GetDataType()) } return fieldNumRows, nil @@ -737,7 +738,7 @@ func GetNumRowOfFieldData(fieldData *schemapb.FieldData) (uint64, error) { case *schemapb.ScalarField_GeometryData: fieldNumRows = getNumRowsOfScalarField(scalarField.GetGeometryData().Data) default: - return 0, fmt.Errorf("%s is not supported now", scalarType) + return 0, merr.WrapErrParameterInvalidMsg("%s is not supported now", scalarType) } case *schemapb.FieldData_Vectors: vectorField := fieldData.GetVectors() @@ -783,10 +784,10 @@ func GetNumRowOfFieldData(fieldData *schemapb.FieldData) (uint64, error) { case *schemapb.VectorField_VectorArray: fieldNumRows = getNumRowsOfArrayVectorField(vectorField.GetVectorArray().Data) default: - return 0, fmt.Errorf("%s is not supported now", vectorFieldType) + return 0, merr.WrapErrParameterInvalidMsg("%s is not supported now", vectorFieldType) } default: - return 0, fmt.Errorf("%s is not supported now", fieldType) + return 0, merr.WrapErrParameterInvalidMsg("%s is not supported now", fieldType) } return fieldNumRows, nil @@ -847,7 +848,7 @@ func EncodeUserRoleCache(user string, role string) string { func DecodeUserRoleCache(cache string) (string, string, error) { index := strings.LastIndex(cache, "/") if index == -1 { - return "", "", fmt.Errorf("invalid param, cache: [%s]", cache) + return "", "", merr.WrapErrParameterInvalidMsg("invalid param, cache: [%s]", cache) } user := cache[:index] role := cache[index+1:] diff --git a/pkg/util/funcutil/map.go b/pkg/util/funcutil/map.go index 0e47d2e3ed..c0062466b4 100644 --- a/pkg/util/funcutil/map.go +++ b/pkg/util/funcutil/map.go @@ -1,6 +1,6 @@ package funcutil -import "fmt" +import "github.com/milvus-io/milvus/pkg/v3/util/merr" func MapReduce(results []map[string]string, method map[string]func(string) error) error { // TODO: use generic type to reconstruct map[string]string -> [T any] map[string]T @@ -8,7 +8,7 @@ func MapReduce(results []map[string]string, method map[string]func(string) error for k, v := range result { fn, ok := method[k] if !ok { - return fmt.Errorf("unknown field %s", k) + return merr.WrapErrParameterInvalidMsg("unknown field %s", k) } if err := fn(v); err != nil { return err diff --git a/pkg/util/funcutil/placeholdergroup.go b/pkg/util/funcutil/placeholdergroup.go index 991dd52894..a6e2cc3e8d 100644 --- a/pkg/util/funcutil/placeholdergroup.go +++ b/pkg/util/funcutil/placeholdergroup.go @@ -4,12 +4,12 @@ import ( "encoding/binary" "math" - "github.com/cockroachdb/errors" "github.com/samber/lo" "google.golang.org/protobuf/proto" "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" ) func SparseVectorDataToPlaceholderGroupBytes(contents [][]byte) []byte { @@ -83,7 +83,7 @@ func fieldDataToPlaceholderValue(fieldData *schemapb.FieldData) (*commonpb.Place vectors := fieldData.GetVectors() x, ok := vectors.GetData().(*schemapb.VectorField_FloatVector) if !ok { - return nil, errors.New("vector data is not schemapb.VectorField_FloatVector") + return nil, merr.WrapErrParameterInvalidMsg("vector data is not schemapb.VectorField_FloatVector") } placeholderValue := &commonpb.PlaceholderValue{ @@ -96,7 +96,7 @@ func fieldDataToPlaceholderValue(fieldData *schemapb.FieldData) (*commonpb.Place vectors := fieldData.GetVectors() x, ok := vectors.GetData().(*schemapb.VectorField_BinaryVector) if !ok { - return nil, errors.New("vector data is not schemapb.VectorField_BinaryVector") + return nil, merr.WrapErrParameterInvalidMsg("vector data is not schemapb.VectorField_BinaryVector") } placeholderValue := &commonpb.PlaceholderValue{ Tag: "$0", @@ -108,7 +108,7 @@ func fieldDataToPlaceholderValue(fieldData *schemapb.FieldData) (*commonpb.Place vectors := fieldData.GetVectors() x, ok := vectors.GetData().(*schemapb.VectorField_Float16Vector) if !ok { - return nil, errors.New("vector data is not schemapb.VectorField_Float16Vector") + return nil, merr.WrapErrParameterInvalidMsg("vector data is not schemapb.VectorField_Float16Vector") } placeholderValue := &commonpb.PlaceholderValue{ Tag: "$0", @@ -120,7 +120,7 @@ func fieldDataToPlaceholderValue(fieldData *schemapb.FieldData) (*commonpb.Place vectors := fieldData.GetVectors() x, ok := vectors.GetData().(*schemapb.VectorField_Bfloat16Vector) if !ok { - return nil, errors.New("vector data is not schemapb.VectorField_BFloat16Vector") + return nil, merr.WrapErrParameterInvalidMsg("vector data is not schemapb.VectorField_BFloat16Vector") } placeholderValue := &commonpb.PlaceholderValue{ Tag: "$0", @@ -131,7 +131,7 @@ func fieldDataToPlaceholderValue(fieldData *schemapb.FieldData) (*commonpb.Place case schemapb.DataType_SparseFloatVector: vectors, ok := fieldData.GetVectors().GetData().(*schemapb.VectorField_SparseFloatVector) if !ok { - return nil, errors.New("vector data is not schemapb.VectorField_SparseFloatVector") + return nil, merr.WrapErrParameterInvalidMsg("vector data is not schemapb.VectorField_SparseFloatVector") } vec := vectors.SparseFloatVector placeholderValue := &commonpb.PlaceholderValue{ @@ -144,7 +144,7 @@ func fieldDataToPlaceholderValue(fieldData *schemapb.FieldData) (*commonpb.Place vectors := fieldData.GetVectors() x, ok := vectors.GetData().(*schemapb.VectorField_Int8Vector) if !ok { - return nil, errors.New("vector data is not schemapb.VectorField_Int8Vector") + return nil, merr.WrapErrParameterInvalidMsg("vector data is not schemapb.VectorField_Int8Vector") } placeholderValue := &commonpb.PlaceholderValue{ @@ -162,7 +162,7 @@ func fieldDataToPlaceholderValue(fieldData *schemapb.FieldData) (*commonpb.Place } return placeholderValue, nil default: - return nil, errors.New("field is not a vector field") + return nil, merr.WrapErrParameterInvalidMsg("field is not a vector field") } } diff --git a/pkg/util/funcutil/policy.go b/pkg/util/funcutil/policy.go index a3f27df009..4c635739a6 100644 --- a/pkg/util/funcutil/policy.go +++ b/pkg/util/funcutil/policy.go @@ -5,7 +5,6 @@ import ( "fmt" "strings" - "github.com/cockroachdb/errors" "github.com/samber/lo" "go.uber.org/zap" "google.golang.org/protobuf/proto" @@ -15,18 +14,19 @@ import ( "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/util" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) func GetVersion(m interface{}) (string, error) { log := log.Ctx(context.TODO()) pbMsg, ok := m.(proto.Message) if !ok { - err := errors.New("MessageDescriptorProto result is nil") + err := merr.WrapErrParameterInvalidMsg("MessageDescriptorProto result is nil") log.RatedInfo(60, "GetVersion failed", zap.Error(err)) return "", err } if !proto.HasExtension(pbMsg.ProtoReflect().Descriptor().Options(), milvuspb.E_MilvusExtObj) { - err := errors.New("Extension not found") + err := merr.WrapErrParameterInvalidMsg("Extension not found") log.Error("GetExtension fail", zap.Error(err)) return "", err } @@ -39,13 +39,13 @@ func GetVersion(m interface{}) (string, error) { func GetPrivilegeExtObj(m interface{}) (commonpb.PrivilegeExt, error) { pbMsg, ok := m.(proto.Message) if !ok { - err := errors.New("MessageDescriptorProto result is nil") + err := merr.WrapErrParameterInvalidMsg("MessageDescriptorProto result is nil") log.RatedInfo(60, "GetPrivilegeExtObj failed", zap.Error(err)) return commonpb.PrivilegeExt{}, err } if !proto.HasExtension(pbMsg.ProtoReflect().Descriptor().Options(), commonpb.E_PrivilegeExtObj) { - err := errors.New("Extension not found") + err := merr.WrapErrParameterInvalidMsg("Extension not found") log.RatedWarn(60, "GetPrivilegeExtObj failed", zap.Error(err)) return commonpb.PrivilegeExt{}, err } @@ -69,7 +69,7 @@ func GetObjectName(m interface{}, index int32) string { pbMsg, ok := m.(proto.Message) if !ok { - err := errors.New("MessageDescriptorProto result is nil") + err := merr.WrapErrParameterInvalidMsg("MessageDescriptorProto result is nil") log.RatedInfo(60, "GetObjectName fail", zap.Error(err)) return util.AnyWord } @@ -95,7 +95,7 @@ func GetObjectNames(m interface{}, index int32) []string { pbMsg, ok := m.(proto.Message) if !ok { - err := errors.New("MessageDescriptorProto result is nil") + err := merr.WrapErrParameterInvalidMsg("MessageDescriptorProto result is nil") log.RatedInfo(60, "GetObjectNames fail", zap.Error(err)) return []string{} } diff --git a/pkg/util/hardware/container_darwin.go b/pkg/util/hardware/container_darwin.go index 1e4db2f700..ee0bded05b 100644 --- a/pkg/util/hardware/container_darwin.go +++ b/pkg/util/hardware/container_darwin.go @@ -12,15 +12,15 @@ package hardware import ( - "github.com/cockroachdb/errors" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // getContainerMemLimit returns memory limit and error func getContainerMemLimit() (uint64, error) { - return 0, errors.New("Not supported") + return 0, merr.WrapErrParameterInvalidMsg("Not supported") } // getContainerMemUsed returns memory usage and error func getContainerMemUsed() (uint64, error) { - return 0, errors.New("Not supported") + return 0, merr.WrapErrParameterInvalidMsg("Not supported") } diff --git a/pkg/util/hardware/container_linux.go b/pkg/util/hardware/container_linux.go index d8ec5a1b4f..be731729e2 100644 --- a/pkg/util/hardware/container_linux.go +++ b/pkg/util/hardware/container_linux.go @@ -14,13 +14,14 @@ package hardware import ( "os" - "github.com/cockroachdb/errors" "github.com/containerd/cgroups/v3" "github.com/containerd/cgroups/v3/cgroup1" statsv1 "github.com/containerd/cgroups/v3/cgroup1/stats" "github.com/containerd/cgroups/v3/cgroup2" statsv2 "github.com/containerd/cgroups/v3/cgroup2/stats" "k8s.io/apimachinery/pkg/api/resource" + + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) func getCgroupV1Stats() (*statsv1.Metrics, error) { @@ -35,7 +36,7 @@ func getCgroupV1Stats() (*statsv1.Metrics, error) { } if stats.GetMemory() == nil || stats.GetMemory().GetUsage() == nil { - return nil, errors.New("cannot find memory usage info from cGroupsv1") + return nil, merr.WrapErrParameterInvalidMsg("cannot find memory usage info from cGroupsv1") } return stats, nil } @@ -52,7 +53,7 @@ func getCgroupV2Stats() (*statsv2.Metrics, error) { } if stats.GetMemory() == nil { - return nil, errors.New("cannot find memory usage info from cGroupsv2") + return nil, merr.WrapErrParameterInvalidMsg("cannot find memory usage info from cGroupsv2") } return stats, nil } diff --git a/pkg/util/hardware/container_windows.go b/pkg/util/hardware/container_windows.go index 1e4db2f700..ee0bded05b 100644 --- a/pkg/util/hardware/container_windows.go +++ b/pkg/util/hardware/container_windows.go @@ -12,15 +12,15 @@ package hardware import ( - "github.com/cockroachdb/errors" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // getContainerMemLimit returns memory limit and error func getContainerMemLimit() (uint64, error) { - return 0, errors.New("Not supported") + return 0, merr.WrapErrParameterInvalidMsg("Not supported") } // getContainerMemUsed returns memory usage and error func getContainerMemUsed() (uint64, error) { - return 0, errors.New("Not supported") + return 0, merr.WrapErrParameterInvalidMsg("Not supported") } diff --git a/pkg/util/hardware/gpu_mem_info.go b/pkg/util/hardware/gpu_mem_info.go index d1cda07c12..9b1dbdf261 100644 --- a/pkg/util/hardware/gpu_mem_info.go +++ b/pkg/util/hardware/gpu_mem_info.go @@ -3,7 +3,7 @@ package hardware -import "github.com/cockroachdb/errors" +import "github.com/milvus-io/milvus/pkg/v3/util/merr" // GPUMemoryInfo holds information about a GPU's memory type GPUMemoryInfo struct { @@ -14,5 +14,5 @@ type GPUMemoryInfo struct { // GetAllGPUMemoryInfo returns mock GPU memory information for non-CUDA builds func GetAllGPUMemoryInfo() ([]GPUMemoryInfo, error) { // Mock error to indicate no CUDA support - return nil, errors.New("CUDA not supported: failed to retrieve GPU memory info or no GPUs found") + return nil, merr.WrapErrParameterInvalidMsg("CUDA not supported: failed to retrieve GPU memory info or no GPUs found") } diff --git a/pkg/util/hardware/gpu_mem_info_cuda.go b/pkg/util/hardware/gpu_mem_info_cuda.go index 0b6f9aec91..a88c62950e 100644 --- a/pkg/util/hardware/gpu_mem_info_cuda.go +++ b/pkg/util/hardware/gpu_mem_info_cuda.go @@ -55,7 +55,7 @@ import "C" import ( "unsafe" - "github.com/cockroachdb/errors" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // GPUMemoryInfo represents a single GPU's memory information. @@ -72,7 +72,7 @@ func GetAllGPUMemoryInfo() ([]GPUMemoryInfo, error) { // Call the C function to retrieve GPU memory info deviceCount := int(C.getAllGPUMemoryInfo(&infos)) if deviceCount == 0 { - return nil, errors.New("failed to retrieve GPU memory info or no GPUs found") + return nil, merr.WrapErrParameterInvalidMsg("failed to retrieve GPU memory info or no GPUs found") } defer C.free(unsafe.Pointer(infos)) // Free the allocated memory diff --git a/pkg/util/indexparams/index_params.go b/pkg/util/indexparams/index_params.go index 234b1d610d..5fcf4d3ae7 100644 --- a/pkg/util/indexparams/index_params.go +++ b/pkg/util/indexparams/index_params.go @@ -22,12 +22,11 @@ import ( "strconv" "unsafe" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus/pkg/v3/common" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" "github.com/milvus-io/milvus/pkg/v3/util/hardware" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -180,11 +179,11 @@ func FillDiskIndexParams(params *paramtable.ComponentParam, indexParams map[stri var ok bool maxDegree, ok = indexParams[MaxDegreeKey] if !ok { - return errors.New("index param max_degree not exist") + return merr.WrapErrParameterInvalidMsg("index param max_degree not exist") } searchListSize, ok = indexParams[SearchListSizeKey] if !ok { - return errors.New("index param search_list_size not exist") + return merr.WrapErrParameterInvalidMsg("index param search_list_size not exist") } extraParams, err := NewBigDataExtraParamsFromJSON(params.AutoIndexConfig.ExtraParams.GetValue()) if err != nil { @@ -227,13 +226,14 @@ func UpdateDiskIndexBuildParams(params *paramtable.ComponentParam, indexParams [ if params.AutoIndexConfig.Enable.GetAsBool() { extraParams, err := NewBigDataExtraParamsFromJSON(params.AutoIndexConfig.ExtraParams.GetValue()) if err != nil { - return indexParams, errors.New("index param search_cache_budget_gb_ratio not exist in AutoIndex Config") + // AutoIndexConfig is server-side configuration, not request input. + return indexParams, merr.WrapErrServiceInternalMsg("index param search_cache_budget_gb_ratio not exist in AutoIndex Config") } searchCacheBudgetGBRatio = fmt.Sprintf("%f", extraParams.SearchCacheBudgetGBRatio) } else { paramVal, err := strconv.ParseFloat(params.CommonCfg.SearchCacheBudgetGBRatio.GetValue(), 64) if err != nil { - return indexParams, errors.New("index param search_cache_budget_gb_ratio not exist in Config") + return indexParams, merr.WrapErrServiceInternalMsg("index param search_cache_budget_gb_ratio not exist in Config") } searchCacheBudgetGBRatio = fmt.Sprintf("%f", paramVal) } @@ -272,7 +272,7 @@ func UpdateDiskIndexBuildParams(params *paramtable.ComponentParam, indexParams [ func SetDiskIndexBuildParams(indexParams map[string]string, fieldDataSize int64) error { pqCodeBudgetGBRatioStr, ok := indexParams[PQCodeBudgetRatioKey] if !ok { - return errors.New("index param pqCodeBudgetGBRatio not exist") + return merr.WrapErrParameterInvalidMsg("index param pqCodeBudgetGBRatio not exist") } pqCodeBudgetGBRatio, err := strconv.ParseFloat(pqCodeBudgetGBRatioStr, 64) if err != nil { @@ -280,7 +280,7 @@ func SetDiskIndexBuildParams(indexParams map[string]string, fieldDataSize int64) } buildNumThreadsRatioStr, ok := indexParams[NumBuildThreadRatioKey] if !ok { - return errors.New("index param buildNumThreadsRatio not exist") + return merr.WrapErrParameterInvalidMsg("index param buildNumThreadsRatio not exist") } buildNumThreadsRatio, err := strconv.ParseFloat(buildNumThreadsRatioStr, 64) if err != nil { @@ -316,7 +316,7 @@ func SetDiskIndexLoadParams(params *paramtable.ComponentParam, indexParams map[s dimStr, ok := indexParams[common.DimKey] if !ok { // type param dim has been put into index params before build index - return errors.New("type param dim not exist") + return merr.WrapErrParameterInvalidMsg("type param dim not exist") } dim, err := strconv.ParseInt(dimStr, 10, 64) if err != nil { diff --git a/pkg/util/lock/metric_mutex.go b/pkg/util/lock/metric_mutex.go index fabc9ba90b..582f00a819 100644 --- a/pkg/util/lock/metric_mutex.go +++ b/pkg/util/lock/metric_mutex.go @@ -4,11 +4,11 @@ import ( "sync" "time" - "github.com/cockroachdb/errors" "go.uber.org/zap" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/metrics" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) @@ -74,7 +74,7 @@ func (mRWLock *MetricsRWMutex) maybeLogUnlockDuration(source string, lockType st } else { log.Error("there's no lock history for the source, there may be some defects in codes", zap.String("source", source)) - return errors.New("unknown source") + return merr.WrapErrParameterInvalidMsg("unknown source") } } return nil diff --git a/pkg/util/merr/error_classification_test.go b/pkg/util/merr/error_classification_test.go new file mode 100644 index 0000000000..4bd155f9c3 --- /dev/null +++ b/pkg/util/merr/error_classification_test.go @@ -0,0 +1,212 @@ +// 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 merr + +import ( + "strings" + "testing" + + "github.com/cockroachdb/errors" + "github.com/stretchr/testify/assert" +) + +// TestMilvusErrorWithInner covers the unified milvusError-with-inner that +// replaced wrappedMilvusError: a WrapErrXxxErr factory relabels the chain with +// its own sentinel code/type/retriability while keeping the inner reachable. +func TestMilvusErrorWithInner(t *testing.T) { + inner := WrapErrCollectionNotLoaded(123) // typed merr, code 101 + res := WrapErrServiceInternalErr(inner, "describe failed for %d", 7) + + // relabels to the outer sentinel's code, masking the inner's + assert.Equal(t, ErrServiceInternal.code(), Code(res)) + // both identities remain reachable + assert.True(t, errors.Is(res, ErrServiceInternal)) + assert.True(t, errors.Is(res, ErrCollectionNotLoaded), "inner reachable via Unwrap") + // classification + retriability follow the outer sentinel + assert.Equal(t, SystemError, GetErrorType(res)) + assert.False(t, IsRetryableErr(res)) + // message carries both context and inner + assert.Contains(t, res.Error(), "describe failed for 7") + assert.Contains(t, res.Error(), "collection not loaded") + // wire status reports the relabeled code + assert.Equal(t, ErrServiceInternal.code(), Status(res).GetCode()) + + // nil inner falls back to the plain *Msg form + res2 := WrapErrServiceInternalErr(nil, "plain") + assert.Equal(t, ErrServiceInternal.code(), Code(res2)) + assert.False(t, errors.Is(res2, ErrCollectionNotLoaded)) + + // plain sentinels / wrapFields values carry no inner + plain := WrapErrCollectionNotFound("foo") + assert.Equal(t, ErrCollectionNotFound.code(), Code(plain)) + assert.True(t, errors.Is(plain, ErrCollectionNotFound)) + assert.Nil(t, errors.Unwrap(plain)) +} + +// TestErrorTypeMarker covers WrapErrAsInputError/WrapErrAsSysError marking the +// broad classification through the chain, including on *Msg (errors.Wrapf) +// results where a direct type assertion would have missed it. +func TestErrorTypeMarker(t *testing.T) { + // *Msg result + mark input: code preserved, classification flipped, status carries the flag + e := WrapErrAsInputError(WrapErrOperationNotSupportedMsg("only support bm25")) + assert.Equal(t, InputError, GetErrorType(e)) + assert.Equal(t, ErrOperationNotSupported.code(), Code(e)) + assert.True(t, errors.Is(e, ErrOperationNotSupported)) + st := Status(e) + assert.Equal(t, "true", st.GetExtraInfo()[InputErrorFlagKey]) + assert.False(t, st.GetRetriable(), "InputError forces Retriable=false") + + // works on a relabeling milvusError-with-inner too; inner stays reachable + marked := WrapErrAsInputError(WrapErrServiceInternalErr(WrapErrCollectionNotLoaded(1), "ctx")) + assert.Equal(t, InputError, GetErrorType(marked)) + assert.Equal(t, ErrServiceInternal.code(), Code(marked)) + assert.True(t, errors.Is(marked, ErrCollectionNotLoaded)) + + // mark found through an outer merr.Wrap context layer + wrapped := Wrap(WrapErrAsInputError(WrapErrServiceInternalMsg("boom")), "outer") + assert.Equal(t, InputError, GetErrorType(wrapped)) + + // sentinel-baked classification unaffected (ParameterInvalid stays InputError) + assert.Equal(t, InputError, GetErrorType(WrapErrParameterInvalidMsg("x"))) + assert.Equal(t, SystemError, GetErrorType(WrapErrServiceInternalMsg("x"))) + + // WrapErrAsSysError flips a sentinel-input error to system + sys := WrapErrAsSysError(WrapErrParameterInvalidMsg("x")) + assert.Equal(t, SystemError, GetErrorType(sys)) + + // WrapErrAsInputErrorWhen only marks matching codes + hit := WrapErrAsInputErrorWhen(WrapErrParameterInvalidMsg("x"), ErrParameterInvalid) + assert.Equal(t, InputError, GetErrorType(hit)) + miss := WrapErrAsInputErrorWhen(WrapErrServiceInternalMsg("x"), ErrParameterInvalid) + assert.Equal(t, SystemError, GetErrorType(miss)) + + // nil-safety + assert.Nil(t, WrapErrAsInputError(nil)) + assert.Nil(t, WrapErrAsSysError(nil)) +} + +// TestSentinelErrorTypeClassification guards the input/system split that the +// proxy fail_input/fail_system alerting relies on. A sentinel silently losing +// (or gaining) WithErrorType(InputError) flips which party an alert blames. +func TestSentinelErrorTypeClassification(t *testing.T) { + // The request's own fault -> InputError (and therefore non-retriable). + inputSentinels := map[string]error{ + "ParameterInvalid": ErrParameterInvalid, + "ParameterMissing": ErrParameterMissing, + "ParameterTooLarge": ErrParameterTooLarge, + "CollectionLoaded": ErrCollectionLoaded, + "ResourceGroupNotFound": ErrResourceGroupNotFound, + "IndexDuplicate": ErrIndexDuplicate, + "PrivilegeNotPermitted": ErrPrivilegeNotPermitted, + "PrivilegeGroupInvalidName": ErrPrivilegeGroupInvalidName, + "NeedAuthenticate": ErrNeedAuthenticate, + "IncorrectParameterFormat": ErrIncorrectParameterFormat, + "MissingRequiredParameters": ErrMissingRequiredParameters, + "InvalidInsertData": ErrInvalidInsertData, + } + for name, err := range inputSentinels { + assert.Equal(t, InputError, GetErrorType(err), "%s should be InputError", name) + } + + // Topology / internal conditions stay SystemError (not the user's fault): + // re-resolving the shard map or a node coming back is the system's job. + // Collection/Database NotFound also stay SystemError at the sentinel level so + // datacoord's retry.Do recovery loops keep retrying a transient not-found; the + // proxy boundary stamps them InputError for users via WrapErrAsInputErrorWhen. + systemSentinels := map[string]error{ + "ServiceInternal": ErrServiceInternal, + "ChannelNotFound": ErrChannelNotFound, + "SegmentNotFound": ErrSegmentNotFound, + "NodeNotFound": ErrNodeNotFound, + "ReplicaNotFound": ErrReplicaNotFound, + "PartitionNotFound": ErrPartitionNotFound, + "CollectionNotLoaded": ErrCollectionNotLoaded, + "CollectionNotFound": ErrCollectionNotFound, + "DatabaseNotFound": ErrDatabaseNotFound, + } + for name, err := range systemSentinels { + assert.Equal(t, SystemError, GetErrorType(err), "%s should stay SystemError", name) + } + + // Closed-world check over the sentinel registry: the EXACT set of codes + // baked WithErrorType(InputError). Unlike the spot checks above, this + // cannot under-cover — adding or removing an InputError mark anywhere in + // errors.go fails here until the expected set is updated deliberately. + wantInputCodes := []int32{ + 102, 104, 105, 107, 108, 109, // Collection: num limit / loaded / illegal schema / vector clustering key / replicate mode / schema mismatch + 300, 301, 302, 303, // ResourceGroup + 702, // IndexDuplicate + 801, 802, // Database: num limit / invalid name + 1100, 1101, 1102, // Parameter: invalid / missing / too large + 1400, 1401, 1402, // Privilege + 1800, 1801, 1802, 1804, // Auth / parameter format / insert data + 2100, // ImportFailed + 2201, // QueryPlan + } + gotInputCodes := make([]int32, 0, len(wantInputCodes)) + for code, sentinel := range registeredCodes { + if sentinel.errType == InputError { + gotInputCodes = append(gotInputCodes, code) + } + } + assert.ElementsMatch(t, wantInputCodes, gotInputCodes, + "the set of InputError-baked sentinel codes changed; update this list only with a deliberate classification decision") +} + +// TestStatusReasonComposition pins what clients actually read: Reason is the +// full composed chain, outermost context first, root cause last. +func TestStatusReasonComposition(t *testing.T) { + err := Wrap(WrapErrCollectionNotFound("c1"), "failed to describe collection") + reason := Status(err).GetReason() + assert.Contains(t, reason, "failed to describe collection") + assert.Contains(t, reason, "collection not found") + assert.Less(t, strings.Index(reason, "failed to describe collection"), + strings.Index(reason, "collection not found"), + "outer context must precede the root cause") +} + +// TestStatusTwoHopRoundtrip simulates proxy -> coord -> proxy: the error is +// serialized to a Status, reconstructed, and serialized again. Code, the +// InputError classification, and forced non-retriability must survive both hops. +func TestStatusTwoHopRoundtrip(t *testing.T) { + orig := WrapErrAsInputError(WrapErrCollectionNotFound("c1")) + + st1 := Status(orig) + hop := Error(st1) + st2 := Status(hop) + + assert.Equal(t, st1.GetCode(), st2.GetCode()) + assert.Equal(t, "true", st2.GetExtraInfo()[InputErrorFlagKey]) + assert.Equal(t, InputError, GetErrorType(Error(st2))) + assert.False(t, st2.GetRetriable()) + assert.Contains(t, st2.GetReason(), "collection not found") +} + +// TestRelabelAsMatchTarget documents the errors.Is geometry of a relabel +// (milvusError-with-inner): the relabel matches its own sentinel AND the inner +// remains reachable through Unwrap, while an unrelated error sharing neither +// code does not match. +func TestRelabelAsMatchTarget(t *testing.T) { + inner := WrapErrCollectionNotFound("c1") + relabeled := WrapErrServiceInternalErr(inner, "downgraded at boundary") + + assert.ErrorIs(t, relabeled, ErrServiceInternal) // relabel identity + assert.ErrorIs(t, relabeled, ErrCollectionNotFound) // inner stays reachable + assert.Equal(t, ErrServiceInternal.code(), Code(relabeled), + "the relabel's code wins for the wire") + assert.NotErrorIs(t, WrapErrDatabaseNotFound("db"), relabeled) +} diff --git a/pkg/util/merr/errors.go b/pkg/util/merr/errors.go index c2657182f1..8857d26de0 100644 --- a/pkg/util/merr/errors.go +++ b/pkg/util/merr/errors.go @@ -17,7 +17,10 @@ package merr import ( + "fmt" + "github.com/cockroachdb/errors" + "github.com/cockroachdb/errors/markers" "github.com/samber/lo" ) @@ -38,6 +41,11 @@ var ErrorTypeName = map[ErrorType]string{ InputError: "input_error", } +// ErrorClassifier defines the method to get the broad classification of a Milvus error. +type ErrorClassifier interface { + GetErrorType() ErrorType +} + func (err ErrorType) String() string { return ErrorTypeName[err] } @@ -62,20 +70,21 @@ var ( ErrServiceResourceInsufficient = newMilvusError("service resource insufficient", 12, true) // Collection related - ErrCollectionNotFound = newMilvusError("collection not found", 100, false) + ErrCollectionNotFound = newMilvusError("collection not found", 100, false) // SystemError by default: internal retry.Do paths (datacoord handler/recovery) must keep retrying through a transient not-found; the proxy boundary stamps it InputError for users via WrapErrAsInputErrorWhen. ErrCollectionNotLoaded = newMilvusError("collection not loaded", 101, false) - ErrCollectionNumLimitExceeded = newMilvusError("exceeded the limit number of collections", 102, false) + ErrCollectionNumLimitExceeded = newMilvusError("exceeded the limit number of collections", 102, false, WithErrorType(InputError)) ErrCollectionNotFullyLoaded = newMilvusError("collection not fully loaded", 103, true) - ErrCollectionLoaded = newMilvusError("collection already loaded", 104, false) - ErrCollectionIllegalSchema = newMilvusError("illegal collection schema", 105, false) + ErrCollectionLoaded = newMilvusError("collection already loaded", 104, false, WithErrorType(InputError)) + ErrCollectionIllegalSchema = newMilvusError("illegal collection schema", 105, false, WithErrorType(InputError)) ErrCollectionOnRecovering = newMilvusError("collection on recovering", 106, true) - ErrCollectionVectorClusteringKeyNotAllowed = newMilvusError("vector clustering key not allowed", 107, false) + ErrCollectionVectorClusteringKeyNotAllowed = newMilvusError("vector clustering key not allowed", 107, false, WithErrorType(InputError)) // Deprecated, keep it only for reserving the error code - ErrCollectionReplicateMode = newMilvusError("can't operate on the collection under standby mode", 108, false) - ErrCollectionSchemaMismatch = newMilvusError("collection schema mismatch", 109, false) + ErrCollectionReplicateMode = newMilvusError("can't operate on the collection under standby mode", 108, false, WithErrorType(InputError)) + ErrCollectionSchemaMismatch = newMilvusError("collection schema mismatch", 109, false, WithErrorType(InputError)) ErrCollectionSchemaVersionNotReady = newMilvusError("collection schema version not ready", 110, true) + // Partition related - ErrPartitionNotFound = newMilvusError("partition not found", 200, false) + ErrPartitionNotFound = newMilvusError("partition not found", 200, false) // SystemError by default; the proxy GetPartitionInfo name chokepoint stamps InputError for user-supplied partition names, while id-based lookups stay system. ErrPartitionNotLoaded = newMilvusError("partition not loaded", 201, false) ErrPartitionNotFullyLoaded = newMilvusError("partition not fully loaded", 202, true) @@ -83,13 +92,16 @@ var ( ErrGeneralCapacityExceeded = newMilvusError("general capacity exceeded", 250, false) // ResourceGroup related - ErrResourceGroupNotFound = newMilvusError("resource group not found", 300, false) - ErrResourceGroupAlreadyExist = newMilvusError("resource group already exist, but create with different config", 301, false) - ErrResourceGroupReachLimit = newMilvusError("resource group num reach limit", 302, false) - ErrResourceGroupIllegalConfig = newMilvusError("resource group illegal config", 303, false) + ErrResourceGroupNotFound = newMilvusError("resource group not found", 300, false, WithErrorType(InputError)) + ErrResourceGroupAlreadyExist = newMilvusError("resource group already exist, but create with different config", 301, false, WithErrorType(InputError)) + ErrResourceGroupReachLimit = newMilvusError("resource group num reach limit", 302, false, WithErrorType(InputError)) + ErrResourceGroupIllegalConfig = newMilvusError("resource group illegal config", 303, false, WithErrorType(InputError)) // go:deprecated - ErrResourceGroupNodeNotEnough = newMilvusError("resource group node not enough", 304, false) - ErrResourceGroupServiceAvailable = newMilvusError("resource group service available", 305, true) + ErrResourceGroupNodeNotEnough = newMilvusError("resource group node not enough", 304, false) + ErrResourceGroupServiceUnAvailable = newMilvusError("resource group service unavailable", 305, true) + // Deprecated: misspelled historical name kept as an alias for backward + // compatibility of the exported symbol; use ErrResourceGroupServiceUnAvailable. + ErrResourceGroupServiceAvailable = ErrResourceGroupServiceUnAvailable // Replica related ErrReplicaNotFound = newMilvusError("replica not found", 400, false) @@ -103,6 +115,11 @@ var ( ErrChannelCPExceededMaxLag = newMilvusError("channel checkpoint exceed max lag", 504, false) ErrChannelTSafeStalled = newMilvusError("channel tsafe stalled", 505, true) ErrChannelDroppedSentinel = newMilvusError("channel checkpoint is dropped sentinel", 506, false) + // Channel routed to the wrong node — the requested channel is not owned by + // the delegator/querynode that received the request. Typically caused by + // stale shard-map at the caller (proxy). Retryable so the caller can + // re-resolve the shard map and dispatch to the correct node. + ErrChannelMisrouted = newMilvusError("channel misrouted: not owned by this node", 507, true) // Segment related ErrSegmentNotFound = newMilvusError("segment not found", 600, false) @@ -119,13 +136,13 @@ var ( // Index related ErrIndexNotFound = newMilvusError("index not found", 700, false) ErrIndexNotSupported = newMilvusError("index type not supported", 701, false) - ErrIndexDuplicate = newMilvusError("index duplicates", 702, false) + ErrIndexDuplicate = newMilvusError("index duplicates", 702, false, WithErrorType(InputError)) ErrTaskDuplicate = newMilvusError("task duplicates", 703, false) // Database related - ErrDatabaseNotFound = newMilvusError("database not found", 800, false) - ErrDatabaseNumLimitExceeded = newMilvusError("exceeded the limit number of database", 801, false) - ErrDatabaseInvalidName = newMilvusError("invalid database name", 802, false) + ErrDatabaseNotFound = newMilvusError("database not found", 800, false) // SystemError by default (same reason as ErrCollectionNotFound); proxy boundary marks it InputError for users via WrapErrAsInputErrorWhen. + ErrDatabaseNumLimitExceeded = newMilvusError("exceeded the limit number of database", 801, false, WithErrorType(InputError)) + ErrDatabaseInvalidName = newMilvusError("invalid database name", 802, false, WithErrorType(InputError)) // Node related ErrNodeNotFound = newMilvusError("node not found", 901, false) @@ -141,6 +158,27 @@ var ( ErrIoUnexpectEOF = newMilvusError("unexpected EOF", 1002, true) ErrIoTooManyRequests = newMilvusError("too many requests", 1003, true) + // Serialization / deserialization step failed — a transformation pipeline + // (proto Marshal/Unmarshal, json encode/decode, arrow schema conversion etc.) + // could not complete. Use for "conversion process failed" semantics rather + // than "stored bytes are corrupt" — for the latter use ErrDataIntegrity. + ErrSerializationFailed = newMilvusError("data serialization or deserialization failed", 1004, false) + + // Data integrity check failed — bytes already on disk (binlog payload, + // event header extras, stats buffer) don't match the layout we expect from + // the schema (type mismatch, valuesRead vs rows mismatch, unknown column + // type, malformed event header). Likely permanent corruption; retry won't + // help. Distinct from ErrSerializationFailed (process step failed) and + // ErrParameterInvalid (caller-input we control). + ErrDataIntegrity = newMilvusError("data integrity check failed", 1009, false) + + // Storage subsystem fallback — used for non-IO / non-serde / non-client-input + // failures inside the storage package (transaction state machine, writer/reader + // lifecycle, FFI internal failures, ext-table metadata operations). + // Prefer ErrIo*/ErrSerializationFailed/ErrDataIntegrity/ErrParameterInvalid + // when they fit; reach for ErrStorage only when none of those describe the failure. + ErrStorage = newMilvusError("storage internal error", 1008, false, WithErrorType(SystemError)) + // Permanent errors - resource doesn't exist or access denied ErrIoPermissionDenied = newMilvusError("permission denied", 1005, false) ErrIoBucketNotFound = newMilvusError("bucket not found", 1006, false) @@ -152,9 +190,9 @@ var ( ErrIoEntityTooLarge = newMilvusError("entity too large", 1012, false) // Parameter related - ErrParameterInvalid = newMilvusError("invalid parameter", 1100, false) - ErrParameterMissing = newMilvusError("missing parameter", 1101, false) - ErrParameterTooLarge = newMilvusError("parameter too large", 1102, false) + ErrParameterInvalid = newMilvusError("invalid parameter", 1100, false, WithErrorType(InputError)) + ErrParameterMissing = newMilvusError("missing parameter", 1101, false, WithErrorType(InputError)) + ErrParameterTooLarge = newMilvusError("parameter too large", 1102, false, WithErrorType(InputError)) // Metrics related ErrMetricNotFound = newMilvusError("metric not found", 1200, false) @@ -168,27 +206,27 @@ var ( // Privilege related // this operation is denied because the user not authorized, user need to login in first - ErrPrivilegeNotAuthenticated = newMilvusError("not authenticated", 1400, false) + ErrPrivilegeNotAuthenticated = newMilvusError("not authenticated", 1400, false, WithErrorType(InputError)) // this operation is denied because the user has no permission to do this, user need higher privilege - ErrPrivilegeNotPermitted = newMilvusError("privilege not permitted", 1401, false) - ErrPrivilegeGroupInvalidName = newMilvusError("invalid privilege group name", 1402, false) + ErrPrivilegeNotPermitted = newMilvusError("privilege not permitted", 1401, false, WithErrorType(InputError)) + ErrPrivilegeGroupInvalidName = newMilvusError("invalid privilege group name", 1402, false, WithErrorType(InputError)) // Alias related - ErrAliasNotFound = newMilvusError("alias not found", 1600, false) + ErrAliasNotFound = newMilvusError("alias not found", 1600, false) // SystemError by default; the proxy alias tasks (Describe/Drop/Alter) stamp InputError for user-supplied alias names. ErrAliasCollectionNameConfilct = newMilvusError("alias and collection name conflict", 1601, false) ErrAliasAlreadyExist = newMilvusError("alias already exist", 1602, false) - ErrCollectionIDOfAliasNotFound = newMilvusError("collection id of alias not found", 1603, false) + ErrCollectionIDOfAliasNotFound = newMilvusError("collection id of alias not found", 1603, false) // Genuinely SystemError: an existing alias whose target collection id cannot be resolved is an internal mapping inconsistency, not user input — left unmarked. // field related - ErrFieldNotFound = newMilvusError("field not found", 1700, false) + ErrFieldNotFound = newMilvusError("field not found", 1700, false) // SystemError by default; proxy boundaries stamp InputError where the field name is user-supplied (e.g. group_by/anns field), while result-assembly lookups stay system. ErrFieldInvalidName = newMilvusError("field name invalid", 1701, false) // high-level restful api related - ErrNeedAuthenticate = newMilvusError("user hasn't authenticated", 1800, false) - ErrIncorrectParameterFormat = newMilvusError("can only accept json format request", 1801, false) - ErrMissingRequiredParameters = newMilvusError("missing required parameters", 1802, false) + ErrNeedAuthenticate = newMilvusError("user hasn't authenticated", 1800, false, WithErrorType(InputError)) + ErrIncorrectParameterFormat = newMilvusError("can only accept json format request", 1801, false, WithErrorType(InputError)) + ErrMissingRequiredParameters = newMilvusError("missing required parameters", 1802, false, WithErrorType(InputError)) ErrMarshalCollectionSchema = newMilvusError("fail to marshal collection schema", 1803, false) - ErrInvalidInsertData = newMilvusError("fail to deal the insert data", 1804, false) + ErrInvalidInsertData = newMilvusError("fail to deal the insert data", 1804, false, WithErrorType(InputError)) ErrInvalidSearchResult = newMilvusError("fail to parse search result", 1805, false) ErrCheckPrimaryKey = newMilvusError("please check the primary key and its' type can only in [int, string]", 1806, false) ErrHTTPRateLimit = newMilvusError("request is rejected by limiter", 1807, true) @@ -214,10 +252,22 @@ var ( errUnexpected = newMilvusError("unexpected error", (1<<16)-1, false) // import - ErrImportFailed = newMilvusError("importing data failed", 2100, false) + // ErrImportFailed is InputError by default: the import data layer + // (internal/util/importutilv2) parses the caller's files and the vast + // majority of its failures are malformed user data (bad JSON/CSV/Parquet, + // type/dim/schema mismatch). Server-side import orchestration and object-IO + // failures use ErrImportSysFailed instead. + ErrImportFailed = newMilvusError("importing data failed", 2100, false, WithErrorType(InputError)) + // ErrImportSysFailed is the server-side counterpart of ErrImportFailed: + // import job orchestration (job not found / no vchannels / restore / + // job-count backpressure) and object-storage IO failures in the readers. + // These are the operator's concern, not the caller's, so they stay + // SystemError and must not be bucketed as fail_input. + ErrImportSysFailed = newMilvusError("importing data failed on server side", 2101, false) // Search/Query related ErrInconsistentRequery = newMilvusError("inconsistent requery result", 2200, true) + ErrQueryPlan = newMilvusError("query plan failed", 2201, false, WithErrorType(InputError)) // Compaction ErrCompactionReadDeltaLogErr = newMilvusError("fail to read delta log", 2300, false) @@ -246,6 +296,13 @@ var ( ErrDataNodeSlotExhausted = newMilvusError("datanode slot exhausted", 2401, false) + // Function / runner execution failed — BM25 / MinHash / embedding / + // analyzer runner returned a malformed output (wrong type, empty, + // unexpected shape). Distinct from generic ServiceInternal: callers can + // pattern-match this code when they specifically care about function + // pipeline failures vs other internal errors. + ErrFunctionFailed = newMilvusError("function execution failed", 2400, false) + // Cipher/Encryption related ErrKMSKeyRevoked = newMilvusError("KMS key has been revoked, access denied", 2500, false) @@ -279,9 +336,45 @@ type milvusError struct { retriable bool errCode int32 errType ErrorType + // inner is an optional underlying error. When set, this milvusError relabels + // the chain with its own errCode/errType/retriable (so Code/IsRetryableErr + // report this sentinel, not the inner) while keeping the inner reachable via + // Unwrap so errors.Is(result, inner) still holds. Replaces wrappedMilvusError. + inner error } +// registeredCodes maps every sentinel code to the sentinel that owns it. +// milvusError.Is matches by code alone, so two sentinels sharing a code would +// silently satisfy errors.Is against each other; the registry also lets a +// wire code be mapped back to its baked classification (ErrorTypeOfCode). +var registeredCodes = make(map[int32]milvusError) + +// newMilvusError defines a package-level sentinel and registers its code, +// panicking at package init on a duplicate. Use makeMilvusError for +// non-sentinel values (e.g. reconstructing an error from a wire Status). func newMilvusError(msg string, code int32, retriable bool, options ...errorOption) milvusError { + if owner, dup := registeredCodes[code]; dup { + panic(fmt.Sprintf("merr: duplicate sentinel error code %d: %q vs %q", code, owner.msg, msg)) + } + err := makeMilvusError(msg, code, retriable, options...) + registeredCodes[code] = err + return err +} + +// ErrorTypeOfCode returns the baked classification of the sentinel that owns +// the given wire code, or SystemError for unregistered codes. Note this is +// the sentinel default only — boundary InputError marks stamped at runtime +// (WrapErrAsInputError) are not recoverable from the code. +func ErrorTypeOfCode(code int32) ErrorType { + if s, ok := registeredCodes[code]; ok { + return s.errType + } + return SystemError +} + +// makeMilvusError constructs a milvusError value without claiming its code in +// the sentinel registry. +func makeMilvusError(msg string, code int32, retriable bool, options ...errorOption) milvusError { err := milvusError{ msg: msg, detail: msg, @@ -300,6 +393,9 @@ func (e milvusError) code() int32 { } func (e milvusError) Error() string { + if e.inner != nil { + return e.msg + ": " + e.inner.Error() + } return e.msg } @@ -307,6 +403,13 @@ func (e milvusError) Detail() string { return e.detail } +// Unwrap exposes the optional inner error so errors.Is(result, inner) holds. +// Returns nil for plain sentinels / wrapFields values, leaving their behavior +// unchanged. +func (e milvusError) Unwrap() error { + return e.inner +} + func (e milvusError) Is(err error) bool { cause := errors.Cause(err) if cause, ok := cause.(milvusError); ok { @@ -315,6 +418,23 @@ func (e milvusError) Is(err error) bool { return false } +// GetErrorType implements the ErrorClassifier interface. +// It returns the predefined classification of the error (SystemError or InputError). +func (e milvusError) GetErrorType() ErrorType { + return e.errType +} + +// wrapInner builds a relabeling milvusError from a sentinel: it copies the +// sentinel's code/errType/retriable, attaches a contextual message, and keeps +// the inner error reachable via Unwrap. This is the unified replacement for +// wrappedMilvusError used by the WrapErrXxxErr factories. +func wrapInner(sentinel milvusError, msg string, inner error) milvusError { + sentinel.msg = msg + sentinel.detail = msg + sentinel.inner = inner + return sentinel +} + type multiErrors struct { errs []error } @@ -356,7 +476,24 @@ func Combine(errs ...error) error { if len(errs) == 0 { return nil } + // A single error must keep its own identity/code: wrapping it in + // multiErrors would make Unwrap return nil and degrade Code() to 65535. + if len(errs) == 1 { + return errs[0] + } return multiErrors{ errs, } } + +func Wrap(err error, msg string) error { + return errors.Wrap(err, msg) +} + +func Wrapf(err error, format string, args ...interface{}) error { + return errors.Wrapf(err, format, args...) +} + +func Mark(err error, reference error) error { + return markers.Mark(err, reference) +} diff --git a/pkg/util/merr/errors_test.go b/pkg/util/merr/errors_test.go index 28d5a4960b..97176bde1d 100644 --- a/pkg/util/merr/errors_test.go +++ b/pkg/util/merr/errors_test.go @@ -18,6 +18,7 @@ package merr import ( "context" + "fmt" "os" "testing" @@ -26,7 +27,6 @@ import ( "github.com/stretchr/testify/suite" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" - "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) type ErrSuite struct { @@ -34,7 +34,6 @@ type ErrSuite struct { } func (s *ErrSuite) SetupSuite() { - paramtable.Init() } func (s *ErrSuite) TestCode() { @@ -46,10 +45,21 @@ func (s *ErrSuite) TestCode() { s.Equal(CanceledCode, Code(context.Canceled)) s.Equal(errUnexpected.errCode, Code(errUnexpected)) - sameCodeErr := newMilvusError("new error", ErrCollectionNotFound.errCode, false) + sameCodeErr := makeMilvusError("new error", ErrCollectionNotFound.errCode, false) s.True(sameCodeErr.Is(ErrCollectionNotFound)) } +func (s *ErrSuite) TestDuplicateSentinelCodePanics() { + // milvusError.Is matches by code alone, so defining a second sentinel on an + // occupied code must fail loudly at init instead of silently satisfying + // errors.Is against an unrelated sentinel. + s.PanicsWithValue( + fmt.Sprintf("merr: duplicate sentinel error code %d: %q vs %q", + ErrCollectionNotFound.errCode, ErrCollectionNotFound.msg, "duplicate probe"), + func() { newMilvusError("duplicate probe", ErrCollectionNotFound.errCode, false) }, + ) +} + func (s *ErrSuite) TestStatus() { err := WrapErrCollectionNotFound(1) status := Status(err) @@ -104,7 +114,7 @@ func (s *ErrSuite) TestWrap() { s.ErrorIs(WrapErrResourceGroupReachLimit("test_ResourceGroup", 1, "failed to get ResourceGroup"), ErrResourceGroupReachLimit) s.ErrorIs(WrapErrResourceGroupIllegalConfig("test_ResourceGroup", nil, "failed to get ResourceGroup"), ErrResourceGroupIllegalConfig) s.ErrorIs(WrapErrResourceGroupNodeNotEnough("test_ResourceGroup", 1, 2, "failed to get ResourceGroup"), ErrResourceGroupNodeNotEnough) - s.ErrorIs(WrapErrResourceGroupServiceAvailable("test_ResourceGroup", "failed to get ResourceGroup"), ErrResourceGroupServiceAvailable) + s.ErrorIs(WrapErrResourceGroupServiceUnAvailable("test_ResourceGroup", "failed to get ResourceGroup"), ErrResourceGroupServiceUnAvailable) // Replica related s.ErrorIs(WrapErrReplicaNotFound(1, "failed to get replica"), ErrReplicaNotFound) @@ -179,6 +189,14 @@ func (s *ErrSuite) TestOldCode() { s.ErrorIs(OldCodeToMerr(commonpb.ErrorCode_RateLimit), ErrServiceRateLimit) s.ErrorIs(OldCodeToMerr(commonpb.ErrorCode_ForceDeny), ErrServiceQuotaExceeded) s.ErrorIs(OldCodeToMerr(commonpb.ErrorCode_UnexpectedError), errUnexpected) + + // Every parameter-class error must project to IllegalArgument for old SDKs + // that still read the deprecated ErrorCode; otherwise the finer-grained + // ParameterMissing/ParameterTooLarge codes regress them to UnexpectedError. + s.Equal(commonpb.ErrorCode_IllegalArgument, Status(ErrParameterInvalid).GetErrorCode()) + s.Equal(commonpb.ErrorCode_IllegalArgument, Status(ErrParameterMissing).GetErrorCode()) + s.Equal(commonpb.ErrorCode_IllegalArgument, Status(ErrParameterTooLarge).GetErrorCode()) + s.Equal(commonpb.ErrorCode_IllegalArgument, Status(WrapErrParameterMissingMsg("collection names cannot be empty")).GetErrorCode()) } func (s *ErrSuite) TestCombine() { @@ -228,7 +246,7 @@ func (s *ErrSuite) TestIsHealthy() { } for _, tc := range cases { s.Run(tc.code.String(), func() { - s.Equal(tc.expect, IsHealthy(tc.code) == nil) + s.Equal(tc.expect, CheckHealthy(tc.code) == nil) }) } } @@ -279,3 +297,88 @@ func TestNewIOErrors(t *testing.T) { func TestErrors(t *testing.T) { suite.Run(t, new(ErrSuite)) } + +// TestWrapErrPreservesInnerChain locks in the contract that WrapErr*Err helpers +// keep the inner error chain reachable via errors.Is while still attributing +// the milvus sentinel for code lookup. Regression guard against the previous +// implementation that string-flattened the inner error and lost its identity. +func TestWrapErrPreservesInnerChain(t *testing.T) { + inner := errors.New("inner-error") + + for _, tc := range []struct { + name string + wrap func() error + sentinel error + other error + code int32 + }{ + { + name: "ServiceInternal", + wrap: func() error { return WrapErrServiceInternalErr(inner, "ctx %s", "test") }, + sentinel: ErrServiceInternal, + other: ErrParameterInvalid, + code: ErrServiceInternal.errCode, + }, + { + name: "ParameterInvalid", + wrap: func() error { return WrapErrParameterInvalidErr(inner, "ctx %d", 42) }, + sentinel: ErrParameterInvalid, + other: ErrServiceInternal, + code: ErrParameterInvalid.errCode, + }, + { + name: "MqInternal", + wrap: func() error { return WrapErrMqInternal(inner, "step1", "step2") }, + sentinel: ErrMqInternal, + other: ErrServiceInternal, + code: ErrMqInternal.errCode, + }, + { + name: "BuildCompactionRequestFail", + wrap: func() error { return WrapErrBuildCompactionRequestFail(inner) }, + sentinel: ErrBuildCompactionRequestFail, + other: ErrServiceInternal, + code: ErrBuildCompactionRequestFail.errCode, + }, + { + name: "GetCompactionPlanResultFail", + wrap: func() error { return WrapErrGetCompactionPlanResultFail(inner) }, + sentinel: ErrGetCompactionPlanResultFail, + other: ErrServiceInternal, + code: ErrGetCompactionPlanResultFail.errCode, + }, + { + name: "OperationNotSupported", + wrap: func() error { return WrapErrOperationNotSupported(inner, "op %s", "drop") }, + sentinel: ErrOperationNotSupported, + other: ErrServiceInternal, + code: ErrOperationNotSupported.errCode, + }, + } { + t.Run(tc.name, func(t *testing.T) { + w := tc.wrap() + assert.True(t, errors.Is(w, tc.sentinel), "must match its sentinel") + assert.True(t, errors.Is(w, inner), "must preserve inner chain") + assert.False(t, errors.Is(w, tc.other), "must not match unrelated sentinel") + assert.Equal(t, tc.code, Code(w), "Code() must report sentinel's code") + assert.Contains(t, w.Error(), inner.Error(), "message must include inner") + + // The wrapped error must carry the SENTINEL's classification, not the + // inner error's. These walk via errors.As(err, &milvusError); without + // wrappedMilvusError.As they would resolve to the inner error and drop + // the sentinel's InputError / retriable marks. + assert.Equal(t, GetErrorType(tc.sentinel), GetErrorType(w), + "GetErrorType must match the sentinel's classification") + assert.Equal(t, IsRetryableErr(tc.sentinel), IsRetryableErr(w), + "IsRetryableErr must match the sentinel's retriable flag") + if GetErrorType(tc.sentinel) == InputError { + assert.Equal(t, "true", Status(w).ExtraInfo[InputErrorFlagKey], + "Status must surface is_input_error for an InputError sentinel") + assert.False(t, Status(w).Retriable, "InputError status must be non-retriable") + } + }) + } + + // nil err falls back to the Msg variant; just make sure that still works. + assert.True(t, errors.Is(WrapErrServiceInternalErr(nil, "no underlying err"), ErrServiceInternal)) +} diff --git a/pkg/util/merr/segcore.go b/pkg/util/merr/segcore.go new file mode 100644 index 0000000000..8b7bc36f76 --- /dev/null +++ b/pkg/util/merr/segcore.go @@ -0,0 +1,188 @@ +// 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 merr + +import "github.com/cockroachdb/errors" + +// segcore error codes are produced by the C++ core (milvus::ErrorCode, defined +// in milvus-common's EasyAssert.h, value range 2000-2099) and travel to Go via +// the CGO CStatus{error_code, error_msg} boundary. Historically two Go paths +// consumed them inconsistently: +// +// - the direct path (SegcoreError) passed the raw C++ code straight through, +// so merr.Code returned an opaque number with no sentinel identity; +// - the wrapper path (the cgo helpers in analyzer/textmatch/index wrappers) +// hand-wrote `if errorCode == 2003/2033` switches, an drift-prone source. +// +// classifySegcoreError is the single source of truth shared by both paths. It +// maps a C++ code to the right merr sentinel (so errors.Is works), carries the +// original code in the segcoreCode field (so the precise code is never lost), +// and applies the error-type classification. Codes not present in the table +// fall back to ErrSegcore, so an unknown / newly-added C++ code is always +// captured safely (non-retriable system error) rather than dropped — it is +// simply unclassified until registered here. + +// segcoreClass describes how a single C++ ErrorCode is surfaced in Go. +type segcoreClass struct { + // sentinel is the merr sentinel this code is mapped to. errors.Is against + // it must keep working for existing callers. + sentinel milvusError + // inputError marks codes that are the caller's fault (malformed request), + // so they are classified as InputError at the boundary. + inputError bool + // signal marks control-flow "errors" that the caller treats as a normal + // outcome (e.g. pretend-finished / cluster-skip), not a failure. Callers + // that need the signal semantics match on the sentinel directly. + signal bool + // retriable marks transient system failures where a retry — possibly + // rerouted to another replica/node — can succeed: object-storage / local-IO + // errors, OOM, and field-not-loaded. inputError codes are non-retriable by + // construction and never set this; permanent system failures (corruption, + // config, internal bug, missing object) leave it false. + retriable bool +} + +// segcoreCodeTable is the registry of known C++ segcore error codes. Codes +// absent here fall back to ErrSegcore (see classifySegcoreError). +// +// Codes carry two orthogonal classifications: +// - inputError: the caller's fault (bad request) -> InputError, non-retriable +// by construction. +// - retriable: a transient system failure where a retry / reroute can succeed. +// +// They are mutually exclusive: a code is either the caller's fault (then a retry +// of the same request is pointless) or a server-side condition that is either +// transient (retriable) or permanent (neither flag). ConfigInvalid (2006) is a +// server-side yaml/config error (not the API caller's fault), so it is left as a +// plain system error until the C++ source splits its mixed user/server semantics. +var segcoreCodeTable = map[int32]segcoreClass{ + // Already-named segcore sentinels (identity preserved). + 2000: {sentinel: ErrSegcore}, + // C++ UnexpectedError(2001) is the generic catch-all the C++ core throws for + // any unclassified std::exception (EasyAssert.h default). It must stay generic + // ErrSegcore so the index/analyze scheduler retries it (master parity), NOT + // ErrSegcoreUnsupported — whose merr-code 2001 only coincides and would make + // scheduler.go fail the task permanently. The real C++ Unsupported is 2003. + 2001: {sentinel: ErrSegcore}, + // C++ NotImplemented(2002) is a real build/runtime failure, not a signal. + // ErrSegcorePretendFinished's merr-code 2002 only coincides; the real + // pretend-finished code is C++ ClusterSkip 2033. Keep generic so a failed + // build retries instead of being reported as JobStateFinished. + 2002: {sentinel: ErrSegcore}, + 2037: {sentinel: ErrSegcoreFollyOtherException, retriable: true}, // FollyOtherException (folly async failure; retry/reroute) + 2038: {sentinel: ErrSegcoreFollyCancel}, // FollyCancel (cancellation; not a pretend-finished signal — sentinel identity preserved, scheduler retries) + 2039: {sentinel: ErrSegcoreOutOfRange}, // OutOfRange (internal bounds bug, not a signal) + 2040: {sentinel: ErrSegcoreGCPNativeError, retriable: true}, // GcpNativeError (object storage; transient) + 2099: {sentinel: KnowhereError}, // KnowhereError + + // Wrapper-path special cases (preserve existing errors.Is behavior that + // datanode/index/scheduler.go relies on): + // 2003 Unsupported -> ErrSegcoreUnsupported (scheduler.go:221 matches) + // 2033 ClusterSkip -> ErrSegcorePretendFinished signal (scheduler.go:224) + 2003: {sentinel: ErrSegcoreUnsupported}, + 2033: {sentinel: ErrSegcorePretendFinished, signal: true}, + + // Caller-input errors (errType=input => non-retriable by construction). + 2020: {sentinel: ErrSegcore, inputError: true}, // FieldIDInvalid: field id not in schema + 2023: {sentinel: ErrSegcore, inputError: true}, // DataIsEmpty: indexing empty/all-null source data + 2025: {sentinel: ErrSegcore, inputError: true}, // JsonKeyInvalid + 2026: {sentinel: ErrSegcore, inputError: true}, // MetricTypeInvalid + 2028: {sentinel: ErrSegcore, inputError: true}, // ExprInvalid: filter expression invalid + 2031: {sentinel: ErrSegcore, inputError: true}, // MetricTypeNotMatch + 2032: {sentinel: ErrSegcore, inputError: true}, // DimNotMatch: query vector dim != schema + 2042: {sentinel: ErrSegcore, inputError: true}, // InvalidParameter: rescorer params + + // Transient system errors (retriable: a retry / reroute to another replica + // can succeed). + 2012: {sentinel: ErrSegcore, retriable: true}, // FileOpenFailed + 2014: {sentinel: ErrSegcore, retriable: true}, // FileReadFailed + 2015: {sentinel: ErrSegcore, retriable: true}, // FileWriteFailed + 2018: {sentinel: ErrSegcore, retriable: true}, // S3Error: object-storage transient (throttling/timeout) + 2027: {sentinel: ErrSegcore, retriable: true}, // FieldNotLoaded: another replica may have it loaded + 2034: {sentinel: ErrSegcore, retriable: true}, // MemAllocateFailed: OOM + 2036: {sentinel: ErrSegcore, retriable: true}, // MmapError + 2043: {sentinel: ErrSegcore, retriable: true}, // InsufficientResource + + // Permanent system errors registered explicitly so a future reader does not + // mistake them for "unclassified" and flip them to retriable. They map to the + // same non-retriable ErrSegcore as the fallback; the raw code is kept in + // segcoreCode. + 2004: {sentinel: ErrSegcore}, // IndexBuildError: build failed (bad data / permanent) + 2016: {sentinel: ErrSegcore}, // BucketInvalid: misconfigured bucket (same on every replica) + 2017: {sentinel: ErrSegcore}, // ObjectNotExist: object missing in shared storage (reroute won't help) + + // Previously-unclassified C++ codes registered explicitly (review §2): an + // unknown code still falls back to non-retriable ErrSegcore, but registering + // them lets the drift-guard test fail on any genuinely new/unmapped code and + // fixes the wrong-class fallback for the caller-input ones. + 2007: {sentinel: ErrSegcore, inputError: true}, // DataTypeInvalid: caller data type wrong + 2021: {sentinel: ErrSegcore, inputError: true}, // FieldAlreadyExist: caller adds a duplicate field + 2022: {sentinel: ErrSegcore, inputError: true}, // OpTypeInvalid: caller op type invalid + 2013: {sentinel: ErrSegcore, retriable: true}, // FileCreateFailed: transient IO (sibling of 2012/2014/2015) + 2005: {sentinel: ErrSegcore}, // IndexAlreadyBuild: internal state (proxy already dedups) + 2006: {sentinel: ErrSegcore}, // ConfigInvalid: mixed user/server config; default system, split later + 2009: {sentinel: ErrSegcore}, // PathInvalid (storage; classify with storage PR) + 2010: {sentinel: ErrSegcore}, // PathAlreadyExist (storage) + 2011: {sentinel: ErrSegcore}, // PathNotExist (storage) + 2019: {sentinel: ErrSegcore}, // RetrieveError: generic retrieve failure + 2024: {sentinel: ErrSegcore}, // DataFormatBroken: data corruption (permanent) + 2030: {sentinel: ErrSegcore}, // UnistdError: syscall failure + 2035: {sentinel: ErrSegcore}, // MemAllocateSizeNotMatch: size logic bug (not OOM) + 2041: {sentinel: ErrSegcore}, // TextIndexNotFound +} + +// classifySegcoreError converts a C++ segcore error code + message into a +// classified merr error. It is the shared entry point for both the direct +// (SegcoreError) and the wrapper cgo paths. +// +// The returned error: +// - matches errors.Is against the mapped sentinel (ErrSegcore as fallback); +// - carries the original C++ code in the segcoreCode field; +// - is marked InputError when the code is an unambiguous caller-input error; +// - is retriable only for transient system codes (object storage / IO / OOM / +// field-not-loaded); all other codes stay non-retriable. +func classifySegcoreError(code int32, msg string) error { + cls, ok := segcoreCodeTable[code] + if !ok { + cls = segcoreClass{sentinel: ErrSegcore} + } + + // Stamp the original C++ code into the segcoreCode field on the sentinel, + // then optionally wrap the message. The InputError mark must be applied to + // the milvusError *before* the errors.Wrap below, because WrapErrAsInputError + // only recognizes a bare milvusError, not a wrapped one. + base := cls.sentinel + if cls.inputError { + WithErrorType(InputError)(&base) + } + if cls.retriable { + base.retriable = true + } + err := wrapFields(base, value("segcoreCode", code)) + if msg != "" { + err = errors.Wrap(err, msg) + } + return err +} + +// IsSegcoreSignal reports whether a segcore error code is a control-flow signal +// (pretend-finished / cluster-skip) that callers treat as a normal outcome +// rather than a failure. +func IsSegcoreSignal(code int32) bool { + cls, ok := segcoreCodeTable[code] + return ok && cls.signal +} diff --git a/pkg/util/merr/segcore_test.go b/pkg/util/merr/segcore_test.go new file mode 100644 index 0000000000..ca443437f8 --- /dev/null +++ b/pkg/util/merr/segcore_test.go @@ -0,0 +1,205 @@ +// 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 merr + +import ( + "testing" + + "github.com/cockroachdb/errors" + "github.com/stretchr/testify/assert" +) + +func TestSegcoreErrorClassification(t *testing.T) { + // Sentinel identity must be preserved for the codes datanode/index + // scheduler relies on via errors.Is. + t.Run("pretend_finished_signal", func(t *testing.T) { + // Only C++ ClusterSkip(2033) is the pretend-finished signal scheduler.go + // matches via errors.Is. + err := SegcoreError(2033, "msg") + assert.ErrorIs(t, err, ErrSegcorePretendFinished) + assert.True(t, IsSegcoreSignal(2033)) + }) + + t.Run("not_implemented_is_not_pretend_finished", func(t *testing.T) { + // C++ NotImplemented(2002) must NOT map to the pretend-finished signal: + // ErrSegcorePretendFinished's merr-code 2002 only coincides, but C++ + // NotImplemented is a real build failure. It must stay generic ErrSegcore + // (system, non-signal) so getStateFromError retries it instead of + // reporting JobStateFinished. + err := SegcoreError(2002, "msg") + assert.ErrorIs(t, err, ErrSegcore) + assert.NotErrorIs(t, err, ErrSegcorePretendFinished) + assert.False(t, IsSegcoreSignal(2002)) + assert.Equal(t, SystemError, GetErrorType(err)) + }) + + t.Run("unsupported_identity", func(t *testing.T) { + // Unsupported(2003) must remain matchable as ErrSegcoreUnsupported + // (scheduler.go:221). + err := SegcoreError(2003, "msg") + assert.ErrorIs(t, err, ErrSegcoreUnsupported) + assert.False(t, IsSegcoreSignal(2003)) + }) + + t.Run("unexpected_error_is_not_unsupported", func(t *testing.T) { + // C++ UnexpectedError(2001) is the generic catch-all the C++ core throws + // for any unclassified exception; it must stay generic ErrSegcore (-> + // scheduler retry), NOT ErrSegcoreUnsupported (whose merr-code 2001 only + // coincides and would make scheduler.go fail the task permanently). + err := SegcoreError(2001, "msg") + assert.ErrorIs(t, err, ErrSegcore) + assert.NotErrorIs(t, err, ErrSegcoreUnsupported) + assert.False(t, IsSegcoreSignal(2001)) + assert.Equal(t, SystemError, GetErrorType(err)) + }) + + t.Run("named_sentinels", func(t *testing.T) { + assert.ErrorIs(t, SegcoreError(2038, "x"), ErrSegcoreFollyCancel) + assert.ErrorIs(t, SegcoreError(2039, "x"), ErrSegcoreOutOfRange) + assert.ErrorIs(t, SegcoreError(2099, "x"), KnowhereError) + }) + + t.Run("input_error_classification", func(t *testing.T) { + // Caller-input codes -> InputError, non-retriable by construction: + // FieldIDInvalid, DataIsEmpty, JsonKeyInvalid, MetricTypeInvalid, + // ExprInvalid, MetricTypeNotMatch, DimNotMatch, InvalidParameter. + for _, code := range []int32{2020, 2023, 2025, 2026, 2028, 2031, 2032, 2042} { + err := SegcoreError(code, "bad query") + assert.Equal(t, InputError, GetErrorType(err), "code %d", code) + assert.ErrorIs(t, err, ErrSegcore, "code %d", code) + // input error must be non-retriable at the boundary + assert.False(t, Status(err).GetRetriable(), "code %d", code) + } + }) + + t.Run("retriable_system_classification", func(t *testing.T) { + // Transient system codes (object storage / local IO / OOM / mmap / + // folly / field-not-loaded / insufficient-resource) -> retriable + // system errors, never InputError. + for _, code := range []int32{2012, 2014, 2015, 2018, 2027, 2034, 2036, 2037, 2040, 2043} { + err := SegcoreError(code, "transient failure") + assert.Equal(t, SystemError, GetErrorType(err), "code %d", code) + assert.True(t, Status(err).GetRetriable(), "code %d should be retriable", code) + } + }) + + t.Run("permanent_system_classification", func(t *testing.T) { + // Registered permanent system codes stay non-retriable system errors: + // IndexBuildError, BucketInvalid, ObjectNotExist. + for _, code := range []int32{2004, 2016, 2017} { + err := SegcoreError(code, "permanent failure") + assert.Equal(t, SystemError, GetErrorType(err), "code %d", code) + assert.False(t, Status(err).GetRetriable(), "code %d should not be retriable", code) + } + }) + + t.Run("system_error_default", func(t *testing.T) { + // A plain segcore error is a non-retriable system error. + err := SegcoreError(2000, "x") + assert.Equal(t, SystemError, GetErrorType(err)) + assert.ErrorIs(t, err, ErrSegcore) + assert.False(t, Status(err).GetRetriable()) + }) + + t.Run("unknown_code_fallback", func(t *testing.T) { + // An unregistered code must fall back to ErrSegcore safely, not be + // dropped or panic. + err := SegcoreError(2055, "future code") + assert.ErrorIs(t, err, ErrSegcore) + assert.Equal(t, SystemError, GetErrorType(err)) + assert.False(t, IsSegcoreSignal(2055)) + }) + + t.Run("wire_code_projection", func(t *testing.T) { + // Pins the client-visible contract: pass-through segcore codes collapse + // to ErrSegcore's wire code on Status, with the original C++ code kept + // in the Reason text. Anyone changing a sentinel's numeric code, the + // Code() extraction, or promoting a table entry to its own sentinel + // changes what clients receive — this test forces that to be explicit. + st := Status(SegcoreError(2028, "expr bad")) + assert.Equal(t, ErrSegcore.code(), st.GetCode()) + assert.Contains(t, st.GetReason(), "2028") + + // A named sentinel keeps its own distinct wire code. + assert.Equal(t, ErrSegcoreUnsupported.code(), Status(SegcoreError(2003, "x")).GetCode()) + + // An unregistered (future) code collapses safely as well. + assert.Equal(t, ErrSegcore.code(), Status(SegcoreError(9999, "x")).GetCode()) + }) + + t.Run("empty_message", func(t *testing.T) { + err := SegcoreError(2000, "") + assert.ErrorIs(t, err, ErrSegcore) + }) + + t.Run("message_wrapped", func(t *testing.T) { + err := SegcoreError(2000, "boom detail") + assert.Contains(t, err.Error(), "boom detail") + // still matchable after message wrap + assert.True(t, errors.Is(err, ErrSegcore)) + }) +} + +// TestSegcoreCodeTableCoverage cross-checks segcoreCodeTable against a +// hand-copied snapshot of the C++ ErrorCode enum. The enum's source of truth +// lives in the external milvus-common dependency (common/EasyAssert.h, fetched +// only at C++ build time), so it cannot be parsed from a Go test on a clean +// checkout — a new C++ code added upstream is therefore NOT detected here; it +// is caught at runtime by the safe fallback (collapse to plain non-retriable +// ErrSegcore, pinned in wire_code_projection above). It guards two things: +// - regression: codes we deliberately classified must stay registered with +// their intended class (a silent edit that drops one fails here); +// - drift: a C++ enum value not present in the table falls back to a plain +// non-retriable ErrSegcore — t.Log lists those so a maintainer adding a new +// C++ code is reminded to classify it here instead of letting it degrade. +func TestSegcoreCodeTableCoverage(t *testing.T) { + // C++ ErrorCode enum values (common/EasyAssert.h, 2000-2099). Keep in sync + // with the C++ side; a new value here that isn't in segcoreCodeTable is + // reported below. + cppCodes := []int32{ + 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2009, 2010, 2011, + 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, + 2023, 2024, 2025, 2026, 2027, 2028, 2030, 2031, 2032, 2033, 2034, + 2035, 2036, 2037, 2038, 2039, 2040, 2041, 2042, 2043, 2099, + } + + // Regression guard: the codes we classified on purpose must stay registered + // with the intended property. + wantInput := []int32{2007, 2020, 2021, 2022, 2023, 2025, 2026, 2028, 2031, 2032, 2042} + wantRetriable := []int32{2012, 2013, 2014, 2015, 2018, 2027, 2034, 2036, 2037, 2040, 2043} + for _, c := range wantInput { + cls, ok := segcoreCodeTable[c] + assert.True(t, ok && cls.inputError, "code %d must stay registered as inputError", c) + } + for _, c := range wantRetriable { + cls, ok := segcoreCodeTable[c] + assert.True(t, ok && cls.retriable, "code %d must stay registered as retriable", c) + } + + // Drift guard: every C++ ErrorCode must be classified explicitly. A new + // enum value added on the C++ side without a segcoreCodeTable entry fails + // here, forcing the author to decide input/retriable/system instead of + // silently degrading to the generic non-retriable ErrSegcore fallback. + var unregistered []int32 + for _, c := range cppCodes { + if _, ok := segcoreCodeTable[c]; !ok { + unregistered = append(unregistered, c) + } + } + assert.Empty(t, unregistered, "segcore C++ codes not classified in segcoreCodeTable; "+ + "register each explicitly (input / retriable / system) in pkg/util/merr/segcore.go: %v", unregistered) +} diff --git a/pkg/util/merr/utils.go b/pkg/util/merr/utils.go index f53af56f9a..7d476bbb54 100644 --- a/pkg/util/merr/utils.go +++ b/pkg/util/merr/utils.go @@ -19,16 +19,14 @@ package merr import ( "context" "fmt" + "math" "strings" "github.com/cockroachdb/errors" - "go.uber.org/zap" + "golang.org/x/exp/constraints" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb" - "github.com/milvus-io/milvus/pkg/v3/log" - "github.com/milvus-io/milvus/pkg/v3/util/logutil" - "github.com/milvus-io/milvus/pkg/v3/util/paramtable" ) const InputErrorFlagKey string = "is_input_error" @@ -40,20 +38,22 @@ func Code(err error) int32 { return 0 } - cause := errors.Cause(err) - switch specificErr := cause.(type) { - case milvusError: - return specificErr.code() - - default: - if errors.Is(specificErr, context.Canceled) { - return CanceledCode - } else if errors.Is(specificErr, context.DeadlineExceeded) { - return TimeoutCode - } else { - return errUnexpected.code() + // Walk the chain for the first milvusError (a sentinel, a wrapFields value, + // or a relabeling milvusError-with-inner). Stopping at the first match means + // a relabel reports its own code, not the inner's. + for cur := err; cur != nil; cur = errors.Unwrap(cur) { + if me, ok := cur.(milvusError); ok { + return me.code() } } + + cause := errors.Cause(err) + if errors.Is(cause, context.Canceled) { + return CanceledCode + } else if errors.Is(cause, context.DeadlineExceeded) { + return TimeoutCode + } + return errUnexpected.code() } func IsRetryableErr(err error) bool { @@ -111,8 +111,14 @@ func Status(err error) *commonpb.Status { code := Code(err) status := &commonpb.Status{ - Code: code, - Reason: previousLastError(err).Error(), + Code: code, + // Reason is the SDK/REST-visible message: the full composed chain, + // outermost context first ("outer context: ...: root cause"). The + // previous leaf-oriented heuristic (previousLastError) dropped the + // actionable outer context whenever the root cause was itself a + // nested error (e.g. strconv.NumError wrapping ErrSyntax), leaving + // clients with only the raw low-level cause. + Reason: err.Error(), // Deprecated, for compatibility ErrorCode: oldCode(code), Retriable: IsRetryableErr(err), @@ -121,23 +127,16 @@ func Status(err error) *commonpb.Status { if GetErrorType(err) == InputError { status.ExtraInfo = map[string]string{InputErrorFlagKey: "true"} + // Invariant enforced at the proxy boundary: an input error means the + // request is malformed, so retrying it unchanged can never succeed. + // Force Retriable=false even if the underlying sentinel is retriable, + // so clients never receive the self-contradictory "your input is wrong + // but you may retry" signal. + status.Retriable = false } return status } -func previousLastError(err error) error { - lastErr := err - for { - nextErr := errors.Unwrap(err) - if nextErr == nil { - break - } - lastErr = err - err = nextErr - } - return lastErr -} - func CheckRPCCall(resp any, err error) error { if err != nil { return err @@ -167,11 +166,13 @@ func StatusWithErrorCode(err error, code commonpb.ErrorCode) *commonpb.Status { return &commonpb.Status{} } - return &commonpb.Status{ - Code: Code(err), - Reason: err.Error(), - ErrorCode: code, - } + // Reuse Status so Retriable / Detail / the is_input_error flag are populated + // (and Retriable forced false for input errors). Otherwise the ~31 rootcoord + // RBAC/credential callers would round-trip genuine input errors as system. + // Only override the explicit wire ErrorCode the caller asked for. + st := Status(err) + st.ErrorCode = code + return st } func oldCode(code int32) commonpb.ErrorCode { @@ -182,7 +183,10 @@ func oldCode(code int32) commonpb.ErrorCode { case ErrCollectionNotFound.code(): return commonpb.ErrorCode_CollectionNotExists - case ErrParameterInvalid.code(): + case ErrParameterInvalid.code(), ErrParameterMissing.code(), ErrParameterTooLarge.code(): + // The legacy contract is that every parameter-class error surfaces as + // IllegalArgument, so the finer-grained 1101/1102 codes must not regress + // old SDKs (which still read the deprecated ErrorCode) to UnexpectedError. return commonpb.ErrorCode_IllegalArgument case ErrNodeNotMatch.code(): @@ -286,33 +290,23 @@ func Error(status *commonpb.Status) error { } var eType ErrorType - _, ok := status.GetExtraInfo()[InputErrorFlagKey] - if ok { + if status.GetExtraInfo()[InputErrorFlagKey] == "true" { eType = InputError } // use code first code := status.GetCode() if code == 0 { - return newMilvusError(status.GetReason(), Code(OldCodeToMerr(status.GetErrorCode())), false, WithDetail(status.GetDetail()), WithErrorType(eType)) + return makeMilvusError(status.GetReason(), Code(OldCodeToMerr(status.GetErrorCode())), false, WithDetail(status.GetDetail()), WithErrorType(eType)) } - return newMilvusError(status.GetReason(), code, status.GetRetriable(), WithDetail(status.GetDetail()), WithErrorType(eType)) + return makeMilvusError(status.GetReason(), code, status.GetRetriable(), WithDetail(status.GetDetail()), WithErrorType(eType)) } -// SegcoreError returns a merr according to the given segcore error code and message +// SegcoreError returns a merr according to the given segcore error code and +// message. Classification (sentinel identity, input-vs-system error type) is +// delegated to the shared segcore code table; see classifySegcoreError. func SegcoreError(code int32, msg string) error { - return newMilvusError(msg, code, false) -} - -// CheckHealthy checks whether the state is healthy, -// returns nil if healthy, -// otherwise returns ErrServiceNotReady wrapped with current state -func CheckHealthy(state commonpb.StateCode) error { - if state != commonpb.StateCode_Healthy { - return WrapErrServiceNotReady(paramtable.GetRole(), paramtable.GetNodeID(), state.String()) - } - - return nil + return classifySegcoreError(code, msg) } func IsHealthy(stateCode commonpb.StateCode) error { @@ -329,46 +323,87 @@ func IsHealthyOrStopping(stateCode commonpb.StateCode) error { return CheckHealthy(stateCode) } -func AnalyzeState(role string, nodeID int64, state *milvuspb.ComponentStates) error { - if err := Error(state.GetStatus()); err != nil { - return errors.Wrapf(err, "%s=%d not healthy", role, nodeID) - } else if state := state.GetState().GetStateCode(); state != commonpb.StateCode_Healthy { - return WrapErrServiceNotReady(role, nodeID, state.String()) - } - - return nil +// errorTypeMarker overrides the broad classification (Input/System) of the +// error it wraps, no matter how deep the milvus sentinel sits in the chain. +// GetErrorType finds it via the ErrorClassifier interface, so the mark works on +// bare milvusError values, *Msg (errors.Wrapf) results, and further-wrapped +// errors alike. It keeps the underlying error reachable via Unwrap, so Code / +// IsRetryableErr / errors.Is are unaffected. +type errorTypeMarker struct { + error + etype ErrorType } +func (m errorTypeMarker) Unwrap() error { return m.error } +func (m errorTypeMarker) GetErrorType() ErrorType { return m.etype } + func WrapErrAsInputError(err error) error { - if merr, ok := err.(milvusError); ok { - WithErrorType(InputError)(&merr) - return merr + if err == nil { + return nil } - return err + return errorTypeMarker{error: err, etype: InputError} +} + +func WrapErrAsSysError(err error) error { + if err == nil { + return nil + } + return errorTypeMarker{error: err, etype: SystemError} } func WrapErrAsInputErrorWhen(err error, targets ...milvusError) error { - if merr, ok := err.(milvusError); ok { - for _, target := range targets { - if target.errCode == merr.errCode { - log.Info("mark error as input error", zap.Error(err)) - WithErrorType(InputError)(&merr) - return merr - } + if err == nil { + return nil + } + code := Code(err) + for _, target := range targets { + if target.errCode == code { + return errorTypeMarker{error: err, etype: InputError} } } return err } +func WrapErrCollectionReplicateMode(operation string) error { + return wrapFields(ErrCollectionReplicateMode, value("operation", operation)) +} + func GetErrorType(err error) ErrorType { - if merr, ok := err.(milvusError); ok { - return merr.errType + // Find the outermost classifier in the chain: an explicit errorTypeMarker + // (from WrapErrAsInputError/SysError) takes precedence over the underlying + // milvusError's baked-in errType, so the mark works through any wrapping. + var ec ErrorClassifier + if errors.As(err, &ec) { + return ec.GetErrorType() } return SystemError } -// Service related +// keeps only 2 decimal places +func toMB[T constraints.Integer | constraints.Float](mem T) T { + return T(math.Round(float64(mem)/1024/1024*100) / 100) +} + +// CheckHealthy checks whether the state is healthy, +// returns nil if healthy, +// otherwise returns ErrServiceNotReady wrapped with current state +func CheckHealthy(stateCode commonpb.StateCode) error { + if stateCode != commonpb.StateCode_Healthy { + return Wrapf(ErrServiceNotReady, "state code: %s", stateCode.String()) + } + return nil +} + +func AnalyzeState(role string, nodeID int64, state *milvuspb.ComponentStates) error { + if err := Error(state.GetStatus()); err != nil { + return WrapErrServiceNotReady(role, nodeID, err.Error()) + } else if stateCode := state.GetState().GetStateCode(); stateCode != commonpb.StateCode_Healthy { + return WrapErrServiceNotReady(role, nodeID, stateCode.String()) + } + return nil +} + func WrapErrServiceNotReady(role string, sessionID int64, state string, msg ...string) error { err := wrapFieldsWithDesc(ErrServiceNotReady, state, @@ -380,6 +415,36 @@ func WrapErrServiceNotReady(role string, sessionID int64, state string, msg ...s return err } +// formatMsg renders a WrapErr* message. When no args are supplied the format is +// used verbatim (no Sprintf), so a '%' in dynamic content — e.g. +// WrapErrServiceInternalMsg(err.Error()) where the message contains "50%" — is +// not misinterpreted as a printf verb and rendered as "%!s(MISSING)" garbage. +// With args it formats as usual. +func formatMsg(format string, args ...any) string { + if len(args) == 0 { + return format + } + return fmt.Sprintf(format, args...) +} + +// wrapMsg attaches a (safely rendered) message to a sentinel for the +// WrapErr*Msg factories. When no args are supplied the format is used verbatim +// (errors.Wrap, no Sprintf), so a '%' in dynamic content — e.g. +// WrapErrServiceInternalMsg(err.Error()) where the message contains "50%" — is +// not misinterpreted as a printf verb. The args path uses errors.Wrapf rather +// than fmt.Sprintf so `go vet` does not classify the WrapErr*Msg helpers as +// printf wrappers and flag every non-constant-format callsite. +func wrapMsg(err error, format string, args ...any) error { + if len(args) == 0 { + return errors.Wrap(err, format) + } + return errors.Wrapf(err, format, args...) +} + +func WrapErrServiceNotReadyMsg(fmt string, args ...any) error { + return wrapMsg(ErrServiceNotReady, fmt, args...) +} + func WrapErrServiceUnavailable(reason string, msg ...string) error { err := wrapFieldsWithDesc(ErrServiceUnavailable, reason) if len(msg) > 0 { @@ -388,10 +453,14 @@ func WrapErrServiceUnavailable(reason string, msg ...string) error { return err } +func WrapErrServiceUnavailableMsg(fmt string, args ...any) error { + return wrapMsg(ErrServiceUnavailable, fmt, args...) +} + func WrapErrServiceMemoryLimitExceeded(predict, limit float32, msg ...string) error { err := wrapFields(ErrServiceMemoryLimitExceeded, - value("predict(MB)", logutil.ToMB(float64(predict))), - value("limit(MB)", logutil.ToMB(float64(limit))), + value("predict(MB)", toMB(float64(predict))), + value("limit(MB)", toMB(float64(limit))), ) if len(msg) > 0 { err = errors.Wrap(err, strings.Join(msg, "->")) @@ -417,6 +486,17 @@ func WrapErrServiceInternal(reason string, msg ...string) error { return err } +func WrapErrServiceInternalErr(err error, format string, args ...any) error { + if err == nil { + return WrapErrServiceInternalMsg(format, args...) + } + return wrapInner(ErrServiceInternal, formatMsg(format, args...), err) +} + +func WrapErrServiceInternalMsg(fmt string, args ...any) error { + return wrapMsg(ErrServiceInternal, fmt, args...) +} + func WrapErrServiceCrossClusterRouting(expectedCluster, actualCluster string, msg ...string) error { err := wrapFields(ErrServiceCrossClusterRouting, value("expectedCluster", expectedCluster), @@ -430,8 +510,8 @@ func WrapErrServiceCrossClusterRouting(expectedCluster, actualCluster string, ms func WrapErrServiceDiskLimitExceeded(predict, limit float32, msg ...string) error { err := wrapFields(ErrServiceDiskLimitExceeded, - value("predict(MB)", logutil.ToMB(float64(predict))), - value("limit(MB)", logutil.ToMB(float64(limit))), + value("predict(MB)", toMB(float64(predict))), + value("limit(MB)", toMB(float64(limit))), ) if len(msg) > 0 { err = errors.Wrap(err, strings.Join(msg, "->")) @@ -455,6 +535,10 @@ func WrapErrServiceQuotaExceeded(reason string, msg ...string) error { return err } +func WrapErrServiceQuotaExceededMsg(fmt string, args ...any) error { + return wrapMsg(ErrServiceQuotaExceeded, fmt, args...) +} + func WrapErrServiceUnimplemented(grpcErr error) error { return wrapFieldsWithDesc(ErrServiceUnimplemented, grpcErr.Error()) } @@ -720,15 +804,21 @@ func WrapErrResourceGroupNodeNotEnough(rg any, current any, expected any, msg .. return err } -// WrapErrResourceGroupServiceAvailable wraps ErrResourceGroupServiceAvailable with resource group -func WrapErrResourceGroupServiceAvailable(msg ...string) error { - err := wrapFields(ErrResourceGroupServiceAvailable) +// WrapErrResourceGroupServiceUnAvailable wraps ErrResourceGroupServiceUnAvailable with resource group +func WrapErrResourceGroupServiceUnAvailable(msg ...string) error { + err := wrapFields(ErrResourceGroupServiceUnAvailable) if len(msg) > 0 { err = errors.Wrap(err, strings.Join(msg, "->")) } return err } +// Deprecated: misspelled historical name kept for backward compatibility of the +// exported symbol; use WrapErrResourceGroupServiceUnAvailable. +func WrapErrResourceGroupServiceAvailable(msg ...string) error { + return WrapErrResourceGroupServiceUnAvailable(msg...) +} + // Replica related func WrapErrReplicaNotFound(id int64, msg ...string) error { err := wrapFields(ErrReplicaNotFound, value("replica", id)) @@ -784,6 +874,14 @@ func WrapErrChannelDroppedSentinel(name string, msg ...string) error { return warpChannelErr(ErrChannelDroppedSentinel, name, msg...) } +// WrapErrChannelMisrouted is used by a delegator/querynode when it receives a +// request for a channel it does not own. Encodes the requested channel name +// in the structured field; callers can put additional context (e.g. the list +// of channels the node actually owns) into msg. +func WrapErrChannelMisrouted(name string, msg ...string) error { + return warpChannelErr(ErrChannelMisrouted, name, msg...) +} + // Segment related func WrapErrSegmentNotFound(id int64, msg ...string) error { err := wrapFields(ErrSegmentNotFound, value("segment", id)) @@ -963,6 +1061,98 @@ func WrapErrNodeNotMatch(expectedNodeID, actualNodeID int64, msg ...string) erro return err } +// WrapErrSerializationFailedMsg creates a new ErrSerializationFailed (code 1004) +// with a detail message. Used when stored bytes cannot be decoded into the +// expected shape and there is no underlying error to wrap (e.g. valuesRead vs +// rows mismatch in payload reader, type mismatch when reading a column from +// the wrong DataType). +func WrapErrSerializationFailedMsg(format string, args ...any) error { + return wrapMsg(ErrSerializationFailed, format, args...) +} + +// WrapErrSerializationFailed wraps an existing underlying error 'err' with +// ErrSerializationFailed (code 1004). Used when a decode / unmarshal / +// schema-conversion step fails with a non-typed inner error (json, proto, +// arrow). If 'err' is already a typed merr, use merr.Wrap(err, msg) instead. +func WrapErrSerializationFailed(err error, format string, args ...any) error { + if err == nil { + return WrapErrSerializationFailedMsg(format, args...) + } + return wrapInner(ErrSerializationFailed, formatMsg(format, args...), err) +} + +// WrapErrDataIntegrityMsg creates a new ErrDataIntegrity (code 1009) with a +// detail message. Use when on-disk bytes don't conform to the expected schema +// (binlog type mismatch, valuesRead vs rows mismatch, malformed event header, +// unparseable stats buffer) — i.e. the stored data itself is corrupt, not the +// (de)serialization step. +func WrapErrDataIntegrityMsg(format string, args ...any) error { + return wrapMsg(ErrDataIntegrity, format, args...) +} + +// WrapErrDataIntegrity wraps an existing underlying error with ErrDataIntegrity +// (code 1009). Use when stored-byte parsing surfaces a non-typed inner error +// (json unmarshal of stats buffer, type assertion of decoded header extras). +// If 'err' is already a typed merr, use merr.Wrap(err, msg) instead. +func WrapErrDataIntegrity(err error, format string, args ...any) error { + if err == nil { + return WrapErrDataIntegrityMsg(format, args...) + } + return wrapInner(ErrDataIntegrity, formatMsg(format, args...), err) +} + +// WrapErrStorageMsg creates a new ErrStorage (code 1008) with a detail message. +// Used when a logical internal error occurs in the storage layer (e.g. invalid +// state, corrupted data structure check, nil data) and there is no underlying +// Go error to wrap. +func WrapErrStorageMsg(format string, args ...any) error { + return wrapMsg(ErrStorage, format, args...) +} + +// WrapErrStorage wraps an existing underlying error 'err' with ErrStorage +// (code 1008). Used for storage subsystem failures (transaction state machine, +// writer/reader lifecycle, FFI internal failures) that are not physical I/O, +// not serialization, and not client-input errors. +// +// IMPORTANT: only use when 'err' is a raw error (FFI / fs / proto / arrow). +// If 'err' is already a typed merr, use merr.Wrap(err, msg) instead — wrapping +// a typed merr here would mask its inner code (defect#3 pattern). +// +// merr.Code(result) returns ErrStorage.code() = 1008; errors.Is(result, ErrStorage) +// succeeds; errors.Is(result, err) also succeeds (inner chain preserved via Unwrap). +func WrapErrStorage(err error, format string, args ...any) error { + if err == nil { + return WrapErrStorageMsg(format, args...) + } + return wrapInner(ErrStorage, formatMsg(format, args...), err) +} + +// WrapErrFunctionFailedMsg creates a new ErrFunctionFailed (code 2400) with a +// detail message. Use when a function / BM25 / MinHash / analyzer runner +// returns a malformed output (wrong type, empty, unexpected shape) and there +// is no underlying Go error to wrap. +func WrapErrFunctionFailedMsg(format string, args ...any) error { + return wrapMsg(ErrFunctionFailed, format, args...) +} + +// WrapErrFunctionFailed wraps an existing underlying error 'err' with +// ErrFunctionFailed (code 2400). Use when a function-pipeline call surfaces +// a non-typed inner error (runner I/O, model invocation, dependency failure). +// +// IMPORTANT: only use when 'err' is a raw error. If 'err' is already a typed +// merr, use merr.Wrap(err, msg) instead — wrapping a typed merr here would +// mask its inner code (defect#3 pattern). +// +// merr.Code(result) returns ErrFunctionFailed.code() = 2400; errors.Is(result, +// ErrFunctionFailed) succeeds; errors.Is(result, err) also succeeds (inner +// chain preserved via Unwrap). +func WrapErrFunctionFailed(err error, format string, args ...any) error { + if err == nil { + return WrapErrFunctionFailedMsg(format, args...) + } + return wrapInner(ErrFunctionFailed, formatMsg(format, args...), err) +} + // IO related func WrapErrIoKeyNotFound(key string, msg ...string) error { err := wrapFields(ErrIoKeyNotFound, value("key", key)) @@ -987,6 +1177,10 @@ func WrapErrIoFailedReason(reason string, msg ...string) error { return err } +func WrapErrIoFailedMsg(fmt string, args ...any) error { + return wrapMsg(ErrIoFailed, fmt, args...) +} + func WrapErrIoUnexpectEOF(key string, err error) error { if err == nil { return nil @@ -1055,6 +1249,17 @@ func WrapErrParameterInvalid[T any](expected, actual T, msg ...string) error { return err } +// WrapErrParameterInvalidErr wraps an existing error 'err' with ErrParameterInvalid (Code 1100). +// This is used when an underlying error (e.g., from parsing, validation utility, or dependency) +// causes a parameter check to fail, and you need to provide extra context +// in 'format' and 'args'. +func WrapErrParameterInvalidErr(err error, format string, args ...any) error { + if err == nil { + return WrapErrParameterInvalidMsg(format, args...) + } + return wrapInner(ErrParameterInvalid, formatMsg(format, args...), err) +} + func WrapErrParameterInvalidRange[T any](lower, upper, actual T, msg ...string) error { err := wrapFields(ErrParameterInvalid, bound("value", actual, lower, upper), @@ -1066,7 +1271,7 @@ func WrapErrParameterInvalidRange[T any](lower, upper, actual T, msg ...string) } func WrapErrParameterInvalidMsg(fmt string, args ...any) error { - return errors.Wrapf(ErrParameterInvalid, fmt, args...) + return wrapMsg(ErrParameterInvalid, fmt, args...) } func WrapErrParameterMissing[T any](param T, msg ...string) error { @@ -1079,6 +1284,10 @@ func WrapErrParameterMissing[T any](param T, msg ...string) error { return err } +func WrapErrParameterMissingMsg(fmt string, args ...any) error { + return wrapMsg(ErrParameterMissing, fmt, args...) +} + func WrapErrParameterTooLarge(name string, msg ...string) error { err := wrapFields(ErrParameterTooLarge, value("message", name)) if len(msg) > 0 { @@ -1114,24 +1323,44 @@ func WrapErrMqTopicNotEmpty(name string, msg ...string) error { } func WrapErrMqInternal(err error, msg ...string) error { - err = wrapFieldsWithDesc(ErrMqInternal, err.Error()) - if len(msg) > 0 { - err = errors.Wrap(err, strings.Join(msg, "->")) + if err == nil { + return ErrMqInternal } - return err + ctx := ErrMqInternal.msg + if len(msg) > 0 { + ctx = strings.Join(msg, "->") + ": " + ctx + } + return wrapInner(ErrMqInternal, ctx, err) +} + +// WrapErrMqInternalMsg creates a new ErrMqInternal (code 1302) with a detail +// message. Use this when there is no underlying Go error to wrap. +func WrapErrMqInternalMsg(format string, args ...any) error { + return wrapMsg(ErrMqInternal, format, args...) } func WrapErrPrivilegeNotAuthenticated(fmt string, args ...any) error { - err := errors.Wrapf(ErrPrivilegeNotAuthenticated, fmt, args...) + err := wrapMsg(ErrPrivilegeNotAuthenticated, fmt, args...) return err } func WrapErrPrivilegeNotPermitted(fmt string, args ...any) error { - err := errors.Wrapf(ErrPrivilegeNotPermitted, fmt, args...) + err := wrapMsg(ErrPrivilegeNotPermitted, fmt, args...) return err } -// Segcore related +// WrapErrSegcoreMsg creates a new ErrSegcore (2000) with a Sprintf-formatted +// message. Use for Go-side segcore invariants where there's no C++ errorCode +// available. When a C++ errorCode is available (the CGO boundary), use +// SegcoreError(code, msg) instead, which classifies the code via the shared +// segcore code table (see segcore.go). +func WrapErrSegcoreMsg(format string, args ...any) error { + return wrapMsg(ErrSegcore, format, args...) +} + +// Deprecated: segcore error classification is now driven by the shared code +// table; use WrapErrSegcoreMsg. Kept for backward compatibility of the exported +// symbol. func WrapErrSegcore(code int32, msg ...string) error { err := wrapFields(ErrSegcore, value("segcoreCode", code)) if len(msg) > 0 { @@ -1140,6 +1369,9 @@ func WrapErrSegcore(code int32, msg ...string) error { return err } +// Deprecated: segcore error classification is now driven by the shared code +// table; use WrapErrSegcoreMsg. Kept for backward compatibility of the exported +// symbol. func WrapErrSegcoreUnsupported(code int32, msg ...string) error { err := wrapFields(ErrSegcoreUnsupported, value("segcoreCode", code)) if len(msg) > 0 { @@ -1230,6 +1462,30 @@ func WrapErrImportFailed(msg ...string) error { return err } +// WrapErrImportFailedMsg is the formatted variant of WrapErrImportFailed, +// matching the standard merr Msg-factory convention (errors.Wrapf) so callers +// pass a format string + args instead of an inline fmt.Sprintf. +func WrapErrImportFailedMsg(fmt string, args ...any) error { + return wrapMsg(ErrImportFailed, fmt, args...) +} + +// WrapErrImportSysFailed wraps ErrImportSysFailed: the server-side / object-IO +// import failures (job orchestration, backpressure, reader open/read) that are +// the operator's concern, not the caller's. Use it instead of +// WrapErrImportFailed wherever the failure is not caused by malformed user data. +func WrapErrImportSysFailed(msg ...string) error { + err := error(ErrImportSysFailed) + if len(msg) > 0 { + err = errors.Wrap(err, strings.Join(msg, "->")) + } + return err +} + +// WrapErrImportSysFailedMsg is the formatted variant of WrapErrImportSysFailed. +func WrapErrImportSysFailedMsg(fmt string, args ...any) error { + return wrapMsg(ErrImportSysFailed, fmt, args...) +} + func WrapErrInconsistentRequery(msg ...string) error { err := error(ErrInconsistentRequery) if len(msg) > 0 { @@ -1238,6 +1494,23 @@ func WrapErrInconsistentRequery(msg ...string) error { return err } +// WrapErrQueryPlanMsg creates a new ErrQueryPlan (code 2201) with a detail +// message. Use this when query plan parsing/validation fails and there is no +// underlying Go error to wrap. +func WrapErrQueryPlanMsg(format string, args ...any) error { + return wrapMsg(ErrQueryPlan, format, args...) +} + +// WrapErrQueryPlan wraps an existing underlying error with ErrQueryPlan +// (code 2201), preserving the inner error chain so callers can still match +// upstream sentinels via errors.Is/As. +func WrapErrQueryPlan(err error, format string, args ...any) error { + if err == nil { + return WrapErrQueryPlanMsg(format, args...) + } + return wrapInner(ErrQueryPlan, formatMsg(format, args...), err) +} + func WrapErrKMSKeyRevoked(dbID int64, reason string) error { return wrapFields(ErrKMSKeyRevoked, value("dbID", dbID), @@ -1260,6 +1533,10 @@ func WrapErrIllegalCompactionPlan(msg ...string) error { return err } +func WrapErrIllegalCompactionPlanMsg(format string, args ...any) error { + return wrapMsg(ErrIllegalCompactionPlan, format, args...) +} + func WrapErrCompactionPlanConflict(msg ...string) error { err := error(ErrCompactionPlanConflict) if len(msg) > 0 { @@ -1328,11 +1605,17 @@ func WrapErrAnalyzeTaskNotFound(id int64) error { } func WrapErrBuildCompactionRequestFail(err error) error { - return wrapFieldsWithDesc(ErrBuildCompactionRequestFail, err.Error()) + if err == nil { + return ErrBuildCompactionRequestFail + } + return wrapInner(ErrBuildCompactionRequestFail, ErrBuildCompactionRequestFail.msg, err) } func WrapErrGetCompactionPlanResultFail(err error) error { - return wrapFieldsWithDesc(ErrGetCompactionPlanResultFail, err.Error()) + if err == nil { + return ErrGetCompactionPlanResultFail + } + return wrapInner(ErrGetCompactionPlanResultFail, ErrGetCompactionPlanResultFail.msg, err) } func WrapErrCompactionResult(msg ...string) error { @@ -1393,3 +1676,18 @@ func WrapErrSnapshotPinned(name any, msg ...string) error { } return err } + +// WrapErrOperationNotSupportedMsg creates a new ErrOperationNotSupported with a detail message (Code 3000). +// This is the primary replacement for fmt.Errorf/errors.New for operations that are +// currently not supported by the system. +func WrapErrOperationNotSupportedMsg(format string, args ...any) error { + return wrapMsg(ErrOperationNotSupported, format, args...) +} + +// WrapErrOperationNotSupported wraps an existing error 'err' with ErrOperationNotSupported (Code 3000). +func WrapErrOperationNotSupported(err error, format string, args ...any) error { + if err == nil { + return WrapErrOperationNotSupportedMsg(format, args...) + } + return wrapInner(ErrOperationNotSupported, formatMsg(format, args...), err) +} diff --git a/pkg/util/merr/utils_test.go b/pkg/util/merr/utils_test.go index ea1a52b708..1d3dbde91c 100644 --- a/pkg/util/merr/utils_test.go +++ b/pkg/util/merr/utils_test.go @@ -99,6 +99,26 @@ func TestChannelTSafeStalledStatus(t *testing.T) { assert.True(t, IsRetryableErr(roundTripErr)) } +func TestStatusInputErrorForcesNonRetriable(t *testing.T) { + // Boundary invariant: an input error must never be reported as retriable, + // even when the underlying sentinel is retriable. Retrying a malformed + // request unchanged can never succeed. + retriable := ErrServiceUnavailable // retriable=true + assert.True(t, IsRetryableErr(retriable)) + + inputErr := WrapErrAsInputError(retriable) + assert.Equal(t, InputError, GetErrorType(inputErr)) + + status := Status(inputErr) + assert.False(t, status.GetRetriable(), "input error must be non-retriable at the boundary") + _, flagged := status.GetExtraInfo()[InputErrorFlagKey] + assert.True(t, flagged, "input error flag should still be set") + + // A non-input retriable error is unaffected. + plain := Status(retriable) + assert.True(t, plain.GetRetriable()) +} + func TestIsMilvusError_WrappedChain(t *testing.T) { // Direct milvus error assert.True(t, IsMilvusError(ErrCollectionNotFound)) diff --git a/pkg/util/metricsinfo/cache.go b/pkg/util/metricsinfo/cache.go index 1fb0318042..86bfb1fe09 100644 --- a/pkg/util/metricsinfo/cache.go +++ b/pkg/util/metricsinfo/cache.go @@ -21,6 +21,7 @@ import ( "time" "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // DefaultMetricsRetention defines the default retention of metrics cache. @@ -106,7 +107,7 @@ func (manager *MetricsCacheManager) GetSystemInfoMetrics() (*milvuspb.GetMetrics if manager.systemInfoMetricsInvalid || manager.systemInfoMetrics == nil || time.Since(manager.systemInfoMetricsLastUpdatedTime) >= retention { - return nil, errInvalidSystemInfosMetricCache + return nil, merr.WrapErrParameterInvalidMsg(msgInvalidSystemInfosMetricCache) } return manager.systemInfoMetrics, nil diff --git a/pkg/util/metricsinfo/err.go b/pkg/util/metricsinfo/err.go index 641784637e..215e1f6eaf 100644 --- a/pkg/util/metricsinfo/err.go +++ b/pkg/util/metricsinfo/err.go @@ -11,12 +11,8 @@ package metricsinfo -import "github.com/cockroachdb/errors" - const ( // MsgUnimplementedMetric represents that user requests an unimplemented metric type MsgUnimplementedMetric = "metric request type is not implemented" msgInvalidSystemInfosMetricCache = "system infos metric is invalid" ) - -var errInvalidSystemInfosMetricCache = errors.New(msgInvalidSystemInfosMetricCache) diff --git a/pkg/util/metricsinfo/metric_request.go b/pkg/util/metricsinfo/metric_request.go index 8e7ab845d5..6cf0e96bd5 100644 --- a/pkg/util/metricsinfo/metric_request.go +++ b/pkg/util/metricsinfo/metric_request.go @@ -17,7 +17,6 @@ import ( "fmt" "sync" - "github.com/cockroachdb/errors" "github.com/tidwall/gjson" "go.uber.org/zap" @@ -26,6 +25,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/common" "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/util/commonpbutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -146,7 +146,7 @@ func (mr *MetricsRequest) ExecuteMetricsRequest(ctx context.Context, req *milvus if !ok { mr.lock.Unlock() log.Warn("unimplemented metric request type", zap.String("req_type", reqType)) - return "", errors.New(MsgUnimplementedMetric) + return "", merr.WrapErrParameterInvalidMsg(MsgUnimplementedMetric) } mr.lock.Unlock() @@ -179,7 +179,7 @@ func ParseMetricRequestType(jsonRet gjson.Result) (string, error) { return v.String(), nil } - return "", fmt.Errorf("%s or %s not found in request", MetricTypeKey, MetricRequestTypeKey) + return "", merr.WrapErrParameterInvalidMsg("%s or %s not found in request", MetricTypeKey, MetricRequestTypeKey) } func ParseMetricProcessInRole(jsonRet gjson.Result) (string, error) { @@ -188,7 +188,7 @@ func ParseMetricProcessInRole(jsonRet gjson.Result) (string, error) { return v.String(), nil } - return "", fmt.Errorf("%s not found in request", MetricRequestProcessInRoleKey) + return "", merr.WrapErrParameterInvalidMsg("%s not found in request", MetricRequestProcessInRoleKey) } func GetCollectionIDFromRequest(jsonReq gjson.Result) int64 { @@ -205,7 +205,7 @@ func ConstructRequestByMetricType(metricType string) (*milvuspb.GetMetricsReques m[MetricTypeKey] = metricType binary, err := json.Marshal(m) if err != nil { - return nil, fmt.Errorf("failed to construct request by metric type %s: %s", metricType, err.Error()) + return nil, merr.WrapErrParameterInvalidMsg("failed to construct request by metric type %s: %s", metricType, err.Error()) } // TODO:: switch metricType to different msgType and return err when metricType is not supported return &milvuspb.GetMetricsRequest{ @@ -219,7 +219,7 @@ func ConstructRequestByMetricType(metricType string) (*milvuspb.GetMetricsReques func ConstructGetMetricsRequest(m map[string]interface{}) (*milvuspb.GetMetricsRequest, error) { binary, err := json.Marshal(m) if err != nil { - return nil, fmt.Errorf("failed to construct request: %s", err.Error()) + return nil, merr.WrapErrParameterInvalidMsg("failed to construct request: %s", err.Error()) } return &milvuspb.GetMetricsRequest{ diff --git a/pkg/util/paramtable/autoindex_param.go b/pkg/util/paramtable/autoindex_param.go index 2a5c6fd025..b8cd6fc2a2 100644 --- a/pkg/util/paramtable/autoindex_param.go +++ b/pkg/util/paramtable/autoindex_param.go @@ -19,11 +19,10 @@ package paramtable import ( "strconv" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/pkg/v3/common" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/metric" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -421,7 +420,7 @@ func GetBuildParamFormatter(defaultMetricsType metric.MetricType, tag string) fu return func(originValue string) string { m, err := funcutil.JSONToMap(originValue) if err != nil { - panic(errors.Wrapf(err, "failed to parse %s config value", tag)) + panic(merr.Wrapf(err, "failed to parse %s config value", tag)) } _, ok := m[common.MetricTypeKey] if ok { @@ -430,7 +429,7 @@ func GetBuildParamFormatter(defaultMetricsType metric.MetricType, tag string) fu m[common.MetricTypeKey] = defaultMetricsType ret, err := funcutil.MapToJSON(m) if err != nil { - panic(errors.Wrapf(err, "failed to convert updated %s map to json", tag)) + panic(merr.Wrapf(err, "failed to convert updated %s map to json", tag)) } return ret } diff --git a/pkg/util/paramtable/grpc_param.go b/pkg/util/paramtable/grpc_param.go index babc2d9c8e..a043a067ed 100644 --- a/pkg/util/paramtable/grpc_param.go +++ b/pkg/util/paramtable/grpc_param.go @@ -31,6 +31,7 @@ import ( "github.com/milvus-io/milvus/pkg/v3/log" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) const ( @@ -616,7 +617,7 @@ func (p *InternalTLSConfig) GetClientCreds(ctx context.Context) (credentials.Tra creds, err := credentials.NewClientTLSFromFile(caPemPath, sni) if err != nil { log.Ctx(ctx).Error("Failed to create internal TLS credentials", zap.Error(err)) - return nil, fmt.Errorf("failed to create internal TLS credentials: %w", err) + return nil, merr.Wrap(err, "failed to create internal TLS credentials") } return creds, nil } diff --git a/pkg/util/ratelimitutil/rate_collector.go b/pkg/util/ratelimitutil/rate_collector.go index a458c630cc..bdaed451bd 100644 --- a/pkg/util/ratelimitutil/rate_collector.go +++ b/pkg/util/ratelimitutil/rate_collector.go @@ -24,6 +24,8 @@ import ( "time" "github.com/samber/lo" + + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) const ( @@ -54,10 +56,10 @@ func NewRateCollector(window time.Duration, granularity time.Duration, enableSub // newRateCollector returns a new RateCollector with given window and granularity. func newRateCollector(window time.Duration, granularity time.Duration, now time.Time, enableSubLabel bool) (*RateCollector, error) { if window == 0 || granularity == 0 { - return nil, fmt.Errorf("create RateCollector failed, window or granularity cannot be 0, window = %d, granularity = %d", window, granularity) + return nil, merr.WrapErrParameterInvalidMsg("create RateCollector failed, window or granularity cannot be 0, window = %d, granularity = %d", window, granularity) } if window < granularity || window%granularity != 0 { - return nil, fmt.Errorf("create RateCollector failed, window has to be a multiplier of the granularity, window = %d, granularity = %d", window, granularity) + return nil, merr.WrapErrParameterInvalidMsg("create RateCollector failed, window has to be a multiplier of the granularity, window = %d, granularity = %d", window, granularity) } rc := &RateCollector{ window: window, @@ -233,7 +235,7 @@ func (r *RateCollector) max(label string, now time.Time) (float64, error) { } return max, nil } - return 0, fmt.Errorf("RateColletor didn't register for label %s", label) + return 0, merr.WrapErrParameterInvalidMsg("RateColletor didn't register for label %s", label) } // Min is shorthand for min(label, time.Now()). @@ -255,7 +257,7 @@ func (r *RateCollector) min(label string, now time.Time) (float64, error) { } return min, nil } - return 0, fmt.Errorf("RateColletor didn't register for label %s", label) + return 0, merr.WrapErrParameterInvalidMsg("RateColletor didn't register for label %s", label) } // Rate is shorthand for rate(label, duration, time.Now()). @@ -308,7 +310,7 @@ func (r *RateCollector) rate(label string, duration time.Duration, now time.Time } return total / float64(size), nil } - return 0, fmt.Errorf("RateColletor didn't register for label %s", label) + return 0, merr.WrapErrParameterInvalidMsg("RateColletor didn't register for label %s", label) } // update would update position and clear old values. diff --git a/pkg/util/replicateutil/config_helper.go b/pkg/util/replicateutil/config_helper.go index 39e652b11c..7c28360714 100644 --- a/pkg/util/replicateutil/config_helper.go +++ b/pkg/util/replicateutil/config_helper.go @@ -22,6 +22,7 @@ import ( "github.com/cockroachdb/errors" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" "github.com/milvus-io/milvus/pkg/v3/util/typeutil" ) @@ -101,7 +102,7 @@ func NewConfigHelper(currentClusterID string, cfg *commonpb.ReplicateConfigurati } } if primaryCount != 1 { - return nil, errors.Wrap(ErrWrongConfiguration, "primary count is not 1") + return nil, merr.Wrap(ErrWrongConfiguration, "primary count is not 1") } if _, ok := vs[currentClusterID]; !ok { return nil, ErrCurrentClusterNotFound @@ -109,7 +110,7 @@ func NewConfigHelper(currentClusterID string, cfg *commonpb.ReplicateConfigurati pchannels := len(vs[currentClusterID].Pchannels) for _, vertice := range vs { if len(vertice.Pchannels) != pchannels { - return nil, errors.Wrap(ErrWrongConfiguration, fmt.Sprintf("pchannel count is not equal for cluster %s", vertice.GetClusterId())) + return nil, merr.Wrapf(ErrWrongConfiguration, "pchannel count is not equal for cluster %s", vertice.GetClusterId()) } } h.currentClusterID = currentClusterID @@ -213,11 +214,11 @@ func (v *MilvusCluster) MustGetSourceChannel(pchannel string) string { // GetTargetChannel returns the target channel of the current cluster. func (v *MilvusCluster) GetTargetChannel(currentClusterPChannel string, targetClusterID string) (string, error) { if !v.targets.Contain(targetClusterID) { - return "", errors.Errorf("target cluster %s not found, current cluster is %s", targetClusterID, v.GetClusterId()) + return "", merr.WrapErrParameterInvalidMsg("target cluster %s not found, current cluster is %s", targetClusterID, v.GetClusterId()) } idx, ok := v.idxMap[currentClusterPChannel] if !ok { - return "", errors.Errorf("current cluster pchannel %s not found in the graph", currentClusterPChannel) + return "", merr.WrapErrServiceInternalMsg("current cluster pchannel %s not found in the graph", currentClusterPChannel) } target := v.h.vs[targetClusterID] return target.Pchannels[idx], nil diff --git a/pkg/util/replicateutil/config_validator.go b/pkg/util/replicateutil/config_validator.go index 3740500412..203c8d2a6a 100644 --- a/pkg/util/replicateutil/config_validator.go +++ b/pkg/util/replicateutil/config_validator.go @@ -23,6 +23,7 @@ import ( "strings" "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // ReplicateConfigValidator validates ReplicateConfiguration according to business rules @@ -50,11 +51,11 @@ func NewReplicateConfigValidator(incomingConfig, currentConfig *commonpb.Replica // Validate performs all validation checks on the configuration func (v *ReplicateConfigValidator) Validate() error { if v.incomingConfig == nil { - return fmt.Errorf("config cannot be nil") + return merr.WrapErrParameterInvalidMsg("config cannot be nil") } clusters := v.incomingConfig.GetClusters() if len(clusters) == 0 { - return fmt.Errorf("clusters list cannot be empty") + return merr.WrapErrParameterMissingMsg("clusters list cannot be empty") } // Perform all validation checks if err := v.validateClusterBasic(clusters); err != nil { @@ -86,47 +87,47 @@ func (v *ReplicateConfigValidator) validateClusterBasic(clusters []*commonpb.Mil uriSet := make(map[string]string) for i, cluster := range clusters { if cluster == nil { - return fmt.Errorf("cluster at index %d is nil", i) + return merr.WrapErrParameterInvalidMsg("cluster at index %d is nil", i) } // clusterID validation: non-empty and no whitespace clusterID := cluster.GetClusterId() if clusterID == "" { - return fmt.Errorf("cluster at index %d has empty clusterID", i) + return merr.WrapErrParameterInvalidMsg("cluster at index %d has empty clusterID", i) } if strings.ContainsAny(clusterID, " \t\n\r") { - return fmt.Errorf("cluster at index %d has clusterID '%s' containing whitespace characters", i, clusterID) + return merr.WrapErrParameterInvalidMsg("cluster at index %d has clusterID '%s' containing whitespace characters", i, clusterID) } // connection_param.uri validation: non-empty and basic URI format connParam := cluster.GetConnectionParam() if connParam == nil { - return fmt.Errorf("cluster '%s' has nil connection_param", clusterID) + return merr.WrapErrParameterInvalidMsg("cluster '%s' has nil connection_param", clusterID) } uri := connParam.GetUri() if uri == "" { - return fmt.Errorf("cluster '%s' has empty URI", clusterID) + return merr.WrapErrParameterInvalidMsg("cluster '%s' has empty URI", clusterID) } _, err := url.ParseRequestURI(uri) if err != nil { - return fmt.Errorf("cluster '%s' has invalid URI format: '%s'", clusterID, uri) + return merr.WrapErrParameterInvalidMsg("cluster '%s' has invalid URI format: '%s'", clusterID, uri) } // Check URI uniqueness if existingClusterID, exists := uriSet[uri]; exists { - return fmt.Errorf("duplicate URI found: '%s' is used by both cluster '%s' and cluster '%s'", uri, existingClusterID, clusterID) + return merr.WrapErrParameterInvalidMsg("duplicate URI found: '%s' is used by both cluster '%s' and cluster '%s'", uri, existingClusterID, clusterID) } uriSet[uri] = clusterID // pchannels validation: non-empty pchannels := cluster.GetPchannels() if len(pchannels) == 0 { - return fmt.Errorf("cluster '%s' has empty pchannels", clusterID) + return merr.WrapErrParameterInvalidMsg("cluster '%s' has empty pchannels", clusterID) } // pchannels uniqueness within cluster pchannelSet := make(map[string]bool) for j, pchannel := range pchannels { if pchannel == "" { - return fmt.Errorf("cluster '%s' has empty pchannel at index %d", clusterID, j) + return merr.WrapErrParameterInvalidMsg("cluster '%s' has empty pchannel at index %d", clusterID, j) } if pchannelSet[pchannel] { - return fmt.Errorf("cluster '%s' has duplicate pchannel: '%s'", clusterID, pchannel) + return merr.WrapErrParameterInvalidMsg("cluster '%s' has duplicate pchannel: '%s'", clusterID, pchannel) } pchannelSet[pchannel] = true } @@ -135,12 +136,12 @@ func (v *ReplicateConfigValidator) validateClusterBasic(clusters []*commonpb.Mil expectedPchannelCount = len(pchannels) firstClusterID = clusterID } else if len(pchannels) != expectedPchannelCount { - return fmt.Errorf("cluster '%s' has %d pchannels, but expected %d (same as cluster '%s')", + return merr.WrapErrParameterInvalidMsg("cluster '%s' has %d pchannels, but expected %d (same as cluster '%s')", clusterID, len(pchannels), expectedPchannelCount, firstClusterID) } // Build cluster maps if _, exists := v.clusterMap[clusterID]; exists { - return fmt.Errorf("duplicate clusterID found: '%s'", clusterID) + return merr.WrapErrParameterInvalidMsg("duplicate clusterID found: '%s'", clusterID) } v.clusterMap[clusterID] = cluster } @@ -151,10 +152,10 @@ func (v *ReplicateConfigValidator) validateClusterBasic(clusters []*commonpb.Mil func (v *ReplicateConfigValidator) validateRelevance() error { currentCluster, exists := v.clusterMap[v.currentClusterID] if !exists { - return fmt.Errorf("current Milvus cluster '%s' must be included in the clusters list", v.currentClusterID) + return merr.WrapErrParameterInvalidMsg("current Milvus cluster '%s' must be included in the clusters list", v.currentClusterID) } if !equalIgnoreOrder(v.currentPChannels, currentCluster.GetPchannels()) { - return fmt.Errorf("current pchannels do not match the pchannels in the config, current pchannels: %v, config pchannels: %v", v.currentPChannels, currentCluster.GetPchannels()) + return merr.WrapErrParameterInvalidMsg("current pchannels do not match the pchannels in the config, current pchannels: %v, config pchannels: %v", v.currentPChannels, currentCluster.GetPchannels()) } return nil } @@ -167,21 +168,21 @@ func (v *ReplicateConfigValidator) validateTopologyEdgeUniqueness(topologies []* edgeSet := make(map[string]struct{}) for i, topology := range topologies { if topology == nil { - return fmt.Errorf("topology at index %d is nil", i) + return merr.WrapErrParameterInvalidMsg("topology at index %d is nil", i) } sourceClusterID := topology.GetSourceClusterId() targetClusterID := topology.GetTargetClusterId() // Validate edge endpoints exist if _, exists := v.clusterMap[sourceClusterID]; !exists { - return fmt.Errorf("topology at index %d references non-existent source cluster: '%s'", i, sourceClusterID) + return merr.WrapErrParameterInvalidMsg("topology at index %d references non-existent source cluster: '%s'", i, sourceClusterID) } if _, exists := v.clusterMap[targetClusterID]; !exists { - return fmt.Errorf("topology at index %d references non-existent target cluster: '%s'", i, targetClusterID) + return merr.WrapErrParameterInvalidMsg("topology at index %d references non-existent target cluster: '%s'", i, targetClusterID) } // Edge uniqueness edgeKey := fmt.Sprintf("%s->%s", sourceClusterID, targetClusterID) if _, exists := edgeSet[edgeKey]; exists { - return fmt.Errorf("duplicate topology relationship found: '%s'", edgeKey) + return merr.WrapErrParameterInvalidMsg("duplicate topology relationship found: '%s'", edgeKey) } edgeSet[edgeKey] = struct{}{} } @@ -215,14 +216,14 @@ func (v *ReplicateConfigValidator) validateTopologyTypeConstraint(topologies []* if outDegree[clusterID] == clusterCount-1 && inDegree[clusterID] == 0 { if centerNode != "" { // Multiple center nodes found - return fmt.Errorf("multiple center nodes found, only one center node is allowed in star topology") + return merr.WrapErrParameterInvalidMsg("multiple center nodes found, only one center node is allowed in star topology") } centerNode = clusterID } } if centerNode == "" { // No center node found - return fmt.Errorf("no center node found, star topology must have exactly one center node") + return merr.WrapErrParameterInvalidMsg("no center node found, star topology must have exactly one center node") } // Validate other nodes (in-degree = 1, out-degree = 0) for clusterID := range v.clusterMap { @@ -230,7 +231,7 @@ func (v *ReplicateConfigValidator) validateTopologyTypeConstraint(topologies []* continue } if inDegree[clusterID] != 1 || outDegree[clusterID] != 0 { - return fmt.Errorf("cluster '%s' does not follow star topology pattern (in-degree=%d, out-degree=%d)", + return merr.WrapErrParameterInvalidMsg("cluster '%s' does not follow star topology pattern (in-degree=%d, out-degree=%d)", clusterID, inDegree[clusterID], outDegree[clusterID]) } } @@ -272,11 +273,11 @@ func (v *ReplicateConfigValidator) validateClusterConsistency(current, incoming currentPchannels := current.GetPchannels() incomingPchannels := incoming.GetPchannels() if len(incomingPchannels) < len(currentPchannels) { - return fmt.Errorf("cluster '%s' pchannels cannot decrease: current=%d, incoming=%d", + return merr.WrapErrParameterInvalidMsg("cluster '%s' pchannels cannot decrease: current=%d, incoming=%d", current.GetClusterId(), len(currentPchannels), len(incomingPchannels)) } if !slices.Equal(currentPchannels, incomingPchannels[:len(currentPchannels)]) { - return fmt.Errorf("cluster '%s' existing pchannels must be preserved at the same positions: current=%v, incoming=%v", + return merr.WrapErrParameterInvalidMsg("cluster '%s' existing pchannels must be preserved at the same positions: current=%v, incoming=%v", current.GetClusterId(), currentPchannels, incomingPchannels) } if len(incomingPchannels) > len(currentPchannels) { @@ -288,11 +289,11 @@ func (v *ReplicateConfigValidator) validateClusterConsistency(current, incoming incomingConn := incoming.GetConnectionParam() if currentConn.GetUri() != incomingConn.GetUri() { - return fmt.Errorf("cluster '%s' connection_param.uri cannot be changed: current=%s, incoming=%s", + return merr.WrapErrParameterInvalidMsg("cluster '%s' connection_param.uri cannot be changed: current=%s, incoming=%s", current.GetClusterId(), currentConn.GetUri(), incomingConn.GetUri()) } if currentConn.GetToken() != incomingConn.GetToken() { - return fmt.Errorf("cluster '%s' connection_param.token cannot be changed", + return merr.WrapErrParameterInvalidMsg("cluster '%s' connection_param.token cannot be changed", current.GetClusterId()) } diff --git a/pkg/util/requestutil/getter.go b/pkg/util/requestutil/getter.go index 6775065705..078b565a9c 100644 --- a/pkg/util/requestutil/getter.go +++ b/pkg/util/requestutil/getter.go @@ -19,6 +19,12 @@ package requestutil import ( + "context" + + "github.com/cockroachdb/errors" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "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/metrics" @@ -212,28 +218,69 @@ var retryableCode typeutil.Set[int32] = typeutil.NewSet( // ) // } +// ParseMetricLabel determines the final Prometheus status label based on the +// response and error. It implements the composite-label scheme: the coarse +// "fail"/"rejected" values are split into fine-grained labels (fail_input / +// fail_system / rejected_system) so monitoring can tell the responsible party +// apart. The split is encoded into the existing status label's value domain +// (additive cardinality) rather than a new dimension label (which would be +// multiplicative). Retryability takes priority over classification. func ParseMetricLabel(resp any, err error) string { - // err only returned by interceptors - if err != nil { - return metrics.RejectedLabel - } - - // check response status code - var status *commonpb.Status + // A response carrying a non-OK status means the request was PROCESSED and + // failed (fail_*), and takes priority over a non-nil err: the REST v2 + // wrappers reconstruct err = merr.Error(status) from that same response, + // which previously routed every processed REST failure into the rejected_* + // buckets and left the fail_* series blind to the entire REST surface. + var st *commonpb.Status switch resp := resp.(type) { case interface{ GetStatus() *commonpb.Status }: - status = resp.GetStatus() + st = resp.GetStatus() case *commonpb.Status: - status = resp + st = resp } - - // check if retry - if !merr.Ok(status) { - // TODO use retriable if all set - if retryableCode.Contain(status.GetCode()) { + if st != nil && !merr.Ok(st) { + // Client cancellation is neither party's failure. + if st.GetCode() == merr.CanceledCode { + return metrics.CancelLabel + } + // Retryability takes priority over input/system classification. + if retryableCode.Contain(st.GetCode()) { return metrics.RetryLabel } - return metrics.FailLabel + + // Hard failure: classify by responsible party. merr.Status already + // stamps the InputError flag into ExtraInfo, so read it directly instead + // of reconstructing the whole milvusError (this is the proxy hot path). + if st.GetExtraInfo()[merr.InputErrorFlagKey] == "true" { + return metrics.FailInputLabel + } + return metrics.FailSystemLabel + } + + // No usable response status: err is the interceptor-level outcome (context + // cancellation, flow control, transport issues, auth/privilege rejection) + // — the request was rejected around processing. Classify merr first: a + // merr error has no GRPCStatus(), so status.Code(err) degrades to + // codes.Unknown and would misbucket user input errors as system + // rejections. The auth/privilege interceptors deliberately return raw gRPC + // codes (not merr, to keep SDK retry behavior correct); those are the + // caller's fault, so bucket them as a user-side rejection. Everything else + // is a system-side rejection. + if err != nil { + // Client cancellation is neither party's failure; don't count it as a + // system rejection. + if errors.Is(err, context.Canceled) { + return metrics.CancelLabel + } + if merr.GetErrorType(err) == merr.InputError { + return metrics.RejectedUserLabel + } + switch status.Code(err) { + case codes.Unauthenticated, codes.PermissionDenied, codes.InvalidArgument: + return metrics.RejectedUserLabel + default: + return metrics.RejectedSystemLabel + } } return metrics.SuccessLabel } diff --git a/pkg/util/requestutil/getter_test.go b/pkg/util/requestutil/getter_test.go index 4d9cfc5a7f..fc07c65075 100644 --- a/pkg/util/requestutil/getter_test.go +++ b/pkg/util/requestutil/getter_test.go @@ -19,11 +19,17 @@ package requestutil import ( + "context" "reflect" "testing" + "github.com/cockroachdb/errors" + "github.com/stretchr/testify/assert" + "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" "github.com/milvus-io/milvus-proto/go-api/v3/milvuspb" + "github.com/milvus-io/milvus/pkg/v3/metrics" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) func TestGetCollectionNameFromRequest(t *testing.T) { @@ -508,3 +514,57 @@ func TestGetStatusFromResponse(t *testing.T) { }) } } + +func TestParseMetricLabel(t *testing.T) { + // transport / interceptor error -> rejected_system (highest priority) + assert.Equal(t, metrics.RejectedSystemLabel, + ParseMetricLabel(&commonpb.Status{}, errors.New("transport failed"))) + + // success + assert.Equal(t, metrics.SuccessLabel, + ParseMetricLabel(&commonpb.Status{}, nil)) + + // retryable hard failure -> retry (retryability beats classification) + assert.Equal(t, metrics.RetryLabel, + ParseMetricLabel(merr.Status(merr.ErrServiceRateLimit), nil)) + + // hard failure, system error -> fail_system + assert.Equal(t, metrics.FailSystemLabel, + ParseMetricLabel(merr.Status(merr.ErrSegcore), nil)) + + // hard failure, input error -> fail_input + inputErr := merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg("bad param")) + assert.Equal(t, metrics.FailInputLabel, + ParseMetricLabel(merr.Status(inputErr), nil)) + + // response that itself implements GetStatus + assert.Equal(t, metrics.FailInputLabel, + ParseMetricLabel(&milvuspb.BoolResponse{Status: merr.Status(inputErr)}, nil)) + + // merr input error through the err path (REST v2 handlers abort with merr + // directly; no GRPCStatus, so the gRPC switch alone would misbucket it as + // rejected_system) -> rejected_user + assert.Equal(t, metrics.RejectedUserLabel, + ParseMetricLabel(&commonpb.Status{}, merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg("bad param")))) + + // merr system error through the err path -> rejected_system + assert.Equal(t, metrics.RejectedSystemLabel, + ParseMetricLabel(&commonpb.Status{}, merr.WrapErrServiceInternalMsg("boom"))) + + // client cancellation through the err path -> cancel, not a system rejection + assert.Equal(t, metrics.CancelLabel, + ParseMetricLabel(&commonpb.Status{}, context.Canceled)) + assert.Equal(t, metrics.CancelLabel, + ParseMetricLabel(&commonpb.Status{}, errors.Wrap(context.Canceled, "rpc aborted"))) + + // REST v2 wrapper shape: the response carries the failed status AND the + // wrapper reconstructs err from it. Processed failures must classify by + // the status (fail_*), not be misrouted into the rejected_* buckets. + restSys := merr.Status(merr.ErrSegcore) + assert.Equal(t, metrics.FailSystemLabel, ParseMetricLabel(restSys, merr.Error(restSys))) + restInput := merr.Status(merr.WrapErrAsInputError(merr.WrapErrParameterInvalidMsg("bad param"))) + assert.Equal(t, metrics.FailInputLabel, ParseMetricLabel(restInput, merr.Error(restInput))) + // Cancellation surfaced through the response status stays out of fail_system. + restCancel := merr.Status(context.Canceled) + assert.Equal(t, metrics.CancelLabel, ParseMetricLabel(restCancel, merr.Error(restCancel))) +} diff --git a/pkg/util/resource/release_codec.go b/pkg/util/resource/release_codec.go index 779988da1a..f8d522a121 100644 --- a/pkg/util/resource/release_codec.go +++ b/pkg/util/resource/release_codec.go @@ -17,8 +17,6 @@ package resource import ( - "fmt" - "google.golang.org/grpc/encoding" "google.golang.org/grpc/mem" "google.golang.org/protobuf/proto" @@ -45,7 +43,7 @@ func (releaseCodec) Name() string { return "proto" } func (releaseCodec) Marshal(v any) (mem.BufferSlice, error) { msg := messageV2Of(v) if msg == nil { - return nil, merr.WrapErrServiceInternal(fmt.Sprintf("releaseCodec: %T does not implement proto.Message", v)) + return nil, merr.WrapErrServiceInternalMsg("releaseCodec: %T does not implement proto.Message", v) } // Only release messages that opt in via MsgPinnable. This avoids a map @@ -82,7 +80,7 @@ func (releaseCodec) Marshal(v any) (mem.BufferSlice, error) { func (releaseCodec) Unmarshal(data mem.BufferSlice, v any) error { msg := messageV2Of(v) if msg == nil { - return merr.WrapErrServiceInternal(fmt.Sprintf("releaseCodec: %T does not implement proto.Message", v)) + return merr.WrapErrServiceInternalMsg("releaseCodec: %T does not implement proto.Message", v) } buf := data.MaterializeToBuffer(mem.DefaultBufferPool()) diff --git a/pkg/util/retry/retry.go b/pkg/util/retry/retry.go index b1e0af5315..616ff41579 100644 --- a/pkg/util/retry/retry.go +++ b/pkg/util/retry/retry.go @@ -72,10 +72,23 @@ func Do(ctx context.Context, fn func() error, opts ...Option) error { } return err } - if c.isRetryErr != nil && !c.isRetryErr(err) { - log.Warn("retry func failed, not be retryable", + + // Caller-explicit RetryErr predicate takes precedence over the + // default InputError abort: when caller passes RetryErr they have + // decided which errors are retriable, framework must not override. + if c.isRetryErr != nil { + if !c.isRetryErr(err) { + log.Warn("retry func failed, not be retryable", + zap.Uint("retried", i), + zap.Uint("attempt", c.attempts), + zap.String("caller", getCaller(2)), + ) + return err + } + } else if merr.GetErrorType(err) == merr.InputError { + log.Warn("retry func failed, input error is non-retriable", zap.Uint("retried", i), - zap.Uint("attempt", c.attempts), + zap.Error(err), zap.String("caller", getCaller(2)), ) return err @@ -165,6 +178,23 @@ func Handle(ctx context.Context, fn func() (bool, error), opts ...Option) error return err } + // shouldRetry=true is the caller's explicit affirmative. Honor + // it. The optional RetryErr predicate is still consulted as a + // second gate, but unlike retry.Do the InputError default abort + // is intentionally not applied here: in retry.Handle the caller + // signals abort via shouldRetry=false, not via the error type, + // otherwise client-side cache-eviction patterns like + // retryIfSchemaError become unreachable for errors classified + // server-side as InputError (e.g. ErrCollectionSchemaMismatch). + if c.isRetryErr != nil && !c.isRetryErr(err) { + log.Warn("retry func failed, not be retryable", + zap.Uint("retried", i), + zap.Uint("attempt", c.attempts), + zap.String("caller", getCaller(2)), + ) + return err + } + deadline, ok := ctx.Deadline() if ok && time.Until(deadline) < c.sleep { isContextErr := errors.IsAny(err, context.Canceled, context.DeadlineExceeded) @@ -210,7 +240,11 @@ func Handle(ctx context.Context, fn func() (bool, error), opts ...Option) error return lastErr } -// errUnrecoverable is error instance for unrecoverable. +// errUnrecoverable is a private identity sentinel used only as a marker by +// Unrecoverable/IsRecoverable. It must NOT be a typed merr error: milvusError.Is +// compares by error code, so giving it a real code (e.g. ParameterInvalid) makes +// every error of that code spuriously match errUnrecoverable and corrupts the +// retriable/InputError classification of whatever was wrapped. var errUnrecoverable = errors.New("unrecoverable error") // Unrecoverable method wrap an error to unrecoverableError. This will make retry diff --git a/pkg/util/retry/retry_test.go b/pkg/util/retry/retry_test.go index 1d871820d3..ca2f7f8d2a 100644 --- a/pkg/util/retry/retry_test.go +++ b/pkg/util/retry/retry_test.go @@ -239,3 +239,53 @@ func TestHandle(t *testing.T) { }, Attempts(10)) assert.NoError(t, err) } + +// Regression: Handle must honor the caller's shouldRetry=true even when the +// returned error is server-classified InputError. This is the cross-case the +// client-side cache-eviction pattern in retryIfSchemaError depends on: +// ErrCollectionSchemaMismatch is tagged InputError server-side (the client +// sent a stale schema and the server rejected) but the client legitimately +// wants to evict its cache and retry. Before this case the framework's +// "default InputError abort" was firing after shouldRetry=true and +// retryIfSchemaError silently never retried. +func TestHandle_ShouldRetryOverridesInputErrorDefault(t *testing.T) { + counter := 0 + err := Handle(context.Background(), func() (bool, error) { + counter++ + if counter == 1 { + return true, merr.WrapErrCollectionSchemaMisMatch("mocked") + } + return false, nil + }, Attempts(10)) + + assert.NoError(t, err) + assert.Equal(t, 2, counter, + "Handle should retry once when caller returns shouldRetry=true on InputError") +} + +// Symmetric guard: shouldRetry=false on InputError still aborts immediately +// (this path was already correct; pinning it down so the regression fix +// above doesn't accidentally turn into "always retry InputError"). +func TestHandle_ShouldRetryFalseAbortsOnInputError(t *testing.T) { + counter := 0 + err := Handle(context.Background(), func() (bool, error) { + counter++ + return false, merr.WrapErrCollectionSchemaMisMatch("mocked") + }, Attempts(10)) + + assert.ErrorIs(t, err, merr.ErrCollectionSchemaMismatch) + assert.Equal(t, 1, counter) +} + +// retry.Do has no shouldRetry signal from the caller, so the InputError +// default abort remains the right behavior — keep it pinned. +func TestDo_InputErrorAbortsByDefault(t *testing.T) { + counter := 0 + err := Do(context.Background(), func() error { + counter++ + return merr.WrapErrCollectionSchemaMisMatch("mocked") + }, Attempts(10)) + + assert.ErrorIs(t, err, merr.ErrCollectionSchemaMismatch) + assert.Equal(t, 1, counter, "Do should abort on InputError without retrying") +} diff --git a/pkg/util/timestamptz/timestamptz.go b/pkg/util/timestamptz/timestamptz.go index 583c413dec..3135c923af 100644 --- a/pkg/util/timestamptz/timestamptz.go +++ b/pkg/util/timestamptz/timestamptz.go @@ -6,11 +6,10 @@ import ( "strings" "time" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/pkg/v3/common" "github.com/milvus-io/milvus/pkg/v3/util/funcutil" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // Define max/min offset boundaries in seconds for validation, exported for external checks if necessary. @@ -40,7 +39,7 @@ func validateOffset(t time.Time) (time.Time, error) { _, offsetSeconds := t.Zone() if offsetSeconds > MaxOffsetSeconds || offsetSeconds < MinOffsetSeconds { offsetHours := offsetSeconds / 3600 - return time.Time{}, fmt.Errorf("UTC offset hour %d is out of the valid range [%d, %d]", + return time.Time{}, merr.WrapErrParameterInvalidMsg("UTC offset hour %d is out of the valid range [%d, %d]", offsetHours, MinOffsetSeconds/3600, MaxOffsetSeconds/3600) } return t, nil @@ -73,7 +72,7 @@ func ParseTimeTz(inputStr string, defaultTimezoneStr string) (time.Time, error) loc, err := time.LoadLocation(defaultTimezoneStr) if err != nil { - return time.Time{}, fmt.Errorf("invalid default timezone string '%s': %w", defaultTimezoneStr, err) + return time.Time{}, merr.Wrapf(err, "invalid default timezone string '%s'", defaultTimezoneStr) } // 4. Fallback parsing: Attempt to parse a naive string using NaiveTzLayouts @@ -89,7 +88,7 @@ func ParseTimeTz(inputStr string, defaultTimezoneStr string) (time.Time, error) } if !parsed { - return time.Time{}, fmt.Errorf("invalid timestamp string: '%s'. Does not match any known format", inputStr) + return time.Time{}, merr.WrapErrParameterInvalidMsg("invalid timestamp string: '%s'. Does not match any known format", inputStr) } // No offset validation needed here: The time was assigned the safe defaultTimezoneStr (loc), @@ -132,13 +131,13 @@ func CompareUnixMicroTz(ts1 string, ts2 string, defaultTimezoneStr string) (bool // 1. Parse the first timestamp t1, err := ParseTimeTz(ts1, defaultTimezoneStr) if err != nil { - return false, fmt.Errorf("error parsing first timestamp '%s': %w", ts1, err) + return false, merr.Wrapf(err, "error parsing first timestamp '%s'", ts1) } // 2. Parse the second timestamp t2, err := ParseTimeTz(ts2, defaultTimezoneStr) if err != nil { - return false, fmt.Errorf("error parsing second timestamp '%s': %w", ts2, err) + return false, merr.Wrapf(err, "error parsing second timestamp '%s'", ts2) } // 3. Compare their Unix Microsecond values (int64) @@ -151,7 +150,7 @@ func CompareUnixMicroTz(ts1 string, ts2 string, defaultTimezoneStr string) (bool func ConvertUnixMicroToTimezoneString(ts int64, targetTimezoneStr string) (string, error) { loc, err := time.LoadLocation(targetTimezoneStr) if err != nil { - return "", fmt.Errorf("invalid target timezone string '%s': %w", targetTimezoneStr, err) + return "", merr.Wrapf(err, "invalid target timezone string '%s'", targetTimezoneStr) } // 1. Convert Unix Microsecond (UTC) to a time.Time object (still in UTC). @@ -391,7 +390,7 @@ func RewriteTimestampTzDefaultValueToString(schema *schemapb.CollectionSchema) e // In a real system, you might log the error and use the raw int64 as a fallback string, // but here we'll set a placeholder string to avoid crashing. tzString = fmt.Sprintf("error converting timestamp: %v", err) - return errors.Wrap(err, tzString) + return merr.Wrap(err, tzString) } // 5. Rewrite the default value field in the response schema. diff --git a/pkg/util/typeutil/convension.go b/pkg/util/typeutil/convension.go index 6c8fde3937..8e1a6d2bf5 100644 --- a/pkg/util/typeutil/convension.go +++ b/pkg/util/typeutil/convension.go @@ -18,7 +18,6 @@ package typeutil import ( "encoding/binary" - "fmt" "math" "reflect" @@ -26,6 +25,7 @@ import ( "google.golang.org/protobuf/proto" "github.com/milvus-io/milvus/pkg/v3/common" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // Generic Clone for proto message @@ -52,7 +52,7 @@ func BytesToFloat32(bytes []byte) float32 { // BytesToInt64 converts a byte slice to uint64. func BytesToInt64(b []byte) (int64, error) { if len(b) != 8 { - return 0, fmt.Errorf("failed to convert []byte to int64: invalid data, must 8 bytes, but %d", len(b)) + return 0, merr.WrapErrParameterInvalidMsg("failed to convert []byte to int64: invalid data, must 8 bytes, but %d", len(b)) } return int64(common.Endian.Uint64(b)), nil @@ -68,7 +68,7 @@ func Int64ToBytes(v int64) []byte { // BigEndianBytesToUint64 converts a byte slice (big endian) to uint64. func BigEndianBytesToUint64(b []byte) (uint64, error) { if len(b) != 8 { - return 0, fmt.Errorf("failed to convert []byte to uint64: invalid data, must 8 bytes, but %d", len(b)) + return 0, merr.WrapErrParameterInvalidMsg("failed to convert []byte to uint64: invalid data, must 8 bytes, but %d", len(b)) } // do not use little or common endian for compatibility issues(the msgid used in rocksmq is using this) @@ -85,7 +85,7 @@ func Uint64ToBytesBigEndian(v uint64) []byte { // BytesToUint64 converts a byte slice to uint64. func BytesToUint64(b []byte) (uint64, error) { if len(b) != 8 { - return 0, fmt.Errorf("failed to convert []byte to uint64: invalid data, must 8 bytes, but %d", len(b)) + return 0, merr.WrapErrParameterInvalidMsg("failed to convert []byte to uint64: invalid data, must 8 bytes, but %d", len(b)) } return common.Endian.Uint64(b), nil } diff --git a/pkg/util/typeutil/field_data.go b/pkg/util/typeutil/field_data.go index e89eb6a4ff..cd60a97836 100644 --- a/pkg/util/typeutil/field_data.go +++ b/pkg/util/typeutil/field_data.go @@ -1,9 +1,8 @@ package typeutil import ( - "fmt" - "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type FieldDataBuilder struct { @@ -28,7 +27,7 @@ func NewFieldDataBuilder(dt schemapb.DataType, fillZero bool, capacity int) (*Fi fillZero: fillZero, }, nil default: - return nil, fmt.Errorf("not supported field type: %s", dt.String()) + return nil, merr.WrapErrParameterInvalidMsg("not supported field type: %s", dt.String()) } } diff --git a/pkg/util/typeutil/field_schema.go b/pkg/util/typeutil/field_schema.go index ca51966a29..b2669f9aaa 100644 --- a/pkg/util/typeutil/field_schema.go +++ b/pkg/util/typeutil/field_schema.go @@ -1,13 +1,11 @@ package typeutil import ( - "fmt" "strconv" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/pkg/v3/common" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type FieldSchemaHelper struct { @@ -18,20 +16,20 @@ type FieldSchemaHelper struct { func (h *FieldSchemaHelper) GetDim() (int64, error) { if !IsVectorType(h.schema.GetDataType()) { - return 0, fmt.Errorf("%s is not of vector type", h.schema.GetDataType()) + return 0, merr.WrapErrParameterInvalidMsg("%s is not of vector type", h.schema.GetDataType()) } if IsSparseFloatVectorType(h.schema.GetDataType()) { - return 0, errors.New("typeutil.GetDim should not invoke on sparse vector type") + return 0, merr.WrapErrParameterInvalidMsg("typeutil.GetDim should not invoke on sparse vector type") } getDim := func(kvPairs *kvPairsHelper[string, string]) (int64, error) { dimStr, err := kvPairs.Get(common.DimKey) if err != nil { - return 0, errors.New("dim not found") + return 0, merr.WrapErrParameterInvalidMsg("dim not found") } dim, err := strconv.Atoi(dimStr) if err != nil { - return 0, fmt.Errorf("invalid dimension: %s", dimStr) + return 0, merr.WrapErrParameterInvalidMsg("invalid dimension: %s", dimStr) } return int64(dim), nil } diff --git a/pkg/util/typeutil/float_util.go b/pkg/util/typeutil/float_util.go index 3d61696808..45fa688691 100644 --- a/pkg/util/typeutil/float_util.go +++ b/pkg/util/typeutil/float_util.go @@ -18,12 +18,10 @@ package typeutil import ( "encoding/binary" - "fmt" "math" - "github.com/cockroachdb/errors" - "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) func bfloat16IsNaN(f uint16) bool { @@ -53,7 +51,7 @@ func float16IsInf(f uint16, sign int) bool { func VerifyFloat(value float64) error { // not allow not-a-number and infinity if math.IsNaN(value) || math.IsInf(value, -1) || math.IsInf(value, 1) { - return fmt.Errorf("value '%f' is not a number or infinity", value) + return merr.WrapErrParameterInvalidMsg("value '%f' is not a number or infinity", value) } return nil @@ -83,13 +81,13 @@ func VerifyFloats64(values []float64) error { func VerifyFloats16(value []byte) error { if len(value)%2 != 0 { - return errors.New("The length of float16 is not aligned to 2.") + return merr.WrapErrParameterInvalidMsg("The length of float16 is not aligned to 2.") } dataSize := len(value) / 2 for i := 0; i < dataSize; i++ { v := binary.LittleEndian.Uint16(value[i*2:]) if float16IsNaN(v) || float16IsInf(v, -1) || float16IsInf(v, 1) { - return errors.New("float16 vector contain nan or infinity value.") + return merr.WrapErrParameterInvalidMsg("float16 vector contain nan or infinity value.") } } return nil @@ -97,13 +95,13 @@ func VerifyFloats16(value []byte) error { func VerifyBFloats16(value []byte) error { if len(value)%2 != 0 { - return errors.New("The length of bfloat16 in not aligned to 2") + return merr.WrapErrParameterInvalidMsg("The length of bfloat16 in not aligned to 2") } dataSize := len(value) / 2 for i := 0; i < dataSize; i++ { v := binary.LittleEndian.Uint16(value[i*2:]) if bfloat16IsNaN(v) || bfloat16IsInf(v, -1) || bfloat16IsInf(v, 1) { - return errors.New("bfloat16 vector contain nan or infinity value.") + return merr.WrapErrParameterInvalidMsg("bfloat16 vector contain nan or infinity value.") } } return nil @@ -121,6 +119,6 @@ func ConvertFloat32ToFP16BF16Bytes(floatData []float32, dataType schemapb.DataTy converted := Float32ArrayToBFloat16Bytes(floatData) return converted, VerifyBFloats16(converted) default: - return nil, fmt.Errorf("unsupported fp16/bf16 vector type: %s", dataType.String()) + return nil, merr.WrapErrParameterInvalidMsg("unsupported fp16/bf16 vector type: %s", dataType.String()) } } diff --git a/pkg/util/typeutil/gen_empty_field_data.go b/pkg/util/typeutil/gen_empty_field_data.go index 79bee1e10f..e123091484 100644 --- a/pkg/util/typeutil/gen_empty_field_data.go +++ b/pkg/util/typeutil/gen_empty_field_data.go @@ -1,9 +1,8 @@ package typeutil import ( - "fmt" - "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) func genEmptyBoolFieldData(field *schemapb.FieldSchema) *schemapb.FieldData { @@ -325,6 +324,6 @@ func GenEmptyFieldData(field *schemapb.FieldSchema) (*schemapb.FieldData, error) case schemapb.DataType_ArrayOfVector: return genEmptyArrayOfVectorFieldData(field) default: - return nil, fmt.Errorf("unsupported data type: %s", dataType.String()) + return nil, merr.WrapErrParameterInvalidMsg("unsupported data type: %s", dataType.String()) } } diff --git a/pkg/util/typeutil/hash.go b/pkg/util/typeutil/hash.go index c988059aaa..cc9b2590e6 100644 --- a/pkg/util/typeutil/hash.go +++ b/pkg/util/typeutil/hash.go @@ -17,18 +17,17 @@ package typeutil import ( - "fmt" "hash/crc32" "math" "strconv" "strings" "unsafe" - "github.com/cockroachdb/errors" "github.com/spaolacci/murmur3" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/pkg/v3/common" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) const substringLengthForCRC = 100 @@ -131,10 +130,10 @@ func HashKey2Partitions(keys *schemapb.FieldData, partitionNames []string) ([]ui hashValues = append(hashValues, value%numPartitions) } default: - return nil, errors.New("currently only support DataType Int64 or VarChar as partition key Field") + return nil, merr.WrapErrParameterInvalidMsg("currently only support DataType Int64 or VarChar as partition key Field") } default: - return nil, errors.New("currently not support vector field as partition keys") + return nil, merr.WrapErrParameterInvalidMsg("currently not support vector field as partition keys") } return hashValues, nil @@ -148,14 +147,14 @@ func RearrangePartitionsForPartitionKey(partitions map[string]int64) ([]string, for partitionName, partitionID := range partitions { splits := strings.Split(partitionName, "_") if len(splits) < 2 { - return nil, nil, fmt.Errorf("bad default partion name in partition key mode: %s", partitionName) + return nil, nil, merr.WrapErrParameterInvalidMsg("bad default partion name in partition key mode: %s", partitionName) } index, err := strconv.ParseInt(splits[len(splits)-1], 10, 64) if err != nil { return nil, nil, err } if (index >= int64(len(partitions))) || (index < 0) { - return nil, nil, fmt.Errorf("illegal partition index in partition key mode: %s", partitionName) + return nil, nil, merr.WrapErrParameterInvalidMsg("illegal partition index in partition key mode: %s", partitionName) } partitionNames[index] = partitionName diff --git a/pkg/util/typeutil/ids_checker.go b/pkg/util/typeutil/ids_checker.go index 4ad7baf61d..f88d3cdef2 100644 --- a/pkg/util/typeutil/ids_checker.go +++ b/pkg/util/typeutil/ids_checker.go @@ -17,9 +17,8 @@ package typeutil import ( - "fmt" - "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) // IDsChecker provides efficient lookup functionality for schema.IDs @@ -55,7 +54,7 @@ func NewIDsChecker(ids *schemapb.IDs) (*IDsChecker, error) { checker.strIDSet[id] = struct{}{} } default: - return nil, fmt.Errorf("unsupported ID type in IDs") + return nil, merr.WrapErrParameterInvalidMsg("unsupported ID type in IDs") } return checker, nil @@ -66,13 +65,13 @@ func NewIDsChecker(ids *schemapb.IDs) (*IDsChecker, error) { // Returns error if cursor is out of bounds or type mismatch func (c *IDsChecker) Contains(idsA *schemapb.IDs, cursor int) (bool, error) { if idsA == nil || idsA.GetIdField() == nil { - return false, fmt.Errorf("idsA is nil or empty") + return false, merr.WrapErrParameterInvalidMsg("idsA is nil or empty") } // Check if cursor is within bounds size := GetSizeOfIDs(idsA) if cursor < 0 || cursor >= size { - return false, fmt.Errorf("cursor %d is out of bounds [0, %d)", cursor, size) + return false, merr.WrapErrParameterInvalidMsg("cursor %d is out of bounds [0, %d)", cursor, size) } // If checker is empty, return false for any query @@ -83,7 +82,7 @@ func (c *IDsChecker) Contains(idsA *schemapb.IDs, cursor int) (bool, error) { switch idsA.GetIdField().(type) { case *schemapb.IDs_IntId: if c.idType != schemapb.DataType_Int64 { - return false, fmt.Errorf("type mismatch: checker expects %v, got Int64", c.idType) + return false, merr.WrapErrParameterInvalidMsg("type mismatch: checker expects %v, got Int64", c.idType) } if c.intIDSet == nil { return false, nil @@ -94,7 +93,7 @@ func (c *IDsChecker) Contains(idsA *schemapb.IDs, cursor int) (bool, error) { case *schemapb.IDs_StrId: if c.idType != schemapb.DataType_VarChar { - return false, fmt.Errorf("type mismatch: checker expects %v, got VarChar", c.idType) + return false, merr.WrapErrParameterInvalidMsg("type mismatch: checker expects %v, got VarChar", c.idType) } if c.strIDSet == nil { return false, nil @@ -104,7 +103,7 @@ func (c *IDsChecker) Contains(idsA *schemapb.IDs, cursor int) (bool, error) { return exists, nil default: - return false, fmt.Errorf("unsupported ID type in idsA") + return false, merr.WrapErrParameterInvalidMsg("unsupported ID type in idsA") } } @@ -112,7 +111,7 @@ func (c *IDsChecker) Contains(idsA *schemapb.IDs, cursor int) (bool, error) { // Returns the indices of IDs that exist in the checker func (c *IDsChecker) ContainsAny(idsA *schemapb.IDs) ([]int, error) { if idsA == nil || idsA.GetIdField() == nil { - return nil, fmt.Errorf("idsA is nil or empty") + return nil, merr.WrapErrParameterInvalidMsg("idsA is nil or empty") } var result []int @@ -125,7 +124,7 @@ func (c *IDsChecker) ContainsAny(idsA *schemapb.IDs) ([]int, error) { switch idsA.GetIdField().(type) { case *schemapb.IDs_IntId: if c.idType != schemapb.DataType_Int64 { - return nil, fmt.Errorf("type mismatch: checker expects %v, got Int64", c.idType) + return nil, merr.WrapErrParameterInvalidMsg("type mismatch: checker expects %v, got Int64", c.idType) } if c.intIDSet == nil { return result, nil @@ -139,7 +138,7 @@ func (c *IDsChecker) ContainsAny(idsA *schemapb.IDs) ([]int, error) { case *schemapb.IDs_StrId: if c.idType != schemapb.DataType_VarChar { - return nil, fmt.Errorf("type mismatch: checker expects %v, got VarChar", c.idType) + return nil, merr.WrapErrParameterInvalidMsg("type mismatch: checker expects %v, got VarChar", c.idType) } if c.strIDSet == nil { return result, nil @@ -152,7 +151,7 @@ func (c *IDsChecker) ContainsAny(idsA *schemapb.IDs) ([]int, error) { } default: - return nil, fmt.Errorf("unsupported ID type in idsA") + return nil, merr.WrapErrParameterInvalidMsg("unsupported ID type in idsA") } return result, nil @@ -190,7 +189,7 @@ func (c *IDsChecker) GetIDType() schemapb.DataType { // Returns a slice of booleans indicating whether each cursor position exists in the checker func (c *IDsChecker) ContainsIDsAtCursors(idsA *schemapb.IDs, cursors []int) ([]bool, error) { if idsA == nil || idsA.GetIdField() == nil { - return nil, fmt.Errorf("idsA is nil or empty") + return nil, merr.WrapErrParameterInvalidMsg("idsA is nil or empty") } size := GetSizeOfIDs(idsA) @@ -198,7 +197,7 @@ func (c *IDsChecker) ContainsIDsAtCursors(idsA *schemapb.IDs, cursors []int) ([] for i, cursor := range cursors { if cursor < 0 || cursor >= size { - return nil, fmt.Errorf("cursor %d is out of bounds [0, %d)", cursor, size) + return nil, merr.WrapErrParameterInvalidMsg("cursor %d is out of bounds [0, %d)", cursor, size) } exists, err := c.Contains(idsA, cursor) diff --git a/pkg/util/typeutil/kv_pair_helper.go b/pkg/util/typeutil/kv_pair_helper.go index 9f5bce35bb..93cf248559 100644 --- a/pkg/util/typeutil/kv_pair_helper.go +++ b/pkg/util/typeutil/kv_pair_helper.go @@ -1,9 +1,8 @@ package typeutil import ( - "fmt" - "github.com/milvus-io/milvus-proto/go-api/v3/commonpb" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type kvPairsHelper[K comparable, V any] struct { @@ -13,7 +12,7 @@ type kvPairsHelper[K comparable, V any] struct { func (h *kvPairsHelper[K, V]) Get(k K) (V, error) { v, ok := h.kvPairs[k] if !ok { - return v, fmt.Errorf("%v not found", k) + return v, merr.WrapErrParameterInvalidMsg("%v not found", k) } return v, nil } diff --git a/pkg/util/typeutil/schema.go b/pkg/util/typeutil/schema.go index 89a51d2844..2c427674e6 100644 --- a/pkg/util/typeutil/schema.go +++ b/pkg/util/typeutil/schema.go @@ -29,12 +29,12 @@ import ( "strings" "unsafe" - "github.com/cockroachdb/errors" "github.com/samber/lo" "google.golang.org/protobuf/proto" "github.com/milvus-io/milvus-proto/go-api/v3/schemapb" "github.com/milvus-io/milvus/pkg/v3/common" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) type getVariableFieldLengthPolicy int @@ -58,7 +58,7 @@ func getVarFieldLength(fieldSchema *schemapb.FieldSchema, policy getVariableFiel case schemapb.DataType_VarChar: maxLengthPerRowValue, ok := paramsMap[common.MaxLengthKey] if !ok { - return 0, fmt.Errorf("the max_length was not specified, field type is %s", fieldSchema.DataType.String()) + return 0, merr.WrapErrParameterMissingMsg("the max_length was not specified, field type is %s", fieldSchema.DataType.String()) } maxLength, err = strconv.Atoi(maxLengthPerRowValue) if err != nil { @@ -79,7 +79,7 @@ func getVarFieldLength(fieldSchema *schemapb.FieldSchema, policy getVariableFiel } return maxLength, nil default: - return 0, fmt.Errorf("unrecognized getVariableFieldLengthPolicy %v", policy) + return 0, merr.WrapErrServiceInternalMsg("unrecognized getVariableFieldLengthPolicy %v", policy) } // Text type does not require max_length, use the same estimate as JSON/Array fields case schemapb.DataType_Text: @@ -88,7 +88,7 @@ func getVarFieldLength(fieldSchema *schemapb.FieldSchema, policy getVariableFiel case schemapb.DataType_Array, schemapb.DataType_JSON, schemapb.DataType_Geometry: return GetDynamicFieldEstimateLength(), nil default: - return 0, fmt.Errorf("field %s is not a variable-length type", fieldSchema.DataType.String()) + return 0, merr.WrapErrParameterInvalidMsg("field %s is not a variable-length type", fieldSchema.DataType.String()) } } @@ -107,7 +107,7 @@ func HasTextField(schema *schemapb.CollectionSchema) bool { func ValidateTextRequiresStorageV3(schema *schemapb.CollectionSchema, storageV3Enabled bool) error { if HasTextField(schema) && !storageV3Enabled { - return fmt.Errorf("TEXT field requires StorageV3; enable common.storage.useLoonFFI") + return merr.WrapErrParameterInvalidMsg("TEXT field requires StorageV3; enable common.storage.useLoonFFI") } return nil } @@ -226,7 +226,7 @@ func estimateSizeBy(schema *schemapb.CollectionSchema, policy getVariableFieldLe case schemapb.DataType_Int8Vector: res += assumedArrayLen * dim default: - return 0, fmt.Errorf("unsupported element type in VectorArray: %s", fs.ElementType.String()) + return 0, merr.WrapErrParameterInvalidMsg("unsupported element type in VectorArray: %s", fs.ElementType.String()) } } } @@ -318,12 +318,12 @@ func EstimateEntitySize(fieldsData []*schemapb.FieldData, rowOffset int, fieldId res += 8 case schemapb.DataType_VarChar, schemapb.DataType_Text: if rowOffset >= len(fs.GetScalars().GetStringData().GetData()) { - return 0, errors.New("offset out range of field datas") + return 0, merr.WrapErrParameterInvalidMsg("offset out range of field datas") } res += len(fs.GetScalars().GetStringData().Data[rowOffset]) case schemapb.DataType_Array: if rowOffset >= len(fs.GetScalars().GetArrayData().GetData()) { - return 0, errors.New("offset out range of field datas") + return 0, merr.WrapErrParameterInvalidMsg("offset out range of field datas") } array := fs.GetScalars().GetArrayData().GetData()[rowOffset] res += CalcScalarSize(&schemapb.FieldData{ @@ -332,12 +332,12 @@ func EstimateEntitySize(fieldsData []*schemapb.FieldData, rowOffset int, fieldId }) case schemapb.DataType_JSON: if rowOffset >= len(fs.GetScalars().GetJsonData().GetData()) { - return 0, errors.New("offset out range of field datas") + return 0, merr.WrapErrParameterInvalidMsg("offset out range of field datas") } res += len(fs.GetScalars().GetJsonData().GetData()[rowOffset]) case schemapb.DataType_Geometry: if rowOffset >= len(fs.GetScalars().GetGeometryData().GetData()) { - return 0, fmt.Errorf("offset out range of field datas") + return 0, merr.WrapErrParameterInvalidMsg("offset out range of field datas") } res += len(fs.GetScalars().GetGeometryData().GetData()[rowOffset]) case schemapb.DataType_BinaryVector, @@ -371,7 +371,7 @@ func EstimateEntitySize(fieldsData []*schemapb.FieldData, rowOffset int, fieldId case schemapb.DataType_ArrayOfVector: arrayVector := fs.GetVectors().GetVectorArray() if int(fieldIdx) >= len(arrayVector.GetData()) { - return 0, errors.New("offset out range of field datas") + return 0, merr.WrapErrParameterInvalidMsg("offset out range of field datas") } res += calcVectorSize(arrayVector.GetData()[fieldIdx], arrayVector.GetElementType()) default: @@ -398,7 +398,7 @@ type SchemaHelper struct { // CreateSchemaHelper returns a new SchemaHelper object func CreateSchemaHelper(schema *schemapb.CollectionSchema) (*SchemaHelper, error) { if schema == nil { - return nil, errors.New("schema is nil") + return nil, merr.WrapErrParameterInvalidMsg("schema is nil") } allFields := GetAllFieldSchemas(schema) @@ -415,37 +415,37 @@ func CreateSchemaHelper(schema *schemapb.CollectionSchema) (*SchemaHelper, error } for offset, field := range allFields { if _, ok := schemaHelper.nameOffset[field.Name]; ok { - return nil, fmt.Errorf("duplicated fieldName: %s", field.Name) + return nil, merr.WrapErrParameterInvalidMsg("duplicated fieldName: %s", field.Name) } if _, ok := schemaHelper.idOffset[field.FieldID]; ok { - return nil, fmt.Errorf("duplicated fieldID: %d", field.FieldID) + return nil, merr.WrapErrParameterInvalidMsg("duplicated fieldID: %d", field.FieldID) } schemaHelper.nameOffset[field.Name] = offset schemaHelper.idOffset[field.FieldID] = offset if field.IsPrimaryKey { if schemaHelper.primaryKeyOffset != -1 { - return nil, errors.New("primary key is not unique") + return nil, merr.WrapErrParameterInvalidMsg("primary key is not unique") } schemaHelper.primaryKeyOffset = offset } if field.IsPartitionKey { if schemaHelper.partitionKeyOffset != -1 { - return nil, errors.New("partition key is not unique") + return nil, merr.WrapErrParameterInvalidMsg("partition key is not unique") } schemaHelper.partitionKeyOffset = offset } if field.IsClusteringKey { if schemaHelper.clusteringKeyOffset != -1 { - return nil, errors.New("clustering key is not unique") + return nil, merr.WrapErrParameterInvalidMsg("clustering key is not unique") } schemaHelper.clusteringKeyOffset = offset } if field.IsDynamic { if schemaHelper.dynamicFieldOffset != -1 { - return nil, errors.New("dynamic field is not unique") + return nil, merr.WrapErrParameterInvalidMsg("dynamic field is not unique") } schemaHelper.dynamicFieldOffset = offset } @@ -473,7 +473,7 @@ func (helper *SchemaHelper) GetTimezone() string { // GetPrimaryKeyField returns the schema of the primary key func (helper *SchemaHelper) GetPrimaryKeyField() (*schemapb.FieldSchema, error) { if helper.primaryKeyOffset == -1 { - return nil, errors.New("failed to get primary key field: no primary in schema") + return nil, merr.WrapErrParameterInvalidMsg("failed to get primary key field: no primary in schema") } return helper.allFields[helper.primaryKeyOffset], nil } @@ -481,7 +481,7 @@ func (helper *SchemaHelper) GetPrimaryKeyField() (*schemapb.FieldSchema, error) // GetPartitionKeyField returns the schema of the partition key func (helper *SchemaHelper) GetPartitionKeyField() (*schemapb.FieldSchema, error) { if helper.partitionKeyOffset == -1 { - return nil, errors.New("failed to get partition key field: no partition key in schema") + return nil, merr.WrapErrParameterInvalidMsg("failed to get partition key field: no partition key in schema") } return helper.allFields[helper.partitionKeyOffset], nil } @@ -490,7 +490,7 @@ func (helper *SchemaHelper) GetPartitionKeyField() (*schemapb.FieldSchema, error // If not found, an error shall be returned. func (helper *SchemaHelper) GetClusteringKeyField() (*schemapb.FieldSchema, error) { if helper.clusteringKeyOffset == -1 { - return nil, errors.New("failed to get clustering key field: not clustering key in schema") + return nil, merr.WrapErrParameterInvalidMsg("failed to get clustering key field: not clustering key in schema") } return helper.allFields[helper.clusteringKeyOffset], nil } @@ -499,7 +499,7 @@ func (helper *SchemaHelper) GetClusteringKeyField() (*schemapb.FieldSchema, erro // if there is no dynamic field defined in schema, error will be returned. func (helper *SchemaHelper) GetDynamicField() (*schemapb.FieldSchema, error) { if helper.dynamicFieldOffset == -1 { - return nil, errors.New("failed to get dynamic field: no dynamic field in schema") + return nil, merr.WrapErrParameterInvalidMsg("failed to get dynamic field: no dynamic field in schema") } return helper.allFields[helper.dynamicFieldOffset], nil } @@ -508,7 +508,7 @@ func (helper *SchemaHelper) GetDynamicField() (*schemapb.FieldSchema, error) { func (helper *SchemaHelper) GetFieldFromName(fieldName string) (*schemapb.FieldSchema, error) { offset, ok := helper.nameOffset[fieldName] if !ok { - return nil, fmt.Errorf("failed to get field schema by name: fieldName(%s) not found", fieldName) + return nil, merr.WrapErrParameterInvalidMsg("failed to get field schema by name: fieldName(%s) not found", fieldName) } return helper.allFields[offset], nil } @@ -546,15 +546,14 @@ func (helper *SchemaHelper) getDefaultJSONField(fieldName string) (*schemapb.Fie return f, nil } } - errMsg := fmt.Sprintf("field %s not exist", fieldName) - return nil, fmt.Errorf("%s", errMsg) + return nil, merr.WrapErrParameterInvalidMsg("field %s not exist", fieldName) } // GetFieldFromID returns the schema of specified field func (helper *SchemaHelper) GetFieldFromID(fieldID int64) (*schemapb.FieldSchema, error) { offset, ok := helper.idOffset[fieldID] if !ok { - return nil, fmt.Errorf("fieldID(%d) not found", fieldID) + return nil, merr.WrapErrParameterInvalidMsg("fieldID(%d) not found", fieldID) } return helper.allFields[offset], nil } @@ -566,7 +565,7 @@ func (helper *SchemaHelper) GetVectorDimFromID(fieldID int64) (int, error) { return 0, err } if !IsVectorType(sch.DataType) { - return 0, fmt.Errorf("field type = %s not has dim", schemapb.DataType_name[int32(sch.DataType)]) + return 0, merr.WrapErrParameterInvalidMsg("field type = %s not has dim", schemapb.DataType_name[int32(sch.DataType)]) } for _, kv := range sch.TypeParams { if kv.Key == common.DimKey { @@ -577,7 +576,7 @@ func (helper *SchemaHelper) GetVectorDimFromID(fieldID int64) (int, error) { return dim, nil } } - return 0, fmt.Errorf("fieldID(%d) not has dim", fieldID) + return 0, merr.WrapErrParameterInvalidMsg("fieldID(%d) not has dim", fieldID) } func (helper *SchemaHelper) GetFunctionByOutputField(field *schemapb.FieldSchema) (*schemapb.FunctionSchema, error) { @@ -588,7 +587,7 @@ func (helper *SchemaHelper) GetFunctionByOutputField(field *schemapb.FieldSchema } } } - return nil, errors.New("function not exist") + return nil, merr.WrapErrParameterInvalidMsg("function not exist") } // As of now, only BM25 function output field is not supported to retrieve raw field data @@ -682,7 +681,7 @@ func NewEmptyArrayOfVectorRow(dim int64, elementType schemapb.DataType) (*schema case schemapb.DataType_Int8Vector: vf.Data = &schemapb.VectorField_Int8Vector{Int8Vector: []byte{}} default: - return nil, fmt.Errorf("unsupported ArrayOfVector element type %s", elementType) + return nil, merr.WrapErrParameterInvalidMsg("unsupported ArrayOfVector element type %s", elementType) } return vf, nil } @@ -1684,10 +1683,10 @@ func UpdateFieldData(base, update []*schemapb.FieldData, baseIdx, updateIdx int6 var updateMap map[string]interface{} // unmarshal base and update if err := json.Unmarshal(baseData.Data[baseIdx], &baseMap); err != nil { - return fmt.Errorf("failed to unmarshal base json: %v", err) + return merr.Wrap(err, "failed to unmarshal base json") } if err := json.Unmarshal(updateData.Data[updateIdx], &updateMap); err != nil { - return fmt.Errorf("failed to unmarshal update json: %v", err) + return merr.Wrap(err, "failed to unmarshal update json") } // merge for k, v := range updateMap { @@ -1696,7 +1695,7 @@ func UpdateFieldData(base, update []*schemapb.FieldData, baseIdx, updateIdx int6 // marshal back newJSON, err := json.Marshal(baseMap) if err != nil { - return fmt.Errorf("failed to marshal merged json: %v", err) + return merr.Wrap(err, "failed to marshal merged json") } baseScalar.GetJsonData().Data[baseIdx] = newJSON } else { @@ -1725,7 +1724,7 @@ func UpdateFieldData(base, update []*schemapb.FieldData, baseIdx, updateIdx int6 baseData.Data[baseIdx] = updateData.Data[updateIdx] } default: - return fmt.Errorf("unsupported scalar field type: %s", baseFieldData.Type.String()) + return merr.WrapErrParameterInvalidMsg("unsupported scalar field type: %s", baseFieldData.Type.String()) } case *schemapb.FieldData_Vectors: @@ -1806,10 +1805,10 @@ func UpdateFieldData(base, update []*schemapb.FieldData, baseIdx, updateIdx int6 } } default: - return fmt.Errorf("unsupported vector field type: %s", baseFieldData.Type.String()) + return merr.WrapErrParameterInvalidMsg("unsupported vector field type: %s", baseFieldData.Type.String()) } default: - return fmt.Errorf("unsupported field type: %s", baseFieldData.Type.String()) + return merr.WrapErrParameterInvalidMsg("unsupported field type: %s", baseFieldData.Type.String()) } } @@ -1835,15 +1834,15 @@ func UpdateArrayFieldByColumnWithOp( return nil } if len(baseIndices) != len(updateIndices) { - return fmt.Errorf("baseIndices and updateIndices length mismatch: %d vs %d", len(baseIndices), len(updateIndices)) + return merr.WrapErrParameterInvalidMsg("baseIndices and updateIndices length mismatch: %d vs %d", len(baseIndices), len(updateIndices)) } if base.GetType() != schemapb.DataType_Array { - return fmt.Errorf("op %s requires Array field, got %s", op.String(), base.GetType().String()) + return merr.WrapErrParameterInvalidMsg("op %s requires Array field, got %s", op.String(), base.GetType().String()) } baseScalar := base.GetScalars() updateScalar := update.GetScalars() if baseScalar.GetArrayData() == nil || updateScalar.GetArrayData() == nil { - return fmt.Errorf("op %s requires non-nil ArrayData on both base and update", op.String()) + return merr.WrapErrParameterInvalidMsg("op %s requires non-nil ArrayData on both base and update", op.String()) } baseData := baseScalar.GetArrayData().Data updateData := updateScalar.GetArrayData().Data @@ -1875,7 +1874,7 @@ func UpdateFieldDataByColumn(base, update *schemapb.FieldData, baseIndices, upda return nil } if len(baseIndices) != len(updateIndices) { - return fmt.Errorf("baseIndices and updateIndices length mismatch: %d vs %d", len(baseIndices), len(updateIndices)) + return merr.WrapErrParameterInvalidMsg("baseIndices and updateIndices length mismatch: %d vs %d", len(baseIndices), len(updateIndices)) } if IsCompactNullableVectorFieldData(base) { return updateCompactNullableVectorFieldDataByColumn(base, update, baseIndices, updateIndices) @@ -1950,10 +1949,10 @@ func UpdateFieldDataByColumn(base, update *schemapb.FieldData, baseIndices, upda var updateMap map[string]interface{} // unmarshal base and update if err := json.Unmarshal(baseData[baseIdx], &baseMap); err != nil { - return fmt.Errorf("failed to unmarshal base json: %v", err) + return merr.Wrap(err, "failed to unmarshal base json") } if err := json.Unmarshal(updateData[updateIdx], &updateMap); err != nil { - return fmt.Errorf("failed to unmarshal update json: %v", err) + return merr.Wrap(err, "failed to unmarshal update json") } // merge for k, v := range updateMap { @@ -1962,7 +1961,7 @@ func UpdateFieldDataByColumn(base, update *schemapb.FieldData, baseIndices, upda // marshal back newJSON, err := json.Marshal(baseMap) if err != nil { - return fmt.Errorf("failed to marshal merged json: %v", err) + return merr.Wrap(err, "failed to marshal merged json") } baseData[baseIdx] = newJSON } @@ -2060,26 +2059,26 @@ func IsCompactNullableVectorFieldData(field *schemapb.FieldData) bool { func updateCompactNullableVectorFieldDataByColumn(base, update *schemapb.FieldData, baseIndices, updateIndices []int64) error { if !IsSupportedNullableVectorType(update.GetType()) { - return fmt.Errorf("cannot update nullable vector field %s with %s", base.GetType().String(), update.GetType().String()) + return merr.WrapErrParameterInvalidMsg("cannot update nullable vector field %s with %s", base.GetType().String(), update.GetType().String()) } if update.GetType() != base.GetType() { - return fmt.Errorf("cannot update nullable vector field %s with %s", base.GetType().String(), update.GetType().String()) + return merr.WrapErrParameterInvalidMsg("cannot update nullable vector field %s with %s", base.GetType().String(), update.GetType().String()) } baseValidData := base.GetValidData() updateValidData := update.GetValidData() if len(updateValidData) == 0 { - return fmt.Errorf("nullable vector field %s missing ValidData", update.GetFieldName()) + return merr.WrapErrParameterInvalidMsg("nullable vector field %s missing ValidData", update.GetFieldName()) } updateByBaseIdx := make(map[int]int, len(baseIndices)) for i, baseIdx := range baseIndices { updateIdx := updateIndices[i] if baseIdx < 0 || int(baseIdx) >= len(baseValidData) { - return fmt.Errorf("base index %d out of range for nullable vector field %s", baseIdx, base.GetFieldName()) + return merr.WrapErrParameterInvalidMsg("base index %d out of range for nullable vector field %s", baseIdx, base.GetFieldName()) } if updateIdx < 0 || int(updateIdx) >= len(updateValidData) { - return fmt.Errorf("update index %d out of range for nullable vector field %s", updateIdx, update.GetFieldName()) + return merr.WrapErrParameterInvalidMsg("update index %d out of range for nullable vector field %s", updateIdx, update.GetFieldName()) } updateByBaseIdx[int(baseIdx)] = int(updateIdx) } @@ -2094,7 +2093,7 @@ func updateCompactNullableVectorFieldDataByColumn(base, update *schemapb.FieldDa baseVector := base.GetVectors() updateVector := update.GetVectors() if baseVector == nil || updateVector == nil { - return fmt.Errorf("nullable vector field data is nil") + return merr.WrapErrParameterInvalidMsg("nullable vector field data is nil") } dim := baseVector.GetDim() if dim == 0 { @@ -2113,7 +2112,7 @@ func updateCompactNullableVectorFieldDataByColumn(base, update *schemapb.FieldDa start := int64(physicalIdx) * elemSize end := start + elemSize if start < 0 || end > int64(len(data)) { - return fmt.Errorf("binary vector physical index %d out of range", physicalIdx) + return merr.WrapErrParameterInvalidMsg("binary vector physical index %d out of range", physicalIdx) } newData = append(newData, data[start:end]...) return nil @@ -2129,7 +2128,7 @@ func updateCompactNullableVectorFieldDataByColumn(base, update *schemapb.FieldDa start := int64(physicalIdx) * dim end := start + dim if start < 0 || end > int64(len(data)) { - return fmt.Errorf("float vector physical index %d out of range", physicalIdx) + return merr.WrapErrParameterInvalidMsg("float vector physical index %d out of range", physicalIdx) } newData = append(newData, data[start:end]...) return nil @@ -2146,7 +2145,7 @@ func updateCompactNullableVectorFieldDataByColumn(base, update *schemapb.FieldDa start := int64(physicalIdx) * elemSize end := start + elemSize if start < 0 || end > int64(len(data)) { - return fmt.Errorf("float16 vector physical index %d out of range", physicalIdx) + return merr.WrapErrParameterInvalidMsg("float16 vector physical index %d out of range", physicalIdx) } newData = append(newData, data[start:end]...) return nil @@ -2163,7 +2162,7 @@ func updateCompactNullableVectorFieldDataByColumn(base, update *schemapb.FieldDa start := int64(physicalIdx) * elemSize end := start + elemSize if start < 0 || end > int64(len(data)) { - return fmt.Errorf("bfloat16 vector physical index %d out of range", physicalIdx) + return merr.WrapErrParameterInvalidMsg("bfloat16 vector physical index %d out of range", physicalIdx) } newData = append(newData, data[start:end]...) return nil @@ -2176,7 +2175,7 @@ func updateCompactNullableVectorFieldDataByColumn(base, update *schemapb.FieldDa baseSparse := baseVector.GetSparseFloatVector() updateSparse := updateVector.GetSparseFloatVector() if updateSparse == nil { - return fmt.Errorf("sparse float vector update data is nil") + return merr.WrapErrParameterInvalidMsg("sparse float vector update data is nil") } baseDim := int64(0) if baseSparse != nil { @@ -2189,7 +2188,7 @@ func updateCompactNullableVectorFieldDataByColumn(base, update *schemapb.FieldDa appendRow := func(vector *schemapb.VectorField, physicalIdx int) error { data := vector.GetSparseFloatVector() if data == nil || physicalIdx < 0 || physicalIdx >= len(data.GetContents()) { - return fmt.Errorf("sparse vector physical index %d out of range", physicalIdx) + return merr.WrapErrParameterInvalidMsg("sparse vector physical index %d out of range", physicalIdx) } row := data.GetContents()[physicalIdx] newSparse.Contents = append(newSparse.Contents, row) @@ -2211,7 +2210,7 @@ func updateCompactNullableVectorFieldDataByColumn(base, update *schemapb.FieldDa start := int64(physicalIdx) * elemSize end := start + elemSize if start < 0 || end > int64(len(data)) { - return fmt.Errorf("int8 vector physical index %d out of range", physicalIdx) + return merr.WrapErrParameterInvalidMsg("int8 vector physical index %d out of range", physicalIdx) } newData = append(newData, data[start:end]...) return nil @@ -2221,7 +2220,7 @@ func updateCompactNullableVectorFieldDataByColumn(base, update *schemapb.FieldDa } baseVector.Data = &schemapb.VectorField_Int8Vector{Int8Vector: newData} default: - return fmt.Errorf("unsupported nullable vector field type %s", base.GetType().String()) + return merr.WrapErrParameterInvalidMsg("unsupported nullable vector field type %s", base.GetType().String()) } base.ValidData = newValidData @@ -2285,7 +2284,7 @@ func MergeFieldData(dst []*schemapb.FieldData, src []*schemapb.FieldData) error switch fieldType := srcFieldData.Field.(type) { case *schemapb.FieldData_Scalars: if _, ok := fieldID2Data[srcFieldData.FieldId]; !ok { - return errors.New("fields in src but not in dst: " + srcFieldData.Type.String()) + return merr.WrapErrParameterInvalidMsg("fields in src but not in dst: " + srcFieldData.Type.String()) } fieldData := fieldID2Data[srcFieldData.FieldId] fieldData.ValidData = append(fieldData.ValidData, srcFieldData.GetValidData()...) @@ -2403,11 +2402,11 @@ func MergeFieldData(dst []*schemapb.FieldData, src []*schemapb.FieldData) error dstScalar.GetBytesData().Data = append(dstScalar.GetBytesData().Data, srcScalar.BytesData.Data...) } default: - return errors.New("unsupported data type: " + srcFieldData.Type.String()) + return merr.WrapErrParameterInvalidMsg("unsupported data type: " + srcFieldData.Type.String()) } case *schemapb.FieldData_Vectors: if _, ok := fieldID2Data[srcFieldData.FieldId]; !ok { - return errors.New("fields in src but not in dst: " + srcFieldData.Type.String()) + return merr.WrapErrParameterInvalidMsg("fields in src but not in dst: " + srcFieldData.Type.String()) } fieldData := fieldID2Data[srcFieldData.FieldId] // Merge ValidData for nullable vectors @@ -2483,7 +2482,7 @@ func MergeFieldData(dst []*schemapb.FieldData, src []*schemapb.FieldData) error case nil: // nullable vector field where all rows are null — no vector data to merge default: - return errors.New("unsupported data type: " + srcFieldData.Type.String()) + return merr.WrapErrParameterInvalidMsg("unsupported data type: " + srcFieldData.Type.String()) } } } @@ -2564,7 +2563,7 @@ func GetPrimaryFieldSchema(schema *schemapb.CollectionSchema) (*schemapb.FieldSc } } - return nil, errors.New("primary field is not found") + return nil, merr.WrapErrParameterInvalidMsg("primary field is not found") } // NormalizeAndValidateExternalCollectionSchema ensures unsupported features are @@ -2591,16 +2590,16 @@ func NormalizeAndValidateExternalCollectionSchema(schema *schemapb.CollectionSch srcSet := schema.GetExternalSource() != "" specSet := schema.GetExternalSpec() != "" if srcSet != specSet { - return fmt.Errorf("external collection %s requires external_source and external_spec to be both set or both empty (got source=%q, spec=%q)", + return merr.WrapErrParameterInvalidMsg("external collection %s requires external_source and external_spec to be both set or both empty (got source=%q, spec=%q)", schema.GetName(), schema.GetExternalSource(), schema.GetExternalSpec()) } if schema.GetEnableDynamicField() { - return fmt.Errorf("external collection %s does not support dynamic field", schema.GetName()) + return merr.WrapErrParameterInvalidMsg("external collection %s does not support dynamic field", schema.GetName()) } if len(schema.GetStructArrayFields()) > 0 { - return fmt.Errorf("external collection %s does not support struct fields", schema.GetName()) + return merr.WrapErrParameterInvalidMsg("external collection %s does not support struct fields", schema.GetName()) } generatedColumns := externalGeneratedColumnOwners(schema) @@ -2616,36 +2615,36 @@ func NormalizeAndValidateExternalCollectionSchema(schema *schemapb.CollectionSch // Function output fields are computed internally, skip all external-data checks. if isExternalGeneratedField(field, generatedColumns) { if field.GetExternalField() != "" { - return fmt.Errorf("function output field '%s' in external collection %s must not have external_field mapping", field.GetName(), schema.GetName()) + return merr.WrapErrParameterInvalidMsg("function output field '%s' in external collection %s must not have external_field mapping", field.GetName(), schema.GetName()) } continue } if field.GetIsPrimaryKey() { - return fmt.Errorf("external collection %s does not support user-defined primary key field %s", schema.GetName(), field.GetName()) + return merr.WrapErrParameterInvalidMsg("external collection %s does not support user-defined primary key field %s", schema.GetName(), field.GetName()) } if field.GetIsPartitionKey() { - return fmt.Errorf("external collection %s does not support partition key field %s", schema.GetName(), field.GetName()) + return merr.WrapErrParameterInvalidMsg("external collection %s does not support partition key field %s", schema.GetName(), field.GetName()) } if field.GetIsClusteringKey() { - return fmt.Errorf("external collection %s does not support clustering key field %s", schema.GetName(), field.GetName()) + return merr.WrapErrParameterInvalidMsg("external collection %s does not support clustering key field %s", schema.GetName(), field.GetName()) } if field.GetAutoID() { - return fmt.Errorf("external collection %s does not support auto id on field %s", schema.GetName(), field.GetName()) + return merr.WrapErrParameterInvalidMsg("external collection %s does not support auto id on field %s", schema.GetName(), field.GetName()) } if field.GetExternalField() == "" { - return fmt.Errorf("field '%s' in external collection %s must have external_field mapping", field.GetName(), schema.GetName()) + return merr.WrapErrParameterInvalidMsg("field '%s' in external collection %s must have external_field mapping", field.GetName(), schema.GetName()) } if !isExternalFieldTypeSupported(field.GetDataType()) { - return fmt.Errorf("external collection %s does not support field type %s on field %s", + return merr.WrapErrParameterInvalidMsg("external collection %s does not support field type %s on field %s", schema.GetName(), field.GetDataType().String(), field.GetName()) } ext := field.GetExternalField() if outputField, ok := generatedColumns[ext]; ok { - return fmt.Errorf("external_field %q on field '%s' in external collection %s conflicts with generated function output field '%s' (field id %d)", + return merr.WrapErrParameterInvalidMsg("external_field %q on field '%s' in external collection %s conflicts with generated function output field '%s' (field id %d)", ext, field.GetName(), schema.GetName(), outputField.GetName(), outputField.GetFieldID()) } externalFieldOwners[ext] = append(externalFieldOwners[ext], field) @@ -2686,7 +2685,7 @@ func validateUniqueExternalFieldOwners(externalFieldOwners map[string][]*schemap for _, f := range owners { parts = append(parts, fmt.Sprintf("%s (%s)", f.GetName(), f.GetDataType().String())) } - return fmt.Errorf("external_field %q is mapped by multiple fields: %s; each external_field must be referenced by at most one user field", + return merr.WrapErrParameterInvalidMsg("external_field %q is mapped by multiple fields: %s; each external_field must be referenced by at most one user field", ext, strings.Join(parts, ", ")) } return nil @@ -2755,12 +2754,12 @@ func ValidateExternalCollectionResolvedSchema(schema *schemapb.CollectionSchema) } if isExternalGeneratedField(field, generatedColumns) { if field.GetExternalField() != "" { - return fmt.Errorf("function output field '%s' in external collection %s must not have external_field mapping", field.GetName(), schema.GetName()) + return merr.WrapErrParameterInvalidMsg("function output field '%s' in external collection %s must not have external_field mapping", field.GetName(), schema.GetName()) } continue } if !isExternalFieldTypeSupported(field.GetDataType()) { - return fmt.Errorf("external collection %s does not support field type %s on field %s", + return merr.WrapErrParameterInvalidMsg("external collection %s does not support field type %s on field %s", schema.GetName(), field.GetDataType().String(), field.GetName()) } ext := field.GetExternalField() @@ -2772,7 +2771,7 @@ func ValidateExternalCollectionResolvedSchema(schema *schemapb.CollectionSchema) externalFieldOwners[ext] = append(externalFieldOwners[ext], field) continue } - return fmt.Errorf("external_field %q on field '%s' in external collection %s conflicts with generated function output field '%s' (field id %d)", + return merr.WrapErrParameterInvalidMsg("external_field %q on field '%s' in external collection %s conflicts with generated function output field '%s' (field id %d)", ext, field.GetName(), schema.GetName(), outputField.GetName(), outputField.GetFieldID()) } return validateUniqueExternalFieldOwners(externalFieldOwners) @@ -2832,7 +2831,7 @@ func GetPartitionKeyFieldSchema(schema *schemapb.CollectionSchema) (*schemapb.Fi } } - return nil, errors.New("partition key field is not found") + return nil, merr.WrapErrParameterInvalidMsg("partition key field is not found") } // GetDynamicField returns the dynamic field if it exists. @@ -2883,7 +2882,7 @@ func GetPrimaryFieldData(datas []*schemapb.FieldData, primaryFieldSchema *schema } if primaryFieldData == nil { - return nil, fmt.Errorf("can't find data for primary field: %v", primaryFieldName) + return nil, merr.WrapErrParameterInvalidMsg("can't find data for primary field: %v", primaryFieldName) } return primaryFieldData, nil @@ -3400,25 +3399,25 @@ func trimSparseFloatArray(vec *schemapb.SparseFloatArray) { func ValidateSparseFloatRows(rows ...[]byte) error { for _, row := range rows { if row == nil { - return errors.New("nil sparse float vector") + return merr.WrapErrParameterInvalidMsg("nil sparse float vector") } if len(row)%8 != 0 { - return fmt.Errorf("invalid data length in sparse float vector: %d", len(row)) + return merr.WrapErrParameterInvalidMsg("invalid data length in sparse float vector: %d", len(row)) } for i := 0; i < SparseFloatRowElementCount(row); i++ { idx := SparseFloatRowIndexAt(row, i) if idx == math.MaxUint32 { - return errors.New("invalid index in sparse float vector: must be less than 2^32-1") + return merr.WrapErrParameterInvalidMsg("invalid index in sparse float vector: must be less than 2^32-1") } if i > 0 && idx <= SparseFloatRowIndexAt(row, i-1) { - return errors.New("unsorted or same indices in sparse float vector") + return merr.WrapErrParameterInvalidMsg("unsorted or same indices in sparse float vector") } val := SparseFloatRowValueAt(row, i) if err := VerifyFloat(float64(val)); err != nil { return err } if val < 0 { - return errors.New("negative value in sparse float vector") + return merr.WrapErrParameterInvalidMsg("negative value in sparse float vector") } } } @@ -3516,16 +3515,16 @@ func CreateSparseFloatRowFromMap(input map[string]interface{}) ([]byte, error) { if num, err := strconv.ParseFloat(v.String(), 64); err == nil { val = num } else { - return 0, fmt.Errorf("invalid value type in JSON: %s", reflect.TypeOf(v)) + return 0, merr.WrapErrParameterInvalidMsg("invalid value type in JSON: %s", reflect.TypeOf(v)) } default: - return 0, fmt.Errorf("invalid value type in JSON: %s", reflect.TypeOf(key)) + return 0, merr.WrapErrParameterInvalidMsg("invalid value type in JSON: %s", reflect.TypeOf(key)) } if VerifyFloat(val) != nil { - return 0, fmt.Errorf("invalid value in JSON: %v", val) + return 0, merr.WrapErrParameterInvalidMsg("invalid value in JSON: %v", val) } if val > math.MaxFloat32 { - return 0, fmt.Errorf("value too large in JSON: %v", val) + return 0, merr.WrapErrParameterInvalidMsg("value too large in JSON: %v", val) } return float32(val), nil } @@ -3538,7 +3537,7 @@ func CreateSparseFloatRowFromMap(input map[string]interface{}) ([]byte, error) { case float64: // check if the float64 is actually an integer if v != float64(int64(v)) { - return 0, fmt.Errorf("invalid index in JSON: %v", v) + return 0, merr.WrapErrParameterInvalidMsg("invalid index in JSON: %v", v) } idx = int64(v) case json.Number: @@ -3548,10 +3547,10 @@ func CreateSparseFloatRowFromMap(input map[string]interface{}) ([]byte, error) { return 0, err } default: - return 0, fmt.Errorf("invalid index type in JSON: %s", reflect.TypeOf(key)) + return 0, merr.WrapErrParameterInvalidMsg("invalid index type in JSON: %s", reflect.TypeOf(key)) } if idx >= math.MaxUint32 { - return 0, fmt.Errorf("index too large in JSON: %v", idx) + return 0, merr.WrapErrParameterInvalidMsg("index too large in JSON: %v", idx) } return uint32(idx), nil } @@ -3592,11 +3591,11 @@ func CreateSparseFloatRowFromMap(input map[string]interface{}) ([]byte, error) { values = append(values, val) } } else { - return nil, errors.New("invalid JSON input") + return nil, merr.WrapErrParameterInvalidMsg("invalid JSON input") } if len(indices) != len(values) { - return nil, errors.New("indices and values length mismatch") + return nil, merr.WrapErrParameterInvalidMsg("indices and values length mismatch") } sortedIndices, sortedValues := SortSparseFloatRow(indices, values) @@ -3653,10 +3652,10 @@ func GetNeedProcessFunctions(fieldIDs []int64, functions []*schemapb.FunctionSch for _, fieldID := range fieldIDs { if f, exists := fieldIDFuncMapping[fieldID]; exists { if f.Type == schemapb.FunctionType_BM25 { - return nil, fmt.Errorf("attempt to insert bm25 function output field") + return nil, merr.WrapErrParameterInvalidMsg("attempt to insert bm25 function output field") } if !allowNonBM25Outputs { - return nil, fmt.Errorf("Insert data has function output field, but collection's property `collection.function.allowInsertNonBM25FunctionOutputs` is not enable") + return nil, merr.WrapErrParameterInvalidMsg("Insert data has function output field, but collection's property `collection.function.allowInsertNonBM25FunctionOutputs` is not enable") } delete(funCandidate, f.Name) } @@ -3747,7 +3746,7 @@ func ExtractStructFieldName(fieldName string) (string, error) { } else if len(parts) == 2 { return parts[1][:len(parts[1])-1], nil } else { - return "", fmt.Errorf("invalid struct field name: %s, more than one [ found", fieldName) + return "", merr.WrapErrParameterInvalidMsg("invalid struct field name: %s, more than one [ found", fieldName) } } @@ -3775,7 +3774,7 @@ func ApplyArrayRowOp( case schemapb.FieldPartialUpdateOp_ARRAY_REMOVE: return removeArrayRow(base, update, elementType) default: - return nil, fmt.Errorf("unsupported FieldPartialUpdateOp: %s", op.String()) + return nil, merr.WrapErrParameterInvalidMsg("unsupported FieldPartialUpdateOp: %s", op.String()) } } @@ -3846,7 +3845,7 @@ func appendArrayRow( } return &schemapb.ScalarField{Data: &schemapb.ScalarField_StringData{StringData: &schemapb.StringArray{Data: merged}}}, nil default: - return nil, fmt.Errorf("ARRAY_APPEND does not support element type: %s", elementType.String()) + return nil, merr.WrapErrParameterInvalidMsg("ARRAY_APPEND does not support element type: %s", elementType.String()) } } @@ -3952,7 +3951,7 @@ func removeArrayRow( } return &schemapb.ScalarField{Data: &schemapb.ScalarField_StringData{StringData: &schemapb.StringArray{Data: out}}}, nil default: - return nil, fmt.Errorf("ARRAY_REMOVE does not support element type: %s", elementType.String()) + return nil, merr.WrapErrParameterInvalidMsg("ARRAY_REMOVE does not support element type: %s", elementType.String()) } } @@ -3978,5 +3977,5 @@ func containsFloat64(haystack []float64, needle float64) bool { } func newArrayCapacityError(got, max int) error { - return fmt.Errorf("array length %d exceeds max_capacity %d", got, max) + return merr.WrapErrParameterInvalidMsg("array length %d exceeds max_capacity %d", got, max) } diff --git a/pkg/util/typeutil/schema_test.go b/pkg/util/typeutil/schema_test.go index 5002a20f5b..72a1ff3512 100644 --- a/pkg/util/typeutil/schema_test.go +++ b/pkg/util/typeutil/schema_test.go @@ -33,6 +33,7 @@ import ( "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/common" + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) func TestSchema(t *testing.T) { @@ -703,7 +704,8 @@ func TestSchema_invalid(t *testing.T) { } _, err := CreateSchemaHelper(schema) assert.Error(t, err) - assert.EqualError(t, err, "duplicated fieldName: field_int8") + assert.ErrorIs(t, err, merr.ErrParameterInvalid) + assert.Contains(t, err.Error(), "duplicated fieldName: field_int8") }) t.Run("Duplicate field id", func(t *testing.T) { schema := &schemapb.CollectionSchema{ @@ -729,7 +731,8 @@ func TestSchema_invalid(t *testing.T) { } _, err := CreateSchemaHelper(schema) assert.Error(t, err) - assert.EqualError(t, err, "duplicated fieldID: 100") + assert.ErrorIs(t, err, merr.ErrParameterInvalid) + assert.Contains(t, err.Error(), "duplicated fieldID: 100") }) t.Run("Duplicated primary key", func(t *testing.T) { schema := &schemapb.CollectionSchema{ @@ -755,7 +758,8 @@ func TestSchema_invalid(t *testing.T) { } _, err := CreateSchemaHelper(schema) assert.Error(t, err) - assert.EqualError(t, err, "primary key is not unique") + assert.ErrorIs(t, err, merr.ErrParameterInvalid) + assert.Contains(t, err.Error(), "primary key is not unique") }) t.Run("field not exist", func(t *testing.T) { schema := &schemapb.CollectionSchema{ @@ -777,15 +781,18 @@ func TestSchema_invalid(t *testing.T) { _, err = helper.GetPrimaryKeyField() assert.Error(t, err) - assert.EqualError(t, err, "failed to get primary key field: no primary in schema") + assert.ErrorIs(t, err, merr.ErrParameterInvalid) + assert.Contains(t, err.Error(), "failed to get primary key field: no primary in schema") _, err = helper.GetFieldFromName("none") assert.Error(t, err) - assert.EqualError(t, err, "failed to get field schema by name: fieldName(none) not found") + assert.ErrorIs(t, err, merr.ErrParameterInvalid) + assert.Contains(t, err.Error(), "failed to get field schema by name: fieldName(none) not found") _, err = helper.GetFieldFromID(101) assert.Error(t, err) - assert.EqualError(t, err, "fieldID(101) not found") + assert.ErrorIs(t, err, merr.ErrParameterInvalid) + assert.Contains(t, err.Error(), "fieldID(101) not found") }) t.Run("vector dim not exist", func(t *testing.T) { schema := &schemapb.CollectionSchema{ diff --git a/pkg/util/typeutil/skip_list.go b/pkg/util/typeutil/skip_list.go index 2e3f6d858c..b5f6c06f89 100644 --- a/pkg/util/typeutil/skip_list.go +++ b/pkg/util/typeutil/skip_list.go @@ -17,11 +17,12 @@ package typeutil import ( - "fmt" "math/rand" "sync" "golang.org/x/exp/constraints" + + "github.com/milvus-io/milvus/pkg/v3/util/merr" ) const ( @@ -265,10 +266,10 @@ func NewSkipList[K constraints.Ordered, V any](opts ...SkipListOption) (*SkipLis opt(option) } if option.maxLevel < 1 { - return nil, fmt.Errorf("invalid maxlevel %d", option.maxLevel) + return nil, merr.WrapErrParameterInvalidMsg("invalid maxlevel %d", option.maxLevel) } if option.skip < 1 { - return nil, fmt.Errorf("invalid skip %d", option.skip) + return nil, merr.WrapErrParameterInvalidMsg("invalid skip %d", option.skip) } return &SkipList[K, V]{ diff --git a/rules.go b/rules.go index 5bc3422c9b..fa4670c5d4 100644 --- a/rules.go +++ b/rules.go @@ -407,3 +407,73 @@ func badlock(m dsl.Matcher) { m.Match(`$mu.Lock(); defer $mu.RUnlock()`).Report(`maybe $mu.RLock() was intended?`) m.Match(`$mu.RLock(); defer $mu.Unlock()`).Report(`maybe $mu.Lock() was intended?`) } + +// Raw error constructors (fmt.Errorf / errors.New / errors.Errorf) must not be +// returned directly from a function body. An error that reaches the gRPC +// boundary has to carry a merr code, so originate it through the merr framework +// (merr.WrapErr*Msg / merr.WrapErr*Err) and add context with merr.Wrap/Wrapf. +// +// Scope: this catches the direct-return form, the form that lets a raw error +// escape to the boundary. Sentinels are expected to be package-level +// (var Err... = errors.New(...)) — ValueSpec nodes that the statement patterns +// below never match — so they are exempt automatically; function-local +// sentinels should be lifted to package level rather than kept inline. +// Assignment-then-return escapes (e := errors.New(); return e) cannot be +// expressed as a ruleguard pattern and are backstopped at the gRPC boundary by +// merr.Status. Test files, the cmd/ and tests/ trees, codegen and the walimpls +// harness run outside the request path and are exempt. +func rawmerrerror(m dsl.Matcher) { + // fmt.Errorf resolves unambiguously to the stdlib, so the literal selector matches. + m.Match( + `return fmt.Errorf($*_)`, + `return $*_, fmt.Errorf($*_)`, + `return fmt.Errorf($*_), $*_`, + ). + Where(!m.File().Name.Matches(`_test\.go$`) && + !m.File().Name.Matches(`test_streaming\.go$`) && + !m.File().PkgPath.Matches(`/milvus/cmd/`) && + !m.File().PkgPath.Matches(`/milvus/tests/`) && + !m.File().PkgPath.Matches(`codegen`) && + !m.File().PkgPath.Matches(`walimplstest`) && + !m.File().PkgPath.Matches(`/mocks/`)). + Report(`raw error returned from function body; originate through the merr framework (merr.WrapErr*/merr.Wrap) instead of fmt.Errorf/errors.New/errors.Errorf`) + + // errors.New/Newf/Errorf: the repo uses github.com/cockroachdb/errors, not the + // stdlib, so a literal `errors.New` selector — which ruleguard type-resolves to + // the stdlib package — never matches. Match by the function name instead, limited + // to the raw constructors; errors.Wrap/Wrapf/Is/As/Cause are intentionally allowed. + m.Match( + `return errors.$fn($*_)`, + `return $*_, errors.$fn($*_)`, + `return errors.$fn($*_), $*_`, + ). + Where(m["fn"].Text.Matches(`^(New|Newf|Errorf)$`) && + !m.File().Name.Matches(`_test\.go$`) && + !m.File().Name.Matches(`test_streaming\.go$`) && + !m.File().PkgPath.Matches(`/milvus/cmd/`) && + !m.File().PkgPath.Matches(`/milvus/tests/`) && + !m.File().PkgPath.Matches(`codegen`) && + !m.File().PkgPath.Matches(`walimplstest`) && + !m.File().PkgPath.Matches(`/mocks/`)). + Report(`raw error returned from function body; originate through the merr framework (merr.WrapErr*/merr.Wrap) instead of fmt.Errorf/errors.New/errors.Errorf`) +} + +// merrsentinel forbids parking merr-typed errors in variables that look like +// sentinels. milvusError.Is compares error codes only, so errors.Is against a +// merr-typed "sentinel" silently matches ANY error sharing the code — a guard +// like errors.Is(err, ErrIgnoredFoo) then treats unrelated internal failures +// as the special case (real incident: stale-meta / already-loaded guards +// acknowledged etcd write failures as success). Identity sentinels must stay +// plain errors.New; merr errors must be created at the return site. +func merrsentinel(m dsl.Matcher) { + m.Match( + `var $x = merr.$f($*_)`, + `var $x = merr.$v`, + ). + Where(!m.File().Name.Matches(`_test\.go$`) && + !m.File().PkgPath.Matches(`/milvus/cmd/`) && + !m.File().PkgPath.Matches(`/milvus/tests/`) && + !m.File().PkgPath.Matches(`/mocks/`) && + !m.File().PkgPath.Matches(`util/merr`)). + Report(`don't store a merr error in a sentinel-like variable: errors.Is on merr compares codes, not identity, so any same-code error would match. Use errors.New for identity sentinels, or create the merr error at the return site`) +} diff --git a/scripts/run_go_unittest.sh b/scripts/run_go_unittest.sh index 3a2339d2f4..aa3376fa0e 100755 --- a/scripts/run_go_unittest.sh +++ b/scripts/run_go_unittest.sh @@ -26,6 +26,12 @@ if [[ $(uname -s) == "Darwin" ]]; then export MallocNanoZone=0 fi +# Azurite default credentials for internal/storage TestAzureObjectStorage. +# Honor any caller-provided value so CI can override with real credentials. +if [[ -z "${AZURE_STORAGE_CONNECTION_STRING}" ]]; then + export AZURE_STORAGE_CONNECTION_STRING="DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;" +fi + # ignore MinIO,S3 unittests MILVUS_DIR="${ROOT_DIR}/internal/" PKG_DIR="${ROOT_DIR}/pkg/" diff --git a/scripts/start_cluster_with_replicas.sh b/scripts/start_cluster_with_replicas.sh new file mode 100755 index 0000000000..78a1d32750 --- /dev/null +++ b/scripts/start_cluster_with_replicas.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash + +# 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. + +# start_cluster_with_replicas.sh — start a local cluster with N querynodes +# and N streamingnodes so that tests with replica_number >= 2 (e.g. +# test_milvus_client_alter_warmup, test_collection multi-replica) can run. +# +# Usage: +# bash scripts/start_cluster_with_replicas.sh # default 2 +# N_REPLICAS=3 bash scripts/start_cluster_with_replicas.sh + +N=${N_REPLICAS:-2} + +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + LIBJEMALLOC=$PWD/internal/core/output/lib/libjemalloc.so + if test -f "$LIBJEMALLOC"; then + export LD_PRELOAD="$LIBJEMALLOC" + export MALLOC_CONF=background_thread:true + else + echo "WARN: Cannot find $LIBJEMALLOC" + fi + export LD_LIBRARY_PATH=$PWD/internal/core/output/lib/:$LD_LIBRARY_PATH +fi + +echo "Starting mixcoord..." +nohup ./bin/milvus run mixcoord --run-with-subprocess >/tmp/mixcoord.log 2>&1 & + +echo "Starting datanode..." +nohup ./bin/milvus run datanode --run-with-subprocess >/tmp/datanode.log 2>&1 & + +# QueryNode ports: 21123, 21124, 21125, ... +QN_PORT_BASE=21123 +echo "Starting ${N} querynode(s)..." +for i in $(seq 1 "$N"); do + port=$((QN_PORT_BASE + i - 1)) + MILVUS_CONF_QUERYNODE_PORT=$port \ + nohup ./bin/milvus run querynode --run-with-subprocess \ + >/tmp/querynode_${i}.log 2>&1 & + echo " querynode #$i on port $port (log: /tmp/querynode_${i}.log)" +done + +# StreamingNode ports: 22222, 22223, 22224, ... +SN_PORT_BASE=22222 +echo "Starting ${N} streamingnode(s)..." +for i in $(seq 1 "$N"); do + port=$((SN_PORT_BASE + i - 1)) + MILVUS_CONF_STREAMINGNODE_PORT=$port \ + nohup ./bin/milvus run streamingnode --run-with-subprocess \ + >/tmp/streamingnode_${i}.log 2>&1 & + echo " streamingnode #$i on port $port (log: /tmp/streamingnode_${i}.log)" +done + +echo "Starting proxy..." +nohup ./bin/milvus run proxy --run-with-subprocess >/tmp/proxy.log 2>&1 & + +echo +echo "Cluster started with ${N} querynode(s) + ${N} streamingnode(s)." +echo "Tail logs: /tmp/{mixcoord,datanode,querynode_*,streamingnode_*,proxy}.log" diff --git a/tests/go_client/testcases/index_test.go b/tests/go_client/testcases/index_test.go index cdb3f0c091..670000d719 100644 --- a/tests/go_client/testcases/index_test.go +++ b/tests/go_client/testcases/index_test.go @@ -635,7 +635,7 @@ func TestCreateIndexWithOtherFieldName(t *testing.T) { // create index in binary field with default name idxBinary := index.NewBinFlatIndex(entity.JACCARD) _, err = mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultBinaryVecFieldName, idxBinary)) - common.CheckErr(t, err, false, "CreateIndex failed: at most one distinct index is allowed per field") + common.CheckErr(t, err, false, "at most one distinct index is allowed per field") } // create all scalar index on json field -> error @@ -813,7 +813,7 @@ func TestCreateIndexDup(t *testing.T) { common.CheckIndex(t, _index, expIndex, common.TNewCheckIndexOpt(common.DefaultNb)) _, err = mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloatVecFieldName, idxIvfSq8)) - common.CheckErr(t, err, false, "CreateIndex failed: at most one distinct index is allowed per field") + common.CheckErr(t, err, false, "at most one distinct index is allowed per field") } func TestCreateIndexSparseVectorGeneric(t *testing.T) { @@ -1199,7 +1199,7 @@ func TestIndexMultiVectorDupName(t *testing.T) { common.CheckErr(t, err, true) _, err = mc.CreateIndex(ctx, client.NewCreateIndexOption(schema.CollectionName, common.DefaultFloat16VecFieldName, idx).WithIndexName("index_1")) - common.CheckErr(t, err, false, "CreateIndex failed: at most one distinct index is allowed per field") + common.CheckErr(t, err, false, "at most one distinct index is allowed per field") // create different index on same field idxRe := index.NewIvfSQ8Index(entity.COSINE, 32) diff --git a/tests/python_client/base/client_base.py b/tests/python_client/base/client_base.py index 6caf5fa30b..c5ecc3b15e 100644 --- a/tests/python_client/base/client_base.py +++ b/tests/python_client/base/client_base.py @@ -31,6 +31,8 @@ class Base: field_schema_wrap = None database_wrap = None tear_down_collection_names = [] + tear_down_role_names = [] + tear_down_user_names = [] resource_group_list = [] async_milvus_client_wrap = None skip_connection = False diff --git a/tests/python_client/base/client_v2_base.py b/tests/python_client/base/client_v2_base.py index 389a321eb3..8f49e9ac5c 100644 --- a/tests/python_client/base/client_v2_base.py +++ b/tests/python_client/base/client_v2_base.py @@ -947,6 +947,9 @@ class TestMilvusClientV2Base(Base): check_result = ResponseChecker( res, func_name, check_task, check_items, check, user_name=user_name, password=password, **kwargs ).run() + # track for per-instance teardown; avoids cross-worker drops under -n + if check is True and check_task is None and user_name not in self.tear_down_user_names: + self.tear_down_user_names.append(user_name) return res, check_result @trace() @@ -1022,6 +1025,9 @@ class TestMilvusClientV2Base(Base): check_result = ResponseChecker( res, func_name, check_task, check_items, check, role_name=role_name, **kwargs ).run() + # track for per-instance teardown; avoids cross-worker drops under -n + if check is True and check_task is None and role_name not in self.tear_down_role_names: + self.tear_down_role_names.append(role_name) return res, check_result @trace() diff --git a/tests/python_client/check/func_check.py b/tests/python_client/check/func_check.py index 4a5dcefadd..b224c56f36 100644 --- a/tests/python_client/check/func_check.py +++ b/tests/python_client/check/func_check.py @@ -504,9 +504,13 @@ class ResponseChecker: num_to_check = min(100, len(distances)) # check 100 items if more than that eps = 1e-6 if check_items.get("metric").upper() in ["IP", "COSINE", "BM25"]: - assert all(distances[i] >= distances[i + 1] - eps for i in range(num_to_check - 1)) + assert all(distances[i] >= distances[i + 1] - eps for i in range(num_to_check - 1)), ( + f"distances not descending within eps={eps}: {distances[:num_to_check]}" + ) else: - assert all(distances[i] <= distances[i + 1] + eps for i in range(num_to_check - 1)) + assert all(distances[i] <= distances[i + 1] + eps for i in range(num_to_check - 1)), ( + f"distances not ascending within eps={eps}: {distances[:num_to_check]}" + ) if check_items.get("vector_nq") is None or check_items.get("original_vectors") is None: log.debug("skip distance check for knowhere does not return the precise distances") else: diff --git a/tests/python_client/milvus_client/test_milvus_client_alias.py b/tests/python_client/milvus_client/test_milvus_client_alias.py index c583ead781..c256ad6c0f 100644 --- a/tests/python_client/milvus_client/test_milvus_client_alias.py +++ b/tests/python_client/milvus_client/test_milvus_client_alias.py @@ -126,8 +126,7 @@ class TestMilvusClientAliasInvalid(TestMilvusClientV2Base): alias_name = cf.gen_unique_str('alias') self.create_alias(client, collection_name, alias_name) error = {ct.err_code: 1601, - ct.err_msg: f"collection name [{alias_name}] conflicts with an existing alias, " - f"please choose a unique name"} + ct.err_msg: f"alias and collection name conflict[database=default][alias={alias_name}]"} self.create_collection(client, alias_name, default_dim, check_task=CheckTasks.err_res, check_items=error) self.drop_alias(client, alias_name) @@ -924,8 +923,8 @@ class TestMilvusClientAliasOperationInvalid(TestMilvusClientV2Base): self.create_collection(client, collection_name2, default_dim, consistency_level="Bounded") # 4. try to rename collection2 to alias name - error = {ct.err_code: 999, - ct.err_msg: f"cannot rename collection to an existing alias: {alias_name}"} + error = {ct.err_code: 1601, + ct.err_msg: f"alias and collection name conflict[database=default][alias={alias_name}]"} self.rename_collection(client, collection_name2, alias_name, check_task=CheckTasks.err_res, check_items=error) diff --git a/tests/python_client/milvus_client/test_milvus_client_alter.py b/tests/python_client/milvus_client/test_milvus_client_alter.py index 6659cc53c9..c6d42a008b 100644 --- a/tests/python_client/milvus_client/test_milvus_client_alter.py +++ b/tests/python_client/milvus_client/test_milvus_client_alter.py @@ -152,15 +152,10 @@ class TestMilvusClientAlterIndex(TestMilvusClientV2Base): assert res1.get("mmap.enabled", None) is None unsupported_values = [None, [], "", 20, " ", 0.01, "new_value"] for value in unsupported_values: - error = {ct.err_code: 1, ct.err_msg: f"invalid mmap.enabled value: {value}, expected: true, false"} - self.alter_index_properties( - client, - collection_name, - idx_names[0], - properties={"mmap.enabled": value}, - check_task=CheckTasks.err_res, - check_items=error, - ) + error = {ct.err_code: 1, ct.err_msg: "invalid mmap.enabled value"} + self.alter_index_properties(client, collection_name, idx_names[0], + properties={"mmap.enabled": value}, + check_task=CheckTasks.err_res, check_items=error) @pytest.mark.tags(CaseLabel.L1) def test_milvus_client_create_index_idempotent_with_field_warmup(self): diff --git a/tests/python_client/milvus_client/test_milvus_client_collection.py b/tests/python_client/milvus_client/test_milvus_client_collection.py index 7ed60ed7a5..b91c2c9dab 100644 --- a/tests/python_client/milvus_client/test_milvus_client_collection.py +++ b/tests/python_client/milvus_client/test_milvus_client_collection.py @@ -296,8 +296,7 @@ class TestMilvusClientCollectionInvalid(TestMilvusClientV2Base): # 1. create collection error = { ct.err_code: 1100, - ct.err_msg: f"float vector index does not support metric type: {metric_type}: " - f"invalid parameter[expected=valid index params][actual=invalid index params", + ct.err_msg: "float vector index does not support metric type", } self.create_collection( client, @@ -1120,7 +1119,7 @@ class TestMilvusClientCollectionValid(TestMilvusClientV2Base): ) # not support search on null vector with is null or is not null filter - error = {ct.err_code: 999, ct.err_msg: "error: IsNull/IsNotNull operations are not supported on vector fields"} + error = {ct.err_code: 999, ct.err_msg: "IsNull/IsNotNull operations are not supported on vector fields"} self.search( client, collection_name, diff --git a/tests/python_client/milvus_client/test_milvus_client_geometry.py b/tests/python_client/milvus_client/test_milvus_client_geometry.py index 66a5e343db..cc21388cb1 100644 --- a/tests/python_client/milvus_client/test_milvus_client_geometry.py +++ b/tests/python_client/milvus_client/test_milvus_client_geometry.py @@ -3441,7 +3441,7 @@ class TestMilvusClientGeometryNegative(TestMilvusClientV2Base): error = { ct.err_code: 65535, - ct.err_msg: "Create retrieve plan by expr failed", + ct.err_msg: "not found", } # We expect error for invalid spatial function self.query( client, diff --git a/tests/python_client/milvus_client/test_milvus_client_highlighter.py b/tests/python_client/milvus_client/test_milvus_client_highlighter.py index 6c2d8ced8b..666b9266a5 100644 --- a/tests/python_client/milvus_client/test_milvus_client_highlighter.py +++ b/tests/python_client/milvus_client/test_milvus_client_highlighter.py @@ -1111,8 +1111,7 @@ class TestMilvusClientHighlighter(TestMilvusClientV2Base): num_of_fragments=1) search_params = {"params": {"nlist": 128}, "metric_type": "BM25"} error = {ct.err_code: 1100, - ct.err_msg: f"failed to create query plan: cannot parse expression: TEXT_MATCH({default_text_field_name}, \"seat\"), " - f"error: field \"{default_text_field_name}\" does not enable match: invalid parameter"} + ct.err_msg: "does not enable match"} self.search( client, diff --git a/tests/python_client/milvus_client/test_milvus_client_index.py b/tests/python_client/milvus_client/test_milvus_client_index.py index 7442c21be9..e0b4a69663 100644 --- a/tests/python_client/milvus_client/test_milvus_client_index.py +++ b/tests/python_client/milvus_client/test_milvus_client_index.py @@ -204,7 +204,7 @@ class TestMilvusClientIndexInvalid(TestMilvusClientV2Base): index_params = self.prepare_index_params(client)[0] index_params.add_index(field_name="vector", index_type="IVF_FLAT", metric_type="L2") # 3. create another index - error = {ct.err_code: 65535, ct.err_msg: "CreateIndex failed: at most one distinct index is allowed per field"} + error = {ct.err_code: 1100, ct.err_msg: "at most one distinct index is allowed per field"} self.create_index(client, collection_name, index_params, check_task=CheckTasks.err_res, check_items=error) self.drop_collection(client, collection_name) @@ -237,8 +237,7 @@ class TestMilvusClientIndexInvalid(TestMilvusClientV2Base): index_params.add_index(field_name="embeddings", index_type=not_supported_index, metric_type="L2") # 4. create another index error = {ct.err_code: 1100, - ct.err_msg: f"data type Int8Vector can't build with this index {not_supported_index}: " - f"invalid parameter[expected=valid index params][actual=invalid index params]"} + ct.err_msg: f"data type Int8Vector can't build with this index {not_supported_index}"} self.create_index(client, collection_name, index_params, check_task=CheckTasks.err_res, check_items=error) self.drop_collection(client, collection_name) @@ -821,9 +820,7 @@ class TestMilvusClientJsonPathIndexInvalid(TestMilvusClientV2Base): index_params.add_index(field_name=default_vector_field_name, index_type="AUTOINDEX", metric_type="COSINE") index_params.add_index(field_name="my_json", index_type="INVERTED") # 3. create index - error = {ct.err_code: 1100, ct.err_msg: "json index must specify cast type: missing parameter" - "[missing_param=json_cast_type]: invalid parameter" - "[expected=valid index params][actual=invalid index params]"} + error = {ct.err_code: 1101, ct.err_msg: "json index must specify cast type: missing parameter"} self.create_index(client, collection_name, index_params, check_task=CheckTasks.err_res, check_items=error) # 4. prepare index params with no json_cast_type @@ -831,9 +828,7 @@ class TestMilvusClientJsonPathIndexInvalid(TestMilvusClientV2Base): index_params.add_index(field_name=default_vector_field_name, index_type="AUTOINDEX", metric_type="COSINE") index_params.add_index(field_name="my_json", index_type="INVERTED", params={"json_path": "my_json['a']['b']"}) # 5. create index - error = {ct.err_code: 1100, ct.err_msg: "json index must specify cast type: missing parameter" - "[missing_param=json_cast_type]: invalid parameter" - "[expected=valid index params][actual=invalid index params]"} + error = {ct.err_code: 1101, ct.err_msg: "json index must specify cast type: missing parameter"} self.create_index(client, collection_name, index_params, check_task=CheckTasks.err_res, check_items=error) # 6. prepare index params with no json_path @@ -908,8 +903,7 @@ class TestMilvusClientJsonPathIndexInvalid(TestMilvusClientV2Base): not_supported_varchar_scalar_index = "bitmap index" got_json_suffix = "" error = {ct.err_code: 1100, ct.err_msg: f"{not_supported_varchar_scalar_index} are only supported on " - f"{supported_field_type} field{got_json_suffix}: invalid parameter[expected=valid " - f"index params][actual=invalid index params]"} + f"{supported_field_type} field{got_json_suffix}"} self.create_index(client, collection_name, index_params, check_task=CheckTasks.err_res, check_items=error) @@ -944,7 +938,7 @@ class TestMilvusClientJsonPathIndexInvalid(TestMilvusClientV2Base): params={"json_cast_type": invalid_json_cast_type, "json_path": f"{json_field_name}['a']['b']"}) # 3. create index - error = {ct.err_code: 1100, ct.err_msg: f"index params][actual=invalid index params]"} + error = {ct.err_code: 1100, ct.err_msg: "is not supported"} self.create_index(client, collection_name, index_params, check_task=CheckTasks.err_res, check_items=error) @@ -979,7 +973,7 @@ class TestMilvusClientJsonPathIndexInvalid(TestMilvusClientV2Base): params={"json_cast_type": cast_type, "json_path": f"{json_field_name}['a']['b']"}) # 3. create index - error = {ct.err_code: 1100, ct.err_msg: f"index params][actual=invalid index params]"} + error = {ct.err_code: 1100, ct.err_msg: "is not supported"} self.create_index(client, collection_name, index_params, check_task=CheckTasks.err_res, check_items=error) @@ -1013,7 +1007,11 @@ class TestMilvusClientJsonPathIndexInvalid(TestMilvusClientV2Base): index_type=supported_double_scalar_index, params={"json_cast_type": "Double", "json_path": invalid_json_path}) # 3. create index - error = {ct.err_code: 65535, ct.err_msg: f"cannot parse identifier: {invalid_json_path}"} + if invalid_json_path == '/': + json_path_err_msg = "mismatched input '/'" + else: + json_path_err_msg = f"cannot parse identifier: {invalid_json_path}" + error = {ct.err_code: 65535, ct.err_msg: json_path_err_msg} self.create_index(client, collection_name, index_params, check_task=CheckTasks.err_res, check_items=error) @@ -1075,7 +1073,7 @@ class TestMilvusClientJsonPathIndexInvalid(TestMilvusClientV2Base): index_type=supported_double_scalar_index, params={"json_cast_type": "varchar", "json_path": f"{json_field_name}['a']"}) # 5. create index - error = {ct.err_code: 65535, ct.err_msg: "CreateIndex failed: at most one distinct index is allowed per field"} + error = {ct.err_code: 1100, ct.err_msg: "at most one distinct index is allowed per field"} self.create_index(client, collection_name, index_params, check_task=CheckTasks.err_res, check_items=error) @@ -1164,7 +1162,7 @@ class TestMilvusClientJsonPathIndexInvalid(TestMilvusClientV2Base): params={"json_cast_type": supported_json_cast_type, "json_path": f"{json_field_name}"}) # 4. create index - error = {ct.err_code: 65535, ct.err_msg: "CreateIndex failed: at most one distinct index is allowed per field"} + error = {ct.err_code: 1100, ct.err_msg: "at most one distinct index is allowed per field"} self.create_index(client, collection_name, index_params, check_task=CheckTasks.err_res, check_items=error) diff --git a/tests/python_client/milvus_client/test_milvus_client_partition.py b/tests/python_client/milvus_client/test_milvus_client_partition.py index 5261973fa4..0ce676e0a8 100644 --- a/tests/python_client/milvus_client/test_milvus_client_partition.py +++ b/tests/python_client/milvus_client/test_milvus_client_partition.py @@ -1519,8 +1519,7 @@ class TestPartitionParams(TestMilvusClientV2Base): self.create_index(client, collection_name, index_params) # load with 3 replicas error = {ct.err_code: 65535, - ct.err_msg: "when load 3 replica count: service resource insufficient" - "[currentStreamingNode=2][expectedStreamingNode=3]"} + ct.err_msg: "service resource insufficient"} self.load_partitions(client, collection_name, [partition_name], replica_number=3, check_task=CheckTasks.err_res, check_items=error) diff --git a/tests/python_client/milvus_client/test_milvus_client_query.py b/tests/python_client/milvus_client/test_milvus_client_query.py index dd8c61076d..e984210570 100644 --- a/tests/python_client/milvus_client/test_milvus_client_query.py +++ b/tests/python_client/milvus_client/test_milvus_client_query.py @@ -213,7 +213,7 @@ class TestMilvusClientQueryInvalid(TestMilvusClientV2Base): else: error = { ct.err_code: 65535, - ct.err_msg: f"cannot parse expression: {term_expr}, error: field invalid_field not exist", + ct.err_msg: "field invalid_field not exist", } self.query(client, collection_name, filter=term_expr, check_task=CheckTasks.err_res, check_items=error) self.drop_collection(client, collection_name) @@ -499,15 +499,14 @@ class TestMilvusClientQueryInvalidShared(TestMilvusClientV2Base): expr_1 = f"{ct.default_int64_field_name} inn [1, 2]" error_1 = { ct.err_code: 65535, - ct.err_msg: "cannot parse expression: int64 inn [1, 2], error: invalid expression: int64 inn [1, 2]", + ct.err_msg: "invalid expression: int64 inn [1, 2]", } self.query(client, INVALID_SHARED_COLLECTION, filter=expr_1, check_task=CheckTasks.err_res, check_items=error_1) expr_2 = f"{ct.default_int64_field_name} in not [1, 2]" error_2 = { ct.err_code: 65535, - ct.err_msg: "cannot parse expression: int64 in not [1, 2], " - "error: not can only apply on boolean: invalid parameter", + ct.err_msg: "not can only apply on boolean", } self.query(client, INVALID_SHARED_COLLECTION, filter=expr_2, check_task=CheckTasks.err_res, check_items=error_2) @@ -523,15 +522,14 @@ class TestMilvusClientQueryInvalidShared(TestMilvusClientV2Base): for expr in exprs: error = { ct.err_code: 1100, - ct.err_msg: f"cannot parse expression: {expr}, error: the right-hand side of 'in' must be a list", + ct.err_msg: "the right-hand side of 'in' must be a list", } self.query(client, INVALID_SHARED_COLLECTION, filter=expr, check_task=CheckTasks.err_res, check_items=error) expr = f"{ct.default_int64_field_name} in (mn)" error = { ct.err_code: 1100, - ct.err_msg: f"cannot parse expression: {expr}, " - "error: value '(mn)' in list cannot be a non-const expression", + ct.err_msg: "value '(mn)' in list cannot be a non-const expression", } self.query(client, INVALID_SHARED_COLLECTION, filter=expr, check_task=CheckTasks.err_res, check_items=error) @@ -548,8 +546,7 @@ class TestMilvusClientQueryInvalidShared(TestMilvusClientV2Base): term_expr = f"{default_primary_key_field_name} in {values}" error = { ct.err_code: 1100, - ct.err_msg: f"failed to create query plan: cannot parse expression: {term_expr}, " - "error: value 'float_val:1' in list cannot be casted to Int64", + ct.err_msg: "value 'float_val:1' in list cannot be casted to Int64", } self.query( client, INVALID_SHARED_COLLECTION, filter=term_expr, check_task=CheckTasks.err_res, check_items=error @@ -559,8 +556,7 @@ class TestMilvusClientQueryInvalidShared(TestMilvusClientV2Base): term_expr = f"{default_primary_key_field_name} in {values}" error = { ct.err_code: 1100, - ct.err_msg: f"failed to create query plan: cannot parse expression: {term_expr}, " - "error: value 'float_val:2' in list cannot be casted to Int64", + ct.err_msg: "value 'float_val:2' in list cannot be casted to Int64", } self.query( client, INVALID_SHARED_COLLECTION, filter=term_expr, check_task=CheckTasks.err_res, check_items=error @@ -681,7 +677,10 @@ class TestMilvusClientQueryInvalidShared(TestMilvusClientV2Base): expected: raise invalid-limit error """ client = self._client() - error = {ct.err_code: 1, ct.err_msg: f"limit [{limit}] is invalid"} + # milvus strips leading/trailing whitespace before echoing the value + # in the error message, so " " becomes "" in `limit [] is invalid` + displayed = limit.strip() if isinstance(limit, str) else limit + error = {ct.err_code: 1, ct.err_msg: f"limit [{displayed}] is invalid"} self.query( client, INVALID_SHARED_COLLECTION, @@ -729,7 +728,10 @@ class TestMilvusClientQueryInvalidShared(TestMilvusClientV2Base): expected: raise invalid-offset error """ client = self._client() - error = {ct.err_code: 1, ct.err_msg: f"offset [{offset}] is invalid"} + # milvus strips leading/trailing whitespace before echoing the value + # in the error message, so " " becomes "" in `offset [] is invalid` + displayed = offset.strip() if isinstance(offset, str) else offset + error = {ct.err_code: 1, ct.err_msg: f"offset [{displayed}] is invalid"} self.query( client, INVALID_SHARED_COLLECTION, @@ -1550,8 +1552,7 @@ class TestMilvusClientQueryValid(TestMilvusClientV2Base): not_support_expr = f"{ct.default_bool_field_name} in [0]" error = { ct.err_code: 65535, - ct.err_msg: "cannot parse expression: bool in [0], error: " - "value 'int64_val:0' in list cannot be casted to Bool", + ct.err_msg: "value 'int64_val:0' in list cannot be casted to Bool", } self.query( client, @@ -5031,7 +5032,7 @@ class TestQueryStringPrimaryShared(TestMilvusClientV2Base): expression = 'float like "0%"' error = { ct.err_code: 65535, - ct.err_msg: f"cannot parse expression: {expression}, error: like operation on non-string or no-json field is unsupported", + ct.err_msg: "like operation on non-string or no-json field is unsupported", } self.query( client, @@ -5080,8 +5081,7 @@ class TestQueryStringPrimaryShared(TestMilvusClientV2Base): expression = "varchar == int64" error = { ct.err_code: 1100, - ct.err_msg: f"failed to create query plan: cannot parse expression: {expression}, " - f"error: comparisons between VarChar and Int64 are not supported: invalid parameter", + ct.err_msg: "comparisons between VarChar and Int64 are not supported", } self.query( client, diff --git a/tests/python_client/milvus_client/test_milvus_client_rbac.py b/tests/python_client/milvus_client/test_milvus_client_rbac.py index ee11e91c90..1486b6b036 100644 --- a/tests/python_client/milvus_client/test_milvus_client_rbac.py +++ b/tests/python_client/milvus_client/test_milvus_client_rbac.py @@ -10,6 +10,14 @@ from pymilvus import DataType from utils.util_log import test_log as log from utils.util_pymilvus import * # noqa: F403 +# RBAC test classes share teardown logic that lists and drops every user, +# role, privilege group, and database on the server. Under pytest-xdist `-n` +# this cross-deletes objects owned by sibling workers. Pin the whole module +# to a single xdist worker (loadgroup) so tests run serially without +# starving other test files of parallelism. +pytestmark = pytest.mark.xdist_group(name="rbac_serial") + + prefix = "client_rbac" diff --git a/tests/python_client/milvus_client/test_milvus_client_search.py b/tests/python_client/milvus_client/test_milvus_client_search.py index bc13a2d999..de4a8757a7 100644 --- a/tests/python_client/milvus_client/test_milvus_client_search.py +++ b/tests/python_client/milvus_client/test_milvus_client_search.py @@ -226,8 +226,7 @@ class TestMilvusClientSearchInvalid(TestMilvusClientV2Base): collection_name = cf.gen_collection_name_by_testcase_name() # 1. create collection error = {ct.err_code: 1100, - ct.err_msg: "float vector index does not support metric type: invalid: " - "invalid parameter[expected=valid index params][actual=invalid index params]"} + ct.err_msg: "float vector index does not support metric type"} self.create_collection(client, collection_name, default_dim, metric_type="invalid", check_task=CheckTasks.err_res, check_items=error) @@ -321,8 +320,7 @@ class TestMilvusClientSearchInvalid(TestMilvusClientV2Base): for null_expr_op in null_expr_ops: null_expr = not_exist_field_name + " " + null_expr_op error = {ct.err_code: 1100, - ct.err_msg: f"failed to create query plan: cannot parse expression: " - f"{null_expr}, error: field {not_exist_field_name} not exist: invalid parameter"} + ct.err_msg: f"field {not_exist_field_name} not exist"} self.search(client, collection_name, vectors_to_search, filter=null_expr, check_task=CheckTasks.err_res, check_items=error) @@ -1300,8 +1298,7 @@ class TestSearchInvalidIndependent(TestMilvusClientV2Base): filter=expression, check_task=CheckTasks.err_res, check_items={"err_code": 1100, - "err_msg": f"cannot parse expression: !{ct.default_bool_field_name}, " - "error: not op can only be applied on boolean expression"}) + "err_msg": "not op can only be applied on boolean expression"}) expression = f"{ct.default_int64_field_name} > 0 and {ct.default_bool_field_name}" log.debug(f"search with expression: {expression}") self.search(client, collection_name, @@ -1310,8 +1307,7 @@ class TestSearchInvalidIndependent(TestMilvusClientV2Base): filter=expression, check_task=CheckTasks.err_res, check_items={"err_code": 1100, - "err_msg": f"cannot parse expression: {ct.default_int64_field_name} > 0 and {ct.default_bool_field_name}, " - "error: 'and' can only be used between boolean expressions"}) + "err_msg": "'and' can only be used between boolean expressions"}) @pytest.mark.tags(CaseLabel.L1) def test_search_with_expression_invalid_array_one(self): @@ -2032,7 +2028,7 @@ class TestSearchInvalidIndependent(TestMilvusClientV2Base): filter=expr, check_task=CheckTasks.err_res, check_items={"err_code": 1100, - "err_msg": "error: two column comparison with JSON type is not supported"}) + "err_msg": "two column comparison with JSON type is not supported"}) @pytest.mark.tags(CaseLabel.L2) def test_search_ef_less_than_limit(self): @@ -3240,7 +3236,7 @@ class TestMilvusClientSearchValid(TestMilvusClientV2Base): search_params=search_params, limit=default_limit) not_supported_hints = "not_supported_hints" error = {ct.err_code: 0, - ct.err_msg: f"Create Plan by expr failed: => hints: {not_supported_hints} not supported"} + ct.err_msg: f"hints: {not_supported_hints} not supported"} search_params = {'hints': not_supported_hints, 'params': cf.get_search_params_params('IVF_FLAT')} self.search(client, collection_name, data=[search_vector], filter='id >= 10', diff --git a/tests/python_client/milvus_client/test_milvus_client_search_by_pk.py b/tests/python_client/milvus_client/test_milvus_client_search_by_pk.py index 789c5193b1..02cf5f7cdf 100644 --- a/tests/python_client/milvus_client/test_milvus_client_search_by_pk.py +++ b/tests/python_client/milvus_client/test_milvus_client_search_by_pk.py @@ -2179,7 +2179,7 @@ class TestSearchByPkIndependent(TestMilvusClientV2Base): filter=search_exp, output_fields=output_fields, check_task=CheckTasks.err_res, - check_items={"err_code": 1100, "err_msg": "error: comparisons between VarChar and Int64 are not supported"}, + check_items={"err_code": 1100, "err_msg": "comparisons between VarChar and Int64 are not supported"}, ) @pytest.mark.tags(CaseLabel.L2) diff --git a/tests/python_client/milvus_client/test_milvus_client_search_iterator.py b/tests/python_client/milvus_client/test_milvus_client_search_iterator.py index a30379a157..1b4954507d 100644 --- a/tests/python_client/milvus_client/test_milvus_client_search_iterator.py +++ b/tests/python_client/milvus_client/test_milvus_client_search_iterator.py @@ -298,8 +298,7 @@ class TestMilvusClientSearchIteratorInValid(TestMilvusClientV2Base): # 3. search vectors_to_search = cf.gen_vectors(1, default_dim) error = {ct.err_code: 1100, - ct.err_msg: f"failed to create query plan: predicate is not a boolean expression: invalidexpr, " - f"data type: JSON: invalid parameter"} + ct.err_msg: "predicate is not a boolean expression"} self.search_iterator(client, collection_name, vectors_to_search, filter=expr, batch_size=20, diff --git a/tests/python_client/milvus_client/test_milvus_client_search_json.py b/tests/python_client/milvus_client/test_milvus_client_search_json.py index f4939a4972..c61e7c3d9e 100644 --- a/tests/python_client/milvus_client/test_milvus_client_search_json.py +++ b/tests/python_client/milvus_client/test_milvus_client_search_json.py @@ -323,12 +323,10 @@ class TestSearchArrayShared(TestMilvusClientV2Base): vectors = cf.gen_vectors(default_nq, default_dim) expression = f"{expr_prefix}({ct.default_string_array_field_name}, '1000')" error = {ct.err_code: 1100, - ct.err_msg: f"cannot parse expression: {expression}, " - f"error: ContainsAll operation element must be an array"} + ct.err_msg: "ContainsAll operation element must be an array"} if expr_prefix in ["array_contains_any", "ARRAY_CONTAINS_ANY"]: error = {ct.err_code: 1100, - ct.err_msg: f"cannot parse expression: {expression}, " - f"error: ContainsAny operation element must be an array"} + ct.err_msg: "ContainsAny operation element must be an array"} self.search(client, self.collection_name, data=vectors[:default_nq], anns_field=default_search_field, diff --git a/tests/python_client/milvus_client/test_milvus_client_search_v2.py b/tests/python_client/milvus_client/test_milvus_client_search_v2.py index cd0b991df8..e3391f77f3 100644 --- a/tests/python_client/milvus_client/test_milvus_client_search_v2.py +++ b/tests/python_client/milvus_client/test_milvus_client_search_v2.py @@ -1875,7 +1875,7 @@ class TestSearchV2Independent(TestMilvusClientV2Base): output_fields=output_fields, check_task=CheckTasks.err_res, check_items={"err_code": 1100, - "err_msg": "error: comparisons between VarChar and Int64 are not supported"}) + "err_msg": "comparisons between VarChar and Int64 are not supported"}) @pytest.mark.tags(CaseLabel.L2) def test_search_using_all_types_of_default_value(self): diff --git a/tests/python_client/milvus_client/test_milvus_client_snapshot.py b/tests/python_client/milvus_client/test_milvus_client_snapshot.py index dd1d034ff8..df40507254 100644 --- a/tests/python_client/milvus_client/test_milvus_client_snapshot.py +++ b/tests/python_client/milvus_client/test_milvus_client_snapshot.py @@ -4474,7 +4474,7 @@ class TestMilvusClientSnapshotAlias(TestMilvusClientSnapshotBase): self.create_alias(client, col_src, alias_name) # 2. Restore with target_collection_name = existing alias name must fail - error = {ct.err_code: 65535, ct.err_msg: "conflicts with an existing alias"} + error = {ct.err_code: 1601, ct.err_msg: "alias and collection name conflict"} self.restore_snapshot( client, snapshot_name, @@ -4502,9 +4502,16 @@ class TestMilvusClientSnapshotAlias(TestMilvusClientSnapshotBase): @pytest.mark.tags(CaseLabel.RBAC) +@pytest.mark.xdist_group(name="snapshot_rbac_serial") class TestMilvusClientSnapshotRbac(TestMilvusClientSnapshotBase): """ Test RBAC v2 privilege enforcement for snapshot operations. + + Pinned to a single xdist worker via ``xdist_group`` because the + teardown does a global ``list_users()`` / ``list_roles()`` and drops + everything non-default. Under ``-n>1`` that cross-deletes objects + owned by other workers and causes cascading "role not found" / + "role has privileges" failures (same root cause as #49699). """ user_pre = "snap_user" diff --git a/tests/python_client/milvus_client/test_milvus_client_timestamptz.py b/tests/python_client/milvus_client/test_milvus_client_timestamptz.py index a05aab249c..e724e6cef6 100644 --- a/tests/python_client/milvus_client/test_milvus_client_timestamptz.py +++ b/tests/python_client/milvus_client/test_milvus_client_timestamptz.py @@ -1034,7 +1034,7 @@ class TestMilvusClientTimestamptzInvalid(TestMilvusClientV2Base): index_params.add_index(default_primary_key_field_name, index_type="AUTOINDEX") index_params.add_index(default_vector_field_name, index_type="AUTOINDEX") index_params.add_index(default_timestamp_field_name, index_type="INVERTED") - error = {ct.err_code: 1100, ct.err_msg: "INVERTED are not supported on Timestamptz field: invalid parameter[expected=valid index params][actual=invalid index params]"} + error = {ct.err_code: 1100, ct.err_msg: "INVERTED are not supported on Timestamptz field"} self.create_collection(client, collection_name, default_dim, schema=schema, consistency_level="Strong", index_params=index_params, check_task=CheckTasks.err_res, @@ -1122,8 +1122,8 @@ class TestMilvusClientTimestamptzInvalid(TestMilvusClientV2Base): consistency_level="Strong", index_params=index_params) # step 2: add field with default value for timestamptz field - error = {ct.err_code: 1100, - ct.err_msg: f"invalid default value of field, name: {default_timestamp_field_name}, err: %!w(*errors.errorString=&{{invalid timestamp string: '1234'. Does not match any known format}}): invalid parameter"} + error = {ct.err_code: 1100, + ct.err_msg: "invalid timestamp string: '1234'. Does not match any known format"} self.add_collection_field(client, collection_name, field_name=default_timestamp_field_name, data_type=DataType.TIMESTAMPTZ, nullable=True, default_value="1234", check_task=CheckTasks.err_res, check_items=error) diff --git a/tests/python_client/testcases/async_milvus_client/test_index_async.py b/tests/python_client/testcases/async_milvus_client/test_index_async.py index 78ab4c4c08..6cc76e50ae 100644 --- a/tests/python_client/testcases/async_milvus_client/test_index_async.py +++ b/tests/python_client/testcases/async_milvus_client/test_index_async.py @@ -170,8 +170,7 @@ class TestAsyncMilvusClientIndexInvalid(TestMilvusClientV2Base): index_params = async_client.prepare_index_params()[0] index_params.add_index(field_name="vector", metric_type=metric) # 3. create index - error = {ct.err_code: 1100, ct.err_msg: f"float vector index does not support metric type: {metric}: " - f"invalid parameter[expected=valid index params][actual=invalid index params"} + error = {ct.err_code: 1100, ct.err_msg: f"float vector index does not support metric type: {metric}"} # It's good to show what the valid index params are await async_client.create_index(collection_name, index_params, check_task=CheckTasks.err_res, diff --git a/tests/python_client/testcases/test_delete.py b/tests/python_client/testcases/test_delete.py index 1f957c5ead..f66d779934 100644 --- a/tests/python_client/testcases/test_delete.py +++ b/tests/python_client/testcases/test_delete.py @@ -1838,8 +1838,8 @@ class TestDeleteString(TestcaseBase): self.init_collection_general(prefix, nb=tmp_nb, insert_data=True, primary_field=ct.default_string_field_name)[0] collection_w.load() error = {ct.err_code: 1100, - ct.err_msg: f"failed to create delete plan: cannot parse expression: {default_invalid_string_exp}, " - f"error: comparisons between VarChar and Int64 are not supported: invalid parameter"} + ct.err_msg: f"failed to create delete plan: cannot parse expression: {default_invalid_string_exp}: " + f"comparisons between VarChar and Int64 are not supported: query plan failed: invalid parameter"} collection_w.delete(expr=default_invalid_string_exp, check_task=CheckTasks.err_res, check_items=error) diff --git a/tests/python_client/testcases/test_index.py b/tests/python_client/testcases/test_index.py index c38562f5bd..460016b54a 100644 --- a/tests/python_client/testcases/test_index.py +++ b/tests/python_client/testcases/test_index.py @@ -233,7 +233,7 @@ class TestIndexOperation(TestcaseBase): c_name = cf.gen_unique_str(prefix) collection_w = self.init_collection_wrap(name=c_name) self.index_wrap.init_index(collection_w.collection, default_field_name, default_index_params) - error = {ct.err_code: 65535, ct.err_msg: "CreateIndex failed: at most one distinct index is allowed per field"} + error = {ct.err_code: 1100, ct.err_msg: "at most one distinct index is allowed per field"} self.index_wrap.init_index( collection_w.collection, default_field_name, @@ -1291,7 +1291,7 @@ class TestIndexInvalid(TestcaseBase): ct.default_float_vec_field_name, index_params=scalar_index_params, check_task=CheckTasks.err_res, - check_items={ct.err_code: 1100, ct.err_msg: "invalid index params"}, + check_items={ct.err_code: 1100, ct.err_msg: "metric type not set for vector index"}, ) @pytest.mark.tags(CaseLabel.L1) @@ -1524,7 +1524,7 @@ class TestIndexInvalid(TestcaseBase): @pytest.mark.tags(CaseLabel.L2) @pytest.mark.parametrize("ratio", [-0.5, 1, 3]) - @pytest.mark.parametrize("index ", ct.all_index_types[10:12]) + @pytest.mark.parametrize("index", ct.all_index_types[10:12]) def test_invalid_sparse_ratio(self, ratio, index): """ target: index creation for unsupported ratio parameter @@ -1551,7 +1551,7 @@ class TestIndexInvalid(TestcaseBase): @pytest.mark.tags(CaseLabel.L2) @pytest.mark.parametrize("inverted_index_algo", ["INVALID_ALGO"]) - @pytest.mark.parametrize("index ", ct.all_index_types[10:12]) + @pytest.mark.parametrize("index", ct.all_index_types[10:12]) def test_invalid_sparse_inverted_index_algo(self, inverted_index_algo, index): """ target: index creation for unsupported sparse inverted index algo parameter @@ -1790,7 +1790,7 @@ class TestIndexString(TestcaseBase): default_index_params, index_name=index_name2, check_task=CheckTasks.err_res, - check_items={ct.err_code: 1, ct.err_msg: "CreateIndex failed"}, + check_items={ct.err_code: 1100, ct.err_msg: "at most one distinct index is allowed per field"}, ) @pytest.mark.tags(CaseLabel.L1) diff --git a/tests/python_client/testcases/test_text_embedding_function_e2e.py b/tests/python_client/testcases/test_text_embedding_function_e2e.py index cfc3a3de3a..3bf597cd66 100644 --- a/tests/python_client/testcases/test_text_embedding_function_e2e.py +++ b/tests/python_client/testcases/test_text_embedding_function_e2e.py @@ -1947,7 +1947,7 @@ class TestTextEmbeddingFunctionCURDNegative(TestcaseBase): """ target: test add function with duplicate name method: create collection with function, try to add another function with same name - expected: error indicating duplicate function name (code=65535) + expected: error indicating duplicate function name (code=1100) """ self._connect() dim = 768 @@ -1988,14 +1988,14 @@ class TestTextEmbeddingFunctionCURDNegative(TestcaseBase): assert False, "Expected exception for duplicate function name" except Exception as e: log.info(f"Expected error: {e}") - assert e.code == 65535 + assert e.code == 1100 assert "duplicate function name" in str(e) def test_add_collection_function_missing_input_field(self, tei_endpoint): """ target: test add function with input field that doesn't exist method: add function referencing non-existent input field - expected: error indicating input field not found (code=65535) + expected: error indicating input field not found (code=1100) """ self._connect() dim = 768 @@ -2025,14 +2025,14 @@ class TestTextEmbeddingFunctionCURDNegative(TestcaseBase): assert False, "Expected exception for missing input field" except Exception as e: log.info(f"Expected error: {e}") - assert e.code == 65535 + assert e.code == 1100 assert "function input field not found" in str(e) def test_add_collection_function_missing_output_field(self, tei_endpoint): """ target: test add function with output field that doesn't exist method: add function referencing non-existent output field - expected: error indicating output field not found (code=65535) + expected: error indicating output field not found (code=1100) """ self._connect() dim = 768 @@ -2062,14 +2062,14 @@ class TestTextEmbeddingFunctionCURDNegative(TestcaseBase): assert False, "Expected exception for missing output field" except Exception as e: log.info(f"Expected error: {e}") - assert e.code == 65535 + assert e.code == 1100 assert "function output field not found" in str(e) def test_add_collection_function_dim_mismatch(self, tei_endpoint): """ target: test add function with dimension mismatch method: create collection with vector field dim=512, add function for model that outputs dim=768 - expected: error indicating dimension mismatch (code=65535) + expected: error indicating dimension mismatch (code=2400) """ self._connect() dim = 512 # Mismatched dimension (TEI model outputs 768) @@ -2099,7 +2099,7 @@ class TestTextEmbeddingFunctionCURDNegative(TestcaseBase): assert False, "Expected exception for dimension mismatch" except Exception as e: log.info(f"Expected error: {e}") - assert e.code == 65535 + assert e.code == 2400 assert "embedding dim" in str(e) # ==================== alter_collection_function negative tests ==================== @@ -2135,7 +2135,7 @@ class TestTextEmbeddingFunctionCURDNegative(TestcaseBase): """ target: test alter function that doesn't exist method: create collection without function, try to alter non-existent function - expected: error indicating function not found (code=65535) + expected: error indicating function not found (code=1100) """ self._connect() dim = 768 @@ -2165,14 +2165,15 @@ class TestTextEmbeddingFunctionCURDNegative(TestcaseBase): assert False, "Expected exception for nonexistent function" except Exception as e: log.info(f"Expected error: {e}") - assert e.code == 65535 + assert e.code == 1100 assert "not found" in str(e) def test_alter_collection_function_invalid_new_endpoint(self, tei_endpoint): """ target: test alter function with invalid endpoint method: create collection with valid function, alter to use invalid endpoint - expected: error indicating endpoint unreachable (code=65535) + expected: error indicating endpoint unreachable (code=2, ServiceUnavailable: + a transport/connect failure to the model backend is retryable) """ self._connect() dim = 768 @@ -2213,8 +2214,10 @@ class TestTextEmbeddingFunctionCURDNegative(TestcaseBase): assert False, "Expected exception for invalid endpoint" except Exception as e: log.info(f"Expected error: {e}") - assert e.code == 65535 - assert "check function" in str(e).lower() and "failed" in str(e).lower() + # transport/connect failure to the model backend is now classified as + # ServiceUnavailable (2, retryable) rather than FunctionFailed (2400) + assert e.code == 2 + assert "invalid_endpoint_12345" in str(e).lower() # ==================== drop_collection_function negative tests ==================== diff --git a/tests/python_client/testcases/test_utility.py b/tests/python_client/testcases/test_utility.py index cdb36ea018..47bea3bd17 100644 --- a/tests/python_client/testcases/test_utility.py +++ b/tests/python_client/testcases/test_utility.py @@ -543,8 +543,8 @@ class TestUtilityParams(TestcaseBase): self.utility_wrap.create_alias(old_collection_name, alias) self.utility_wrap.rename_collection(old_collection_name, alias, check_task=CheckTasks.err_res, - check_items={"err_code": 65535, - "err_msg": f"cannot rename collection to an existing alias: {alias}"}) + check_items={"err_code": 1601, + "err_msg": f"alias and collection name conflict[database=default][alias={alias}]"}) @pytest.mark.tags(CaseLabel.L1) def test_rename_collection_using_alias(self): diff --git a/tests/restful_client_v2/testcases/test_collection_operations.py b/tests/restful_client_v2/testcases/test_collection_operations.py index 709dabf62c..a0efe4c2bb 100644 --- a/tests/restful_client_v2/testcases/test_collection_operations.py +++ b/tests/restful_client_v2/testcases/test_collection_operations.py @@ -451,7 +451,7 @@ class TestCreateCollection(TestBase): } logging.info(f"create collection {name} with payload: {payload}") rsp = client.collection_create(payload) - assert rsp["code"] == 65535 + assert rsp["code"] == 1100 rsp = client.collection_list() all_collections = rsp["data"] diff --git a/tests/restful_client_v2/testcases/test_index_operation.py b/tests/restful_client_v2/testcases/test_index_operation.py index b541ab0794..87b255f2ce 100644 --- a/tests/restful_client_v2/testcases/test_index_operation.py +++ b/tests/restful_client_v2/testcases/test_index_operation.py @@ -678,4 +678,4 @@ class TestCreateIndexNegative(TestBase): payload["indexParams"][0]["params"]["nlist"] = "16384" rsp = self.index_client.index_create(payload) assert rsp["code"] == 1100 - assert "not supported" in rsp["message"] + assert "does not support metric type" in rsp["message"] diff --git a/tests/restful_client_v2/testcases/test_jobs_operation.py b/tests/restful_client_v2/testcases/test_jobs_operation.py index bb47a54ba8..a16977b0b1 100644 --- a/tests/restful_client_v2/testcases/test_jobs_operation.py +++ b/tests/restful_client_v2/testcases/test_jobs_operation.py @@ -4052,7 +4052,7 @@ class TestCreateImportJobNegative(TestBase): } } rsp = self.import_job_client.create_import_jobs(payload) - assert rsp['code'] == 1100 and "invalid" in rsp['message'] + assert rsp['code'] == 1101 and "partition not specified" in rsp['message'] def test_import_job_with_wrong_file_type(self): # create collection diff --git a/tests/restful_client_v2/testcases/test_partition_operation.py b/tests/restful_client_v2/testcases/test_partition_operation.py index 44717b5686..b4a256ce4e 100644 --- a/tests/restful_client_v2/testcases/test_partition_operation.py +++ b/tests/restful_client_v2/testcases/test_partition_operation.py @@ -113,7 +113,7 @@ class TestPartitionE2E(TestBase): assert rsp['code'] == 0 # drop partition when it is loaded rsp = self.partition_client.partition_drop(collection_name=name, partition_name=partition_name) - assert rsp['code'] == 65535 + assert rsp['code'] == 1100 # drop partition after release rsp = self.partition_client.partition_release(collection_name=name, partition_names=[partition_name]) rsp = self.partition_client.partition_drop(collection_name=name, partition_name=partition_name) diff --git a/tests/restful_client_v2/testcases/test_user_operation.py b/tests/restful_client_v2/testcases/test_user_operation.py index b3cc0e5b76..2e37503d6d 100644 --- a/tests/restful_client_v2/testcases/test_user_operation.py +++ b/tests/restful_client_v2/testcases/test_user_operation.py @@ -160,5 +160,5 @@ class TestUserNegative(TestBase): if i == 0: assert rsp['code'] == 0 else: - assert rsp['code'] == 65535 + assert rsp['code'] == 1100 assert "user already exists" in rsp['message'] diff --git a/tests/restful_client_v2/testcases/test_vector_operations.py b/tests/restful_client_v2/testcases/test_vector_operations.py index 9d80d52ee7..3b6142d4e7 100644 --- a/tests/restful_client_v2/testcases/test_vector_operations.py +++ b/tests/restful_client_v2/testcases/test_vector_operations.py @@ -3345,7 +3345,7 @@ class TestSearchVectorNegative(TestBase): "offset": 0, } rsp = self.vector_client.vector_search(payload) - assert rsp["code"] == 65535 + assert rsp['code'] == 1100 @pytest.mark.parametrize("offset", [-1, 100_001]) def test_search_vector_with_invalid_offset(self, offset): @@ -3370,7 +3370,7 @@ class TestSearchVectorNegative(TestBase): "offset": offset, } rsp = self.vector_client.vector_search(payload) - assert rsp["code"] == 65535 + assert rsp['code'] == 1100 def test_search_vector_with_invalid_collection_name(self): """ @@ -4271,8 +4271,8 @@ class TestHybridSearchVector(TestBase): rsp = self.vector_client.vector_hybrid_search(payload) # When offset + limit exceeds max allowed if large_offset + limit > MAX_SUM_OFFSET_AND_LIMIT: - assert rsp["code"] == 65535 - assert "exceeds" in rsp["message"] or "invalid" in rsp["message"].lower() + assert rsp['code'] == 1100 + assert "exceeds" in rsp['message'] or "invalid" in rsp['message'].lower() # When offset is larger than the available results elif large_offset >= nb: # Should return empty results or handle gracefully