#include "gtest/gtest.h"
|
|
#include "db/NewDB.h" // NewDB 的头文件
|
|
#include "leveldb/env.h"
|
|
#include "leveldb/db.h"
|
|
#include "db/write_batch_internal.h"
|
|
#include <iostream>
|
|
#include <random>
|
|
#include <ctime>
|
|
#include <thread>
|
|
#include <mutex>
|
|
|
|
using namespace leveldb;
|
|
|
|
Status OpenNewDB(std::string dbName, NewDB** db) {
|
|
Options options = Options();
|
|
options.create_if_missing = true;
|
|
return NewDB::Open(options, dbName, db);
|
|
}
|
|
|
|
// 全局的随机数引擎
|
|
std::default_random_engine rng;
|
|
|
|
// 设置随机种子
|
|
void SetGlobalSeed(unsigned seed) {
|
|
rng.seed(seed);
|
|
}
|
|
|
|
// 生成随机字符串
|
|
std::string GenerateRandomString(size_t length) {
|
|
static const char alphanum[] =
|
|
"0123456789"
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
"abcdefghijklmnopqrstuvwxyz";
|
|
|
|
std::uniform_int_distribution<int> dist(0, sizeof(alphanum) - 2);
|
|
|
|
std::string str(length, 0);
|
|
for (size_t i = 0; i < length; ++i) {
|
|
str[i] = alphanum[dist(rng)];
|
|
}
|
|
return str;
|
|
}
|
|
|
|
int getRandomInRange(int min, int max) {
|
|
return min + std::rand() % ((max + 1) - min);
|
|
}
|
|
|
|
std::mutex results_mutex;
|
|
std::vector<bool> results;
|
|
|
|
void InsertResult(bool TRUEorFALSE){
|
|
std::unique_lock<std::mutex> lock(results_mutex, std::defer_lock);
|
|
lock.lock();
|
|
results.emplace_back(TRUEorFALSE);
|
|
lock.unlock();
|
|
}
|
|
|
|
std::string testdbname = "dbtest38";
|
|
|
|
|
|
void thread_task_deleteindex(NewDB* db, int thread_id) {
|
|
db->DeleteIndex("address");
|
|
}
|
|
|
|
void thread_task_delete(NewDB* db, int thread_id) {
|
|
int i = getRandomInRange(0,9999);
|
|
|
|
std::string key = "k_" + std::to_string(i);
|
|
std::string fieldvalue;
|
|
|
|
FieldArray retrieved_fields;
|
|
if(db->Get_fields(ReadOptions(), Slice(key), &retrieved_fields).ok()){
|
|
fieldvalue = retrieved_fields[1].second;
|
|
}
|
|
|
|
// while (db->indexed_fields_write.find("address") == db->indexed_fields_write.end()) {
|
|
// std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
// }
|
|
db->Delete(WriteOptions(), key);
|
|
|
|
// check the consistency
|
|
if(db->Get_fields(ReadOptions(), Slice(key), &retrieved_fields).ok()){
|
|
InsertResult(false);
|
|
return;
|
|
}
|
|
|
|
Field field_to_query{"address", fieldvalue}; // field_value of field address
|
|
if (db->indexed_fields_read.find("address") == db->indexed_fields_read.end()) { // index not ready
|
|
// std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
std::vector<std::string> matching_keys = db->QueryByIndex(field_to_query);
|
|
if(matching_keys.empty()){
|
|
InsertResult(true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// index ready
|
|
std::vector<std::string> matching_keys = db->QueryByIndex(field_to_query);
|
|
if(!matching_keys.empty()){
|
|
// std::cout << thread_id << std::endl;
|
|
InsertResult(false);
|
|
return;
|
|
}
|
|
|
|
InsertResult(true);
|
|
}
|
|
|
|
void thread_task_createindex(NewDB* db, int thread_id) {
|
|
db->CreateIndexOnField("address");
|
|
}
|
|
|
|
void thread_task_put(NewDB* db, int thread_id) {
|
|
std::string key = "k_" + GenerateRandomString(20);
|
|
FieldArray fields = {
|
|
{"name", GenerateRandomString(5)},
|
|
{"address", GenerateRandomString(15)},
|
|
{"phone", GenerateRandomString(11)}
|
|
};
|
|
|
|
// while (db->indexed_fields_write.find("address") == db->indexed_fields_write.end()) {
|
|
// std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
// }
|
|
db->Put_fields(WriteOptions(), key, fields);
|
|
|
|
// check the consistency
|
|
FieldArray retrieved_fields;
|
|
if(!db->Get_fields(ReadOptions(), Slice(key), &retrieved_fields).ok()){
|
|
InsertResult(false);
|
|
return;
|
|
}
|
|
|
|
Field field_to_query{"address", fields[1].second}; // field_value of field address
|
|
|
|
if (db->indexed_fields_read.find("address") == db->indexed_fields_read.end()) { // index not ready
|
|
// std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
std::vector<std::string> matching_keys = db->QueryByIndex(field_to_query);
|
|
if(matching_keys.empty()){
|
|
InsertResult(true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// index ready
|
|
std::vector<std::string> matching_keys = db->QueryByIndex(field_to_query);
|
|
if(matching_keys.empty()){
|
|
// std::cout << thread_id << std::endl;
|
|
InsertResult(false);
|
|
return;
|
|
}
|
|
|
|
int tag = 0;
|
|
for(auto& matching_key : matching_keys){
|
|
if(key == matching_key){
|
|
tag = 1;
|
|
break;
|
|
}
|
|
}
|
|
if(tag != 1){
|
|
InsertResult(false);
|
|
return;
|
|
}
|
|
|
|
InsertResult(true);
|
|
}
|
|
|
|
void thread_task_put_key(NewDB* db, int thread_id, std::string key, std::string name) {
|
|
FieldArray fields = {
|
|
{"name", name},
|
|
{"address", GenerateRandomString(15)},
|
|
{"phone", GenerateRandomString(11)}
|
|
};
|
|
|
|
db->Put_fields(WriteOptions(), key, fields);
|
|
}
|
|
|
|
TEST(TestNewDB, ConcurrencyALLTest) {
|
|
// 创建 NewDB 实例
|
|
NewDB* db;
|
|
ASSERT_TRUE(OpenNewDB(testdbname, &db).ok());
|
|
|
|
// 批量插入数据
|
|
const int num_records = 10000;
|
|
|
|
for (int i = 0; i < num_records; ++i) {
|
|
// std::string key = "k_" + GenerateRandomString(20);
|
|
std::string key = "k_" + std::to_string(i);
|
|
FieldArray fields = {
|
|
{"name", GenerateRandomString(5)},
|
|
{"address", GenerateRandomString(15)},
|
|
{"phone", GenerateRandomString(11)}
|
|
};
|
|
ASSERT_TRUE(db->Put_fields(WriteOptions(), key, fields).ok());
|
|
}
|
|
|
|
// 创建线程向量用于保存所有线程对象
|
|
std::vector<std::thread> threads;
|
|
std::vector<std::thread> threads2;
|
|
|
|
threads.emplace_back([=]() { thread_task_createindex(db, 0); });
|
|
|
|
// 启动100个线程,每个线程都运行thread_task函数
|
|
int thread_num = 100;
|
|
for (int i = 1; i <= thread_num; ++i) {
|
|
// 创建随机设备用于种子
|
|
std::random_device rd;
|
|
|
|
// 使用随机设备创建一个均匀分布的随机数生成器
|
|
std::uniform_int_distribution<int> dist(0, 1);
|
|
|
|
// 根据随机数选择执行哪个代码块
|
|
if (dist(rd) == 0) {
|
|
threads.emplace_back([=]() { thread_task_delete(db, i); });
|
|
} else {
|
|
threads.emplace_back([=]() { thread_task_put(db, i); });
|
|
}
|
|
// threads.emplace_back([=]() { thread_task_put(db, i); });
|
|
}
|
|
|
|
// 等待所有线程完成
|
|
for (auto& th : threads) {
|
|
if (th.joinable()) {
|
|
th.join();
|
|
}
|
|
}
|
|
|
|
// check if all the results are true
|
|
for(const auto& result : results){
|
|
EXPECT_EQ(result, true);
|
|
}
|
|
|
|
threads2.emplace_back([=]() { thread_task_deleteindex(db, 200); });
|
|
|
|
// 启动100个线程,每个线程都运行thread_task函数
|
|
for (int i = 201; i <= thread_num+200; ++i) {
|
|
// 创建随机设备用于种子
|
|
std::random_device rd;
|
|
|
|
// 使用随机设备创建一个均匀分布的随机数生成器
|
|
std::uniform_int_distribution<int> dist(0, 1);
|
|
|
|
// 根据随机数选择执行哪个代码块
|
|
if (dist(rd) == 0) {
|
|
threads2.emplace_back([=]() { thread_task_delete(db, i); });
|
|
} else {
|
|
threads2.emplace_back([=]() { thread_task_put(db, i); });
|
|
}
|
|
// threads2.emplace_back([=]() { thread_task_put(db, i); });
|
|
}
|
|
|
|
// 等待所有线程完成
|
|
for (auto& th : threads2) {
|
|
if (th.joinable()) {
|
|
th.join();
|
|
}
|
|
}
|
|
|
|
// check if all the results are true
|
|
for(const auto& result : results){
|
|
EXPECT_EQ(result, true);
|
|
}
|
|
|
|
delete db;
|
|
}
|
|
|
|
TEST(TestNewDB, ConcurrencyPutSameKeyTest) {
|
|
// 创建 NewDB 实例
|
|
NewDB* db;
|
|
ASSERT_TRUE(OpenNewDB(testdbname, &db).ok());
|
|
|
|
// 批量插入数据
|
|
const int num_records = 1;
|
|
|
|
for (int i = 0; i < num_records; ++i) {
|
|
// std::string key = "k_" + GenerateRandomString(20);
|
|
std::string key = "k_" + std::to_string(i);
|
|
FieldArray fields = {
|
|
{"name", GenerateRandomString(5)},
|
|
{"address", GenerateRandomString(15)},
|
|
{"phone", GenerateRandomString(11)}
|
|
};
|
|
ASSERT_TRUE(db->Put_fields(WriteOptions(), key, fields).ok());
|
|
}
|
|
|
|
db->DeleteIndex("name");
|
|
|
|
db->CreateIndexOnField("name");
|
|
|
|
// 创建线程向量用于保存所有线程对象
|
|
std::vector<std::thread> threads;
|
|
std::string name;
|
|
|
|
for (int i = 1; i <= 1; ++i) {
|
|
std::string ikey = "k_" + std::to_string(i);
|
|
name = "bob";
|
|
threads.emplace_back([=]() { thread_task_put_key(db, i, ikey, name); });
|
|
name = "john";
|
|
threads.emplace_back([=]() { thread_task_put_key(db, i, ikey, name); });
|
|
|
|
// 等待所有线程完成
|
|
for (auto& th : threads) {
|
|
if (th.joinable()) {
|
|
th.join();
|
|
}
|
|
}
|
|
|
|
// check data and index
|
|
FieldArray retrieved_fields;
|
|
Status s = db->Get_fields(ReadOptions(), Slice(ikey), &retrieved_fields);
|
|
ASSERT_TRUE(s.ok());
|
|
|
|
std::string fieldvalue = retrieved_fields[0].second;
|
|
Field field_to_query{"name", fieldvalue};
|
|
|
|
// fieldvalue should be found by index
|
|
std::vector<std::string> matching_keys = db->QueryByIndex(field_to_query);
|
|
ASSERT_FALSE(matching_keys.empty());
|
|
ASSERT_TRUE(std::find(matching_keys.begin(), matching_keys.end(), ikey) != matching_keys.end());
|
|
}
|
|
|
|
delete db;
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
// 设置全局随机种子
|
|
SetGlobalSeed(static_cast<unsigned>(time(nullptr)));
|
|
testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|