feat: use tl::unexpected in Index default implementations#2141
Conversation
Merge ProtectionsYour pull request matches the following merge protections and will not be merged until they are valid. 🟢 Require kind labelWonderful, this rule succeeded.
🟢 Require version labelWonderful, this rule succeeded.
🟢 Require linked issue for feature/bug PRsWonderful, this rule succeeded.
|
There was a problem hiding this comment.
Code Review
This pull request refactors include/vsag/index.h to return expected errors using the LOG_ERROR_AND_RETURNS macro instead of throwing std::runtime_error for unsupported index operations, alongside correcting some grammatical errors in the remaining exception messages. The review feedback highlights a garbled error message in the Tune method and identifies several formatting inconsistencies across multiple methods (such as Pretrain, CalcDistanceById, and GetRawVectorByIds), including cramped single-line function bodies, missing semicolons on macro calls, and redundant trailing semicolons.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| LOG_ERROR_AND_RETURNS(ErrorType::UNSUPPORTED_INDEX_OPERATION, | ||
| "Index does not support Tune supported by this index") |
There was a problem hiding this comment.
The error message string contains a garbled phrase: "Index does not support Tune supported by this index". It should be simplified to "Index does not support Tune" for clarity. Also, please append a semicolon ; to the macro call to adhere to standard C++ statement formatting.
LOG_ERROR_AND_RETURNS(ErrorType::UNSUPPORTED_INDEX_OPERATION,
"Index does not support Tune");| Pretrain(const std::vector<int64_t>& base_tag_ids, uint32_t k, const std::string& parameters){ | ||
| LOG_ERROR_AND_RETURNS(ErrorType::UNSUPPORTED_INDEX_OPERATION, | ||
| "Index does not support pretrain")}; |
There was a problem hiding this comment.
The formatting of this function is inconsistent and cramped. The opening brace { lacks a preceding space, the closing brace } is placed on the same line as the macro call, and there is a redundant trailing semicolon ; after the closing brace.
Please format it consistently with the rest of the class, and append a semicolon to the macro call.
Pretrain(const std::vector<int64_t>& base_tag_ids, uint32_t k, const std::string& parameters) {
LOG_ERROR_AND_RETURNS(ErrorType::UNSUPPORTED_INDEX_OPERATION,
"Index does not support pretrain");
}| CalcDistanceById(const float* vector, int64_t id, bool calculate_precise_distance = true) | ||
| const {LOG_ERROR_AND_RETURNS(ErrorType::UNSUPPORTED_INDEX_OPERATION, | ||
| "Index does not support get distance by id")}; |
There was a problem hiding this comment.
The formatting of this function is cramped and inconsistent. The function body is placed on the same line as the opening brace, and there is a redundant trailing semicolon after the closing brace.
Please format it cleanly on multiple lines and append a semicolon to the macro call.
CalcDistanceById(const float* vector, int64_t id, bool calculate_precise_distance = true) const {
LOG_ERROR_AND_RETURNS(ErrorType::UNSUPPORTED_INDEX_OPERATION,
"Index does not support get distance by id");
}| CalcDistanceById(const DatasetPtr& vector, int64_t id, bool calculate_precise_distance = true) | ||
| const {LOG_ERROR_AND_RETURNS(ErrorType::UNSUPPORTED_INDEX_OPERATION, | ||
| "Index does not support get distance by id")}; |
There was a problem hiding this comment.
The formatting of this function is cramped and inconsistent. Please format it cleanly on multiple lines and append a semicolon to the macro call.
CalcDistanceById(const DatasetPtr& vector, int64_t id, bool calculate_precise_distance = true) const {
LOG_ERROR_AND_RETURNS(ErrorType::UNSUPPORTED_INDEX_OPERATION,
"Index does not support get distance by id");
}| LOG_ERROR_AND_RETURNS(ErrorType::UNSUPPORTED_INDEX_OPERATION, | ||
| "Index does not support get distance by id")}; |
There was a problem hiding this comment.
| GetRawVectorByIds(const int64_t* ids, int64_t count, Allocator* specified_allocator = nullptr) | ||
| const {LOG_ERROR_AND_RETURNS(ErrorType::UNSUPPORTED_INDEX_OPERATION, | ||
| "Index does not support GetRawVectorByIds")}; |
There was a problem hiding this comment.
The formatting of this function is cramped and inconsistent. Please format it cleanly on multiple lines and append a semicolon to the macro call.
GetRawVectorByIds(const int64_t* ids, int64_t count, Allocator* specified_allocator = nullptr) const {
LOG_ERROR_AND_RETURNS(ErrorType::UNSUPPORTED_INDEX_OPERATION,
"Index does not support GetRawVectorByIds");
}f3d42fb to
8efabed
Compare
There was a problem hiding this comment.
Pull request overview
This PR updates the vsag::Index base class’s default virtual method implementations to avoid throwing exceptions for APIs that already return tl::expected<T, Error>, instead returning tl::unexpected(Error) via LOG_ERROR_AND_RETURNS to standardize unsupported-operation handling (Fixes #2140).
Changes:
- Replaced
throw std::runtime_error(...)withLOG_ERROR_AND_RETURNS(ErrorType::UNSUPPORTED_INDEX_OPERATION, ...)in manytl::expected<..., Error>default implementations. - Standardized several unsupported-operation messages to the “Index does not support …” form.
- Adjusted behavior of at least one non-
tl::expectedmethod default (CheckFeature) in the base class.
| Tune(const std::string& parameters, bool disable_future_tuning = false) { | ||
| throw std::runtime_error("Tune is not supported by this index"); | ||
| LOG_ERROR_AND_RETURNS(ErrorType::UNSUPPORTED_INDEX_OPERATION, | ||
| "Index does not support Tune supported by this index") | ||
| } |
| virtual tl::expected<void, Error> | ||
| Train(const DatasetPtr& data) { | ||
| throw std::runtime_error("Index not support Train"); | ||
| LOG_ERROR_AND_RETURNS(ErrorType::UNSUPPORTED_INDEX_OPERATION, | ||
| "Index does not support Train") | ||
| } |
| Pretrain(const std::vector<int64_t>& base_tag_ids, uint32_t k, const std::string& parameters){ | ||
| LOG_ERROR_AND_RETURNS(ErrorType::UNSUPPORTED_INDEX_OPERATION, | ||
| "Index does not support pretrain")}; |
| int64_t global_optimum_tag_id = std::numeric_limits<int64_t>::max()){ | ||
| LOG_ERROR_AND_RETURNS(ErrorType::UNSUPPORTED_INDEX_OPERATION, | ||
| "Index does not support feedback")}; |
| [[nodiscard]] virtual tl::expected<float, Error> | ||
| CalcDistanceById(const float* vector, | ||
| int64_t id, | ||
| bool calculate_precise_distance = true) const { | ||
| throw std::runtime_error("Index doesn't support get distance by id"); | ||
| }; | ||
| CalcDistanceById(const float* vector, int64_t id, bool calculate_precise_distance = true) | ||
| const {LOG_ERROR_AND_RETURNS(ErrorType::UNSUPPORTED_INDEX_OPERATION, | ||
| "Index does not support get distance by id")}; |
| @@ -656,8 +666,8 @@ class Index { | |||
| */ | |||
| [[nodiscard]] virtual tl::expected<std::vector<IndexDetailInfo>, Error> | |||
| GetIndexDetailInfos() const { | |||
| @@ -672,8 +682,8 @@ class Index { | |||
| */ | |||
| virtual tl::expected<DetailDataPtr, Error> | |||
| GetDetailDataByName(const std::string& name, IndexDetailInfo& info) const { | |||
| @@ -692,8 +702,8 @@ class Index { | |||
| */ | |||
| [[nodiscard]] virtual tl::expected<DatasetPtr, Error> | |||
| [[nodiscard]] virtual bool | ||
| CheckFeature(IndexFeature feature) const { | ||
| throw std::runtime_error("Index doesn't support check feature"); | ||
| return false; | ||
| } |
| [[nodiscard]] virtual std::string | ||
| GetStats() const { | ||
| throw std::runtime_error("Index not support GetStats"); | ||
| throw std::runtime_error("Index does not support GetStats"); | ||
| } |
52f7cc3 to
f943474
Compare
f943474 to
830d2ef
Compare
| virtual tl::expected<bool, Error> | ||
| Tune(const std::string& parameters, bool disable_future_tuning = false) { | ||
| throw std::runtime_error("Tune is not supported by this index"); | ||
| return tl::unexpected( | ||
| Error(ErrorType::UNSUPPORTED_INDEX_OPERATION, "Index does not support Tune")); | ||
| } |
| [[nodiscard]] virtual tl::expected<DatasetPtr, Error> | ||
| KnnSearch(const DatasetPtr& query, | ||
| int64_t k, | ||
| const std::string& parameters, | ||
| const FilterPtr& filter) const { | ||
| throw std::runtime_error("Index doesn't support new filter"); | ||
| return tl::unexpected( | ||
| Error(ErrorType::UNSUPPORTED_INDEX_OPERATION, "Index does not support new filter")); | ||
| } |
| virtual tl::expected<void, Error> | ||
| GetExtraInfoByIds(const int64_t* ids, int64_t count, char* extra_infos) const { | ||
| throw std::runtime_error("Index doesn't support GetExtraInfoByIds"); | ||
| return tl::unexpected(Error(ErrorType::UNSUPPORTED_INDEX_OPERATION, | ||
| "Index does not support GetExtraInfoByIds")); | ||
| }; |
| [[nodiscard]] virtual bool | ||
| CheckFeature(IndexFeature feature) const { | ||
| throw std::runtime_error("Index doesn't support check feature"); | ||
| return false; | ||
| } |
830d2ef to
80b8766
Compare
|
Thank you for the review! I have addressed the formatting issues by removing the trailing semicolons after function bodies and updated the documentation comments to reflect the new tl::unexpected-based error handling. Regarding the LOG_ERROR_AND_RETURNS macro suggestion: The macro depends on Options::Instance().logger(), which causes linking errors in header-only default implementations. After consulting with the maintainers, we decided to use return tl::unexpected(Error(...)) directly to avoid these linking issues while still providing the expected-based error handling. For CheckFeature: The task description indicates that methods returning bool should keep throwing exceptions, but CheckFeature is specifically a feature capability check method where returning false (indicating the feature is not supported) is more semantically correct than throwing an exception. This aligns with the common pattern in feature detection APIs. |
| REQUIRE_THROWS(index->Add(dataset->base_)); | ||
| REQUIRE_THROWS(index->Remove(0)); | ||
| REQUIRE_THROWS(index->CheckFeature(IndexFeature::SUPPORT_ESTIMATE_MEMORY)); | ||
| REQUIRE_FALSE(index->CheckFeature(IndexFeature::SUPPORT_ESTIMATE_MEMORY)); | ||
| REQUIRE_THROWS(index->EstimateMemory(1000)); |
| * @return tl::unexpected(ErrorType::UNSUPPORTED_INDEX_OPERATION) If the index implementation does not support this operation | ||
| * (default behavior for base class). |
| * @return tl::unexpected(ErrorType::UNSUPPORTED_INDEX_OPERATION) If the index implementation does not support this operation | ||
| * (default behavior for base class). |
| * @return tl::unexpected(ErrorType::UNSUPPORTED_INDEX_OPERATION) If the index implementation does not support this operation | ||
| * (default behavior for base class). |
| * @return tl::unexpected(ErrorType::UNSUPPORTED_INDEX_OPERATION) If the index implementation does not support this operation | ||
| * (default behavior for base class). |
| * | ||
| * @return IndexPtr A pointer to the exported model index. | ||
| * @throws std::runtime_error If the index does not support exporting the model. | ||
| * @return tl::unexpected(ErrorType::UNSUPPORTED_INDEX_OPERATION) If the index does not support exporting the model. |
| * | ||
| * @return DatasetPtr A pointer to the exported IDs dataset. | ||
| * @throws std::runtime_error If the index does not support exporting the IDs. | ||
| * @return tl::unexpected(ErrorType::UNSUPPORTED_INDEX_OPERATION) If the index does not support exporting the IDs. |
| * After setting this state, no further modifications are supported, such as no additions or deletions | ||
| * | ||
| * @throws std::runtime_error If the index does not support to set immutable | ||
| * @return tl::unexpected(ErrorType::UNSUPPORTED_INDEX_OPERATION) If the index does not support to set immutable |
| [[nodiscard]] virtual bool | ||
| CheckFeature(IndexFeature feature) const { | ||
| throw std::runtime_error("Index doesn't support check feature"); | ||
| return false; |
| virtual tl::expected<bool, Error> | ||
| Tune(const std::string& parameters, bool disable_future_tuning = false) { | ||
| throw std::runtime_error("Tune is not supported by this index"); | ||
| return tl::unexpected( | ||
| Error(ErrorType::UNSUPPORTED_INDEX_OPERATION, "Index does not support Tune")); | ||
| } |
af903b7 to
94d4646
Compare
Replace throw std::runtime_error with tl::unexpected(Error) in Index virtual method default implementations that return tl::expected<T, Error>. This ensures error handling is consistent with the API contract. - Use ErrorType::UNSUPPORTED_INDEX_OPERATION for all unsupported operations - Standardize error messages to "Index does not support <operation>" - Change CheckFeature default to return false instead of throwing - Update tests to use REQUIRE_FALSE(has_value()) for expected-returning methods - Remove is_old_index parameter from TestBatchCalcDistanceById Methods returning non-expected types (GetStats, GetMemoryUsageDetail, etc.) retain throw behavior as they cannot use tl::unexpected. Closes antgroup#2140 Signed-off-by: LHT129 <tianlan.lht@antgroup.com>
94d4646 to
7c55664
Compare
| const char* const HGRAPH_EXTRA_INFO_SIZE = "extra_info_size"; | ||
| const char* const HGRAPH_SUPPORT_DUPLICATE = "support_duplicate"; | ||
| const char* const HGRAPH_DUPLICATE_DISTANCE_THRESHOLD = "duplicate_distance_threshold"; | ||
| const char* const HGRAPH_SUPPORT_TOMBSTONE = "support_tomb_stone"; |
| inline bool | ||
| RecoverRemove(LabelType label) { | ||
| // 1. check is removed | ||
| if (not use_reverse_map_) { | ||
| return false; | ||
| } | ||
| InnerIdType inner_id = INVALID_ID; | ||
| if (not label_remap_.Find(label, inner_id) or inner_id != INVALID_ID) { | ||
| return false; | ||
| } | ||
|
|
||
| // 2. find inner_id | ||
| inner_id = GetIdByLabel(label, true); | ||
|
|
||
| // 3. recover | ||
| deleted_ids_.erase(inner_id); | ||
| label_remap_.InsertOrAssign(label, inner_id); | ||
| return true; | ||
| } |
| inline bool | ||
| IsTombstoneLabel(LabelType label) { | ||
| if (not use_reverse_map_) { | ||
| return false; | ||
| } | ||
| InnerIdType inner_id = INVALID_ID; | ||
| if (not label_remap_.Find(label, inner_id)) { | ||
| return false; | ||
| } | ||
| return inner_id == INVALID_ID; | ||
| } |
| void | ||
| HGraph::recover_remove(int64_t id) { | ||
| // note: | ||
| // 1. this function doesn't recover entry_point and route_graphs caused by Remove() | ||
| // 2. use this function only when is_tombstone is checked | ||
|
|
||
| std::shared_lock label_lock(this->label_lookup_mutex_); | ||
| auto inner_id = this->label_table_->GetIdByLabel(id, true); | ||
| this->bottom_graph_->RecoverDeleteNeighborsById(inner_id); | ||
| this->label_table_->RecoverRemove(id); | ||
| delete_count_--; | ||
| } |
| // recover failed: roll back | ||
| Remove({label}); | ||
| } catch (std::runtime_error& e) { | ||
| // recover failed: roll back | ||
| Remove({label}); | ||
| } |
| * [case 2] fail to recovery -> add process | ||
| * exist + delete + not recovery: is_label_valid = false, is_tombstone = ture, is_recovered = false | ||
| * | ||
| * [case 3] tombstone recovery -> continue | ||
| * exist + delete + recovery: is_label_valid = false, is_tombstone = ture, is_recovered = true |
| * @return tl::expected<DatasetPtr, Error> | ||
| * - On success: A DatasetPtr containing the raw vector data | ||
| * (format depends on implementation, but typically includes vector arrays). | ||
| * - On failure: An error object (e.g., invalid ID, out of memory). | ||
| * @throws std::runtime_error If the index implementation does not support this operation | ||
| * @return tl::unexpected(ErrorType::UNSUPPORTED_INDEX_OPERATION) If the index implementation does not support this operation | ||
| * (default behavior for base class). | ||
| * |
| // try recover tombstone | ||
| if (this->data_type_ != DataTypes::DATA_TYPE_SPARSE) { | ||
| auto one_base = get_single_dataset(data, j); | ||
| bool is_process_finished = try_recover_tombstone(one_base, failed_ids); | ||
| if (is_process_finished) { |
Summary
Fixes #2140