Preset API.

This commit is contained in:
Kishore Nallan 2022-02-17 16:23:59 +05:30
parent 3be68b38c9
commit facdda2a03
6 changed files with 253 additions and 6 deletions

View File

@ -64,6 +64,8 @@ private:
spp::sparse_hash_map<std::string, std::string> collection_symlinks;
spp::sparse_hash_map<std::string, nlohmann::json> preset_configs;
// Auto incrementing ID assigned to each collection
// Using a ID instead of a collection's name makes renaming possible
std::atomic<uint32_t> next_collection_id;
@ -95,6 +97,7 @@ public:
static constexpr const char* NEXT_COLLECTION_ID_KEY = "$CI";
static constexpr const char* SYMLINK_PREFIX = "$SL";
static constexpr const char* PRESET_PREFIX = "$PS";
static constexpr const char* BATCHED_INDEXER_STATE_KEY = "$BI";
static CollectionManager & get_instance() {
@ -159,6 +162,8 @@ public:
static std::string get_symlink_key(const std::string & symlink_name);
static std::string get_preset_key(const std::string & preset_name);
Store* get_store();
ThreadPool* get_thread_pool() const;
@ -177,4 +182,13 @@ public:
Option<bool> upsert_symlink(const std::string & symlink_name, const std::string & collection_name);
Option<bool> delete_symlink(const std::string & symlink_name);
// presets
spp::sparse_hash_map<std::string, nlohmann::json> get_presets() const;
Option<nlohmann::json> get_preset(const std::string & preset_name) const;
Option<bool> upsert_preset(const std::string & preset_name, const nlohmann::json& preset_config);
Option<bool> delete_preset(const std::string & preset_name);
};

View File

