diff --git a/docs/references/http_api_reference.md b/docs/references/http_api_reference.md index 9a7050ff9d..26f5b987cb 100644 --- a/docs/references/http_api_reference.md +++ b/docs/references/http_api_reference.md @@ -431,7 +431,12 @@ Drops an index. ``` curl --request DELETE \ --url localhost:23820/databases/{database_name}/tables/{table_name}/indexes/{index_name} \ - --header 'accept: application/json' + --header 'accept: application/json' \ + --header 'content-type: application/json' \ + --data ' \ +{ + "drop_option": "ignore_if_not_exists" +} ' ``` #### Response diff --git a/python/infinity/errors.py b/python/infinity/errors.py index c903c3bb02..1c7a3395f6 100644 --- a/python/infinity/errors.py +++ b/python/infinity/errors.py @@ -95,6 +95,15 @@ class ErrorCode(IntEnum): MUTIPLE_FUNCTION_MATCHED = 3064, INSERT_WITHOUT_VALUES = 3065, INVALID_CONFLICT_TYPE = 3066, + INVALID_JSON_FORMAT = 3067, + DUPLICATE_COLUMN_NAME = 3068, + INVALID_EXPRESSION = 3069, + SEGMENT_NOT_EXIST = 3070, + AGGREGATE_FUNCTION_WITH_EMPTY_ARGS = 3071, + BLOCK_NOT_EXIST = 3072, + INVALID_TOPK_TYPE = 3073, + INVALID_CREATE_OPTION = 3074, + INVALID_DROP_OPTION = 3075, TXN_ROLLBACK = 4001, TXN_CONFLICT = 4002, diff --git a/python/test_http_api/httpapibase.py b/python/test_http_api/httpapibase.py index fb11f0604b..a7cab7e2e0 100644 --- a/python/test_http_api/httpapibase.py +++ b/python/test_http_api/httpapibase.py @@ -209,17 +209,10 @@ def show_table_columns(self, db_name, table_name, expect): # part index def create_index(self, db_name, table_name, index_name, fields=[], index={}, expect={ "error_code": 0 - }, opt="kIgnore"): + }, opt="ignore_if_exists"): url = f"databases/{db_name}/tables/{table_name}/indexes/{index_name}" - ignore = False - if opt == "kIgnore": - ignore = True - elif opt == "kError": - ignore = False - else: - ignore = opt h = self.set_up_header(['accept', 'content-type'], ) - d = self.set_up_data([], {"fields": fields, "index": index, "create_option": {"ignore_if_exists": ignore}}) + d = self.set_up_data([], {"fields": fields, "index": index, "create_option": opt}) r = self.request(url, "post", h, d) self.tear_down(r, expect) return diff --git a/python/test_http_api/test_index.py b/python/test_http_api/test_index.py index e3354fe30d..c61f64ba09 100644 --- a/python/test_http_api/test_index.py +++ b/python/test_http_api/test_index.py @@ -226,7 +226,7 @@ def test_http_create_drop_vector_index_invalid_options(self, column_name, index_ {"type": "double"}, {"type": "varchar"}, {"type": "boolean"}, {"type": "vector", "dimension": 3, "element_type": "float", }]) def test_http_create_drop_different_fulltext_index_invalid_options(self, column_name, index_type, - params, types): + params, types): db_name = "default" table_name = "test_create_drop_different_fulltext_index_invalid_options" idxname = "my_index" @@ -661,7 +661,7 @@ def test_http_create_index_with_valid_options(self): "type": "integer", } }) - idx_option = ["kError", "kIgnore"] + idx_option = ["error", "ignore_if_exists"] for opt in idx_option: self.create_index(db_name, table_name, idxname, ["c1"], { "type": "HNSW", diff --git a/python/test_http_api/test_insert.py b/python/test_http_api/test_insert.py index ce6f30d040..8eb6b4dd43 100644 --- a/python/test_http_api/test_insert.py +++ b/python/test_http_api/test_insert.py @@ -32,7 +32,7 @@ def test_http_insert_basic(self): self.insert(db_name, table_name, [{"c1": 1, "c2": 1}]) self.insert(db_name, table_name, [{"c1": 2, "c2": 2}]) self.insert(db_name, table_name, [ - {"c1": 3, "c2": 3}, {"c1": 4, "c2": 4}]) + {"c1": 3, "c2": 3}, {"c1": 4, "c2": 4}]) self.drop_table(db_name, table_name) return @@ -66,7 +66,7 @@ def test_http_insert_big_varchar(self): }) for i in range(100): self.insert(db_name, table_name, [ - {"c1": "test_insert_big_varchar" * 1000}]) + {"c1": "test_insert_big_varchar" * 1000}]) self.drop_table(db_name, table_name) return @@ -178,7 +178,7 @@ def test_http_insert_data_into_non_existent_table(self): table_name = "test_insert_data_into_non_existent_table" self.drop_table(db_name, table_name) self.create_table(db_name, table_name, { - "c1": {"type": "integer", }, "c2": {"type": "integer", }}) + "c1": {"type": "integer", }, "c2": {"type": "integer", }}) self.drop_table(db_name, table_name) values = [{"c1": 1, "c2": 1}] @@ -349,7 +349,6 @@ def test_http_batch_insert(self): self.drop_table(db_name, table_name) return - @pytest.mark.skip(reason="error") @pytest.mark.parametrize("batch", [10, 1024]) @pytest.mark.parametrize("types", [(1, False), (1.1, False), ("1#$@!adf", False), ([1, 2, 3], True)]) def test_http_insert_with_invalid_data_type(self, batch, types): @@ -411,7 +410,7 @@ def test_http_various_insert_types(self): values = [{"c1": [1, 2, 3]} for _ in range(5)] self.insert(db_name, table_name, values, { - "status_code": 500, + "status_code": 200, }) self.drop_table(db_name, table_name) diff --git a/src/common/status.cppm b/src/common/status.cppm index 851493dee7..62224d1242 100644 --- a/src/common/status.cppm +++ b/src/common/status.cppm @@ -113,6 +113,8 @@ export enum class ErrorCode : long { kAggregateFunctionWithEmptyArgs = 3071, kBlockNotExist = 3072, kInvalidTopKType = 3073, + kInvalidCreateOption = 3074, + kInvalidDropOption = 3075, // 4. Txn fail kTxnRollback = 4001, diff --git a/src/network/http_server.cpp b/src/network/http_server.cpp index dbb47ca097..2dadefeeca 100644 --- a/src/network/http_server.cpp +++ b/src/network/http_server.cpp @@ -101,17 +101,38 @@ class CreateDatabaseHandler final : public HttpRequestHandler { String body_info = request->readBodyToString(); nlohmann::json body_info_json = nlohmann::json::parse(body_info); String option = body_info_json["create_option"]; - CreateDatabaseOptions create_option; - if (option == "ignore_if_exists") { - create_option.conflict_type_ = ConflictType::kIgnore; + HTTPStatus http_status; + nlohmann::json json_response; + + CreateDatabaseOptions options; + if(body_info_json.contains("create_option")) { + auto create_option = body_info_json["create_option"]; + if(create_option.is_string()) { + String option = create_option; + if(option == "ignore_if_exists") { + options.conflict_type_ = ConflictType::kIgnore; + } else if(option == "error") { + options.conflict_type_ = ConflictType::kError; + } else if(option == "replace_if_exists") { + options.conflict_type_ = ConflictType::kReplace; + } else { + json_response["error_code"] = 3074; + json_response["error_message"] = fmt::format("Invalid create option: {}", option); + http_status = HTTPStatus::CODE_500; + return ResponseFactory::createResponse(http_status, json_response.dump()); + } + } else { + json_response["error_code"] = 3067; + json_response["error_message"] = "'CREATE OPTION' field value should be string type"; + http_status = HTTPStatus::CODE_500; + return ResponseFactory::createResponse(http_status, json_response.dump()); + } } // create database - auto result = infinity->CreateDatabase(database_name, create_option); + auto result = infinity->CreateDatabase(database_name, options); - HTTPStatus http_status; - nlohmann::json json_response; if (result.IsOk()) { json_response["error_code"] = 0; http_status = HTTPStatus::CODE_200; @@ -134,19 +155,37 @@ class DropDatabaseHandler final : public HttpRequestHandler { String database_name = request->getPathVariable("database_name"); // get drop option + HTTPStatus http_status; + nlohmann::json json_response; + String body_info = request->readBodyToString(); nlohmann::json body_info_json = nlohmann::json::parse(body_info); String option = body_info_json["drop_option"]; - DropDatabaseOptions drop_option; - - if (option == "ignore_if_not_exists") { - drop_option.conflict_type_ = ConflictType::kIgnore; + DropDatabaseOptions options; + if(body_info_json.contains("drop_option")) { + auto drop_option = body_info_json["drop_option"]; + if(drop_option.is_string()) { + String option = drop_option; + if(option == "ignore_if_not_exists") { + options.conflict_type_ = ConflictType::kIgnore; + } else if(option == "error") { + options.conflict_type_ = ConflictType::kError; + } else { + json_response["error_code"] = 3075; + json_response["error_message"] = fmt::format("Invalid drop option: {}", option); + http_status = HTTPStatus::CODE_500; + return ResponseFactory::createResponse(http_status, json_response.dump()); + } + } else { + json_response["error_code"] = 3067; + json_response["error_message"] = "'DROP OPTION' field value should be string type"; + http_status = HTTPStatus::CODE_500; + return ResponseFactory::createResponse(http_status, json_response.dump()); + } } - auto result = infinity->DropDatabase(database_name, drop_option); + auto result = infinity->DropDatabase(database_name, options); - nlohmann::json json_response; - HTTPStatus http_status; if (result.IsOk()) { json_response["error_code"] = 0; http_status = HTTPStatus::CODE_200; @@ -278,15 +317,31 @@ class CreateTableHandler final : public HttpRequestHandler { } } Vector table_constraint; - String option = body_info_json["create_option"]; - CreateTableOptions create_table_opts; - if (option == "ignore_if_exists") { - create_table_opts.conflict_type_ = ConflictType::kIgnore; - } else if (option == "replace_if_exists") { - create_table_opts.conflict_type_ = ConflictType::kReplace; + + CreateTableOptions options; + if(body_info_json.contains("create_option")) { + auto create_option = body_info_json["create_option"]; + if(create_option.is_string()) { + String option = create_option; + if(option == "ignore_if_exists") { + options.conflict_type_ = ConflictType::kIgnore; + } else if(option == "error") { + options.conflict_type_ = ConflictType::kError; + } else if(option == "replace_if_exists") { + options.conflict_type_ = ConflictType::kReplace; + } else { + json_response["error_code"] = 3074; + json_response["error_message"] = fmt::format("Invalid create option: {}", option); + http_status = HTTPStatus::CODE_500; + } + } else { + json_response["error_code"] = 3067; + json_response["error_message"] = "'CREATE OPTION' field value should be string type"; + http_status = HTTPStatus::CODE_500; + } } - auto result = infinity->CreateTable(database_name, table_name, column_definitions, table_constraint, create_table_opts); + auto result = infinity->CreateTable(database_name, table_name, column_definitions, table_constraint, options); if (result.IsOk()) { json_response["error_code"] = 0; @@ -311,17 +366,34 @@ class DropTableHandler final : public HttpRequestHandler { String body_info = request->readBodyToString(); nlohmann::json body_info_json = nlohmann::json::parse(body_info); - String option = body_info_json["drop_option"]; - DropTableOptions drop_table_opts; - if (option == "ignore_if_not_exists") { - drop_table_opts.conflict_type_ = ConflictType::kIgnore; - } - - auto result = infinity->DropTable(database_name, table_name, drop_table_opts); HTTPStatus http_status; http_status = HTTPStatus::CODE_200; nlohmann::json json_response; + + DropTableOptions options; + if(body_info_json.contains("drop_option")) { + auto drop_option = body_info_json["drop_option"]; + if(drop_option.is_string()) { + String option = drop_option; + if(option == "ignore_if_not_exists") { + options.conflict_type_ = ConflictType::kIgnore; + } else if(option == "error") { + options.conflict_type_ = ConflictType::kError; + } else { + json_response["error_code"] = 3075; + json_response["error_message"] = fmt::format("Invalid drop option: {}", option); + http_status = HTTPStatus::CODE_500; + } + } else { + json_response["error_code"] = 3067; + json_response["error_message"] = "'DROP OPTION' field value should be string type"; + http_status = HTTPStatus::CODE_500; + } + } + + auto result = infinity->DropTable(database_name, table_name, options); + if (result.IsOk()) { json_response["error_code"] = 0; http_status = HTTPStatus::CODE_200; @@ -872,11 +944,11 @@ class InsertHandler final : public HttpRequestHandler { switch (value_type) { case nlohmann::json::value_t::number_integer: { - const_expr->long_array_.emplace_back(value.template get()); + const_expr->long_array_.emplace_back(value_ref.template get()); break; } case nlohmann::json::value_t::number_unsigned: { - const_expr->long_array_.emplace_back(value.template get()); + const_expr->long_array_.emplace_back(value_ref.template get()); break; } default: { @@ -1393,10 +1465,35 @@ class DropIndexHandler final : public HttpRequestHandler { auto table_name = request->getPathVariable("table_name"); auto index_name = request->getPathVariable("index_name"); - auto result = infinity->DropIndex(database_name, table_name, index_name, DropIndexOptions()); + String body_info = request->readBodyToString(); + + nlohmann::json body_info_json = nlohmann::json::parse(body_info); nlohmann::json json_response; HTTPStatus http_status; + DropIndexOptions options; + if(body_info_json.contains("drop_option")) { + auto drop_option = body_info_json["drop_option"]; + if(drop_option.is_string()) { + String option = drop_option; + if(option == "ignore_if_not_exists") { + options.conflict_type_ = ConflictType::kIgnore; + } else if(option == "error") { + options.conflict_type_ = ConflictType::kError; + } else { + json_response["error_code"] = 3074; + json_response["error_message"] = fmt::format("Invalid drop option: {}", option); + http_status = HTTPStatus::CODE_500; + } + } else { + json_response["error_code"] = 3067; + json_response["error_message"] = "'CREATE OPTION' field value should be string type"; + http_status = HTTPStatus::CODE_500; + } + } + + auto result = infinity->DropIndex(database_name, table_name, index_name, options); + if (result.IsOk()) { json_response["error_code"] = 0; http_status = HTTPStatus::CODE_200; @@ -1422,19 +1519,35 @@ class CreateIndexHandler final : public HttpRequestHandler { String body_info_str = request->readBodyToString(); nlohmann::json body_info_json = nlohmann::json::parse(body_info_str); + nlohmann::json json_response; + HTTPStatus http_status; + CreateIndexOptions options; - auto create_option = body_info_json["create_option"]; - auto ignore_if_exists = create_option["ignore_if_exists"]; - if (ignore_if_exists.is_boolean() && ignore_if_exists) { - options.conflict_type_ = ConflictType::kIgnore; + if(body_info_json.contains("create_option")) { + auto create_option = body_info_json["create_option"]; + if(create_option.is_string()) { + String option = create_option; + if(option == "ignore_if_exists") { + options.conflict_type_ = ConflictType::kIgnore; + } else if(option == "error") { + options.conflict_type_ = ConflictType::kError; + } else if(option == "replace_if_exists") { + options.conflict_type_ = ConflictType::kReplace; + } else { + json_response["error_code"] = 3075; + json_response["error_message"] = fmt::format("Invalid create option: {}", option); + http_status = HTTPStatus::CODE_500; + } + } else { + json_response["error_code"] = 3067; + json_response["error_message"] = "'CREATE OPTION' field value should be string type"; + http_status = HTTPStatus::CODE_500; + } } auto fields = body_info_json["fields"]; auto index = body_info_json["index"]; - nlohmann::json json_response; - HTTPStatus http_status; - auto index_info_list = new Vector(); { auto index_info = new IndexInfo(); diff --git a/src/storage/txn/txn.cpp b/src/storage/txn/txn.cpp index 0389d54fef..43d0756ce4 100644 --- a/src/storage/txn/txn.cpp +++ b/src/storage/txn/txn.cpp @@ -427,8 +427,10 @@ TxnTimeStamp Txn::Commit() { if (txn_context_.GetTxnState() == TxnState::kToRollback) { // abort because of conflict - RecoverableError(Status::TxnRollback(txn_id_)); + LOG_ERROR(fmt::format("Txn: {} is rollbacked. rollback ts: {}", txn_id_, this->CommitTS())); + return this->CommitTS(); } + if (txn_context_.GetTxnState() != TxnState::kCommitted) { UnrecoverableError("Transaction isn't in COMMITTED status."); }