@ -45,6 +45,16 @@ bool put_upsert_alias(const std::shared_ptr<http_req>& req, const std::shared_pt
bool del_alias(const std::shared_ptr<http_req>& req, const std::shared_ptr<http_res>& res);
// Presets
bool get_presets(const std::shared_ptr<http_req>& req, const std::shared_ptr<http_res>& res);
bool get_preset(const std::shared_ptr<http_req>& req, const std::shared_ptr<http_res>& res);
bool put_upsert_preset(const std::shared_ptr<http_req>& req, const std::shared_ptr<http_res>& res);
bool del_preset(const std::shared_ptr<http_req>& req, const std::shared_ptr<http_res>& res);
// Overrides
bool get_overrides(const std::shared_ptr<http_req>& req, const std::shared_ptr<http_res>& res);

View File

@ -207,6 +207,8 @@ Option<bool> CollectionManager::load(const size_t collection_batch_size, const s
return num_processed == num_collections;
});
// load aliases
std::string symlink_prefix_key = std::string(SYMLINK_PREFIX) + "_";
rocksdb::Iterator* iter = store->scan(symlink_prefix_key);
while(iter->Valid() && iter->key().starts_with(symlink_prefix_key)) {
@ -218,6 +220,19 @@ Option<bool> CollectionManager::load(const size_t collection_batch_size, const s
delete iter;
// load presets
std::string preset_prefix_key = std::string(PRESET_PREFIX) + "_";
iter = store->scan(preset_prefix_key);
while(iter->Valid() && iter->key().starts_with(preset_prefix_key)) {
std::vector<std::string> parts;
StringUtils::split(iter->key().ToString(), parts, preset_prefix_key);
preset_configs[parts[0]] = iter->value().ToString();
iter->Next();
}
delete iter;
LOG(INFO) << "Loaded " << num_collections << " collection(s).";
loading_pool.shutdown();
@ -245,6 +260,8 @@ void CollectionManager::dispose() {
}
collections.clear();
collection_symlinks.clear();
preset_configs.clear();
store->close();
}
@ -596,6 +613,8 @@ Option<bool> CollectionManager::do_search(std::map<std::string, std::string>& re
const char *EXHAUSTIVE_SEARCH = "exhaustive_search";
const char *SPLIT_JOIN_TOKENS = "split_join_tokens";
const char *PRESET = "preset";
if(req_params.count(NUM_TYPOS) == 0) {
req_params[NUM_TYPOS] = "2";
}
@ -1234,3 +1253,47 @@ Option<bool> CollectionManager::load_collection(const nlohmann::json &collection
return Option<bool>(true);
}
spp::sparse_hash_map<std::string, nlohmann::json> CollectionManager::get_presets() const {
std::shared_lock lock(mutex);
return preset_configs;
}
Option<nlohmann::json> CollectionManager::get_preset(const string& preset_name) const {
std::shared_lock lock(mutex);
const auto& it = preset_configs.find(preset_name);
if(it != preset_configs.end()) {
return Option<nlohmann::json>(it->second);
}
return Option<nlohmann::json>(404, "Not found.");
}
Option<bool> CollectionManager::upsert_preset(const string& preset_name, const nlohmann::json& preset_config) {
std::unique_lock lock(mutex);
bool inserted = store->insert(get_preset_key(preset_name), preset_config.dump());
if(!inserted) {
return Option<bool>(500, "Unable to insert into store.");
}
preset_configs[preset_name] = preset_config;
return Option<bool>(true);
}
std::string CollectionManager::get_preset_key(const string& preset_name) {
return std::string(PRESET_PREFIX) + "_" + preset_name;
}
Option<bool> CollectionManager::delete_preset(const string& preset_name) {
std::unique_lock lock(mutex);
bool removed = store->remove(get_preset_key(preset_name));
if(!removed) {
return Option<bool>(500, "Unable to delete from store.");
}
preset_configs.erase(preset_name);
return Option<bool>(true);
}

View File

@ -323,13 +323,23 @@ bool post_multi_search(const std::shared_ptr<http_req>& req, const std::shared_p
}
nlohmann::json req_json;
const auto preset_it = req->params.find("preset");
try {
req_json = nlohmann::json::parse(req->body);
} catch(const std::exception& e) {
LOG(ERROR) << "JSON error: " << e.what();
res->set_400("Bad JSON.");
return false;
if(preset_it != req->params.end()) {
const auto preset_op = CollectionManager::get_instance().get_preset(preset_it->second);
if(preset_op.ok()) {
req_json = preset_op.get();
}
}
if(req_json.empty()) {
try {
req_json = nlohmann::json::parse(req->body);
} catch(const std::exception& e) {
LOG(ERROR) << "JSON error: " << e.what();
res->set_400("Bad JSON.");
return false;
}
}
if(req_json.count("searches") == 0) {
@ -1486,3 +1496,95 @@ bool is_doc_del_route(uint64_t route_hash) {
bool found = server->get_route(route_hash, &rpath);
return found && (rpath->handler == del_remove_document || rpath->handler == del_remove_documents);
}
bool get_presets(const std::shared_ptr<http_req>& req, const std::shared_ptr<http_res>& res) {
CollectionManager & collectionManager = CollectionManager::get_instance();
const spp::sparse_hash_map<std::string, nlohmann::json> & presets = collectionManager.get_presets();
nlohmann::json res_json = nlohmann::json::object();
res_json["presets"] = nlohmann::json::array();
for(const auto& preset_kv: presets) {
nlohmann::json preset;
preset["name"] = preset_kv.first;
preset["value"] = preset_kv.second;
res_json["presets"].push_back(preset);
}
res->set_200(res_json.dump());
return true;
}
bool get_preset(const std::shared_ptr<http_req>& req, const std::shared_ptr<http_res>& res) {
const std::string & preset_name = req->params["preset_name"];
CollectionManager & collectionManager = CollectionManager::get_instance();
Option<nlohmann::json> preset_op = collectionManager.get_preset(preset_name);
if(!preset_op.ok()) {
res->set_404();
return false;
}
nlohmann::json res_json;
res_json["name"] = preset_name;
res_json["value"] = preset_op.get();
res->set_200(res_json.dump());
return true;
}
bool put_upsert_preset(const std::shared_ptr<http_req>& req, const std::shared_ptr<http_res>& res) {
nlohmann::json req_json;
try {
req_json = nlohmann::json::parse(req->body);
} catch(const std::exception& e) {
LOG(ERROR) << "JSON error: " << e.what();
res->set_400("Bad JSON.");
return false;
}
CollectionManager & collectionManager = CollectionManager::get_instance();
const std::string & preset_name = req->params["name"];
const char* PRESET_VALUE = "value";
if(req_json.count(PRESET_VALUE) == 0) {
res->set_400(std::string("Parameter `") + PRESET_VALUE + "` is required.");
return false;
}
Option<bool> success_op = collectionManager.upsert_preset(preset_name, req_json[PRESET_VALUE]);
if(!success_op.ok()) {
res->set_500(success_op.error());
return false;
}
req_json["name"] = preset_name;
res->set_200(req_json.dump());
return true;
}
bool del_preset(const std::shared_ptr<http_req>& req, const std::shared_ptr<http_res>& res) {
const std::string & preset_name = req->params["name"];
CollectionManager & collectionManager = CollectionManager::get_instance();
Option<nlohmann::json> preset_op = collectionManager.get_preset(preset_name);
if(!preset_op.ok()) {
res->set_404();
return false;
}
Option<bool> delete_op = collectionManager.delete_preset(preset_name);
if(!delete_op.ok()) {
res->set_500(delete_op.error());
return false;
}
nlohmann::json res_json;
res_json["name"] = preset_name;
res_json["value"] = preset_op.get();
res->set_200(res_json.dump());
return true;
}

View File

@ -58,6 +58,11 @@ void master_server_routes() {
server->post("/keys", post_create_key);
server->del("/keys/:id", del_key);
server->get("/presets", get_presets);
server->get("/presets/:name", get_preset);
server->put("/presets/:name", put_upsert_preset);
server->del("/presets/:name", del_preset);
// meta
server->get("/metrics.json", get_metrics_json);
server->get("/stats.json", get_stats_json);

View File

@ -673,3 +673,56 @@ TEST_F(CollectionManagerTest, ParseSortByClause) {
sort_by_parsed = CollectionManager::parse_sort_by_str(",,", sort_fields);
ASSERT_FALSE(sort_by_parsed);
}
TEST_F(CollectionManagerTest, Presets) {
// try getting on a blank slate
auto presets = collectionManager.get_presets();
ASSERT_TRUE(presets.empty());
// insert some presets
nlohmann::json preset_obj;
preset_obj["query_by"] = "foo";
collectionManager.upsert_preset("preset1", preset_obj);
preset_obj["query_by"] = "bar";
collectionManager.upsert_preset("preset2", preset_obj);
ASSERT_EQ(2, collectionManager.get_presets().size());
// try fetching individual presets
auto preset_op = collectionManager.get_preset("preset1");
ASSERT_TRUE(preset_op.ok());
ASSERT_EQ(1, preset_op.get().size());
ASSERT_EQ("foo", preset_op.get()["query_by"]);
preset_op = collectionManager.get_preset("preset2");
ASSERT_TRUE(preset_op.ok());
ASSERT_EQ(1, preset_op.get().size());
ASSERT_EQ("bar", preset_op.get()["query_by"]);
// delete a preset
auto del_op = collectionManager.delete_preset("preset2");
ASSERT_TRUE(del_op.ok());
std::string val;
auto status = store->get(CollectionManager::get_preset_key("preset2"), val);
ASSERT_EQ(StoreStatus::NOT_FOUND, status);
ASSERT_EQ(1, collectionManager.get_presets().size());
preset_op = collectionManager.get_preset("preset2");
ASSERT_FALSE(preset_op.ok());
ASSERT_EQ(404, preset_op.code());
// should be able to restore state on init
collectionManager.dispose();
delete store;
store = new Store("/tmp/typesense_test/coll_manager_test_db");
collectionManager.init(store, 1.0, "auth_key", quit);
collectionManager.load(8, 1000);
ASSERT_EQ(1, collectionManager.get_presets().size());
preset_op = collectionManager.get_preset("preset1");
ASSERT_TRUE(preset_op.ok());
}