From 96b892b5afa1d3cb1ca32bd314268cb6dd7e18e3 Mon Sep 17 00:00:00 2001 From: augurier <14434658+augurier@user.noreply.gitee.com> Date: Tue, 19 Nov 2024 15:18:04 +0800 Subject: [PATCH 01/14] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=EF=BC=88?= =?UTF-8?q?=E5=8F=8D=EF=BC=89=E5=BA=8F=E5=88=97=E5=8C=96=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=EF=BC=8C=E5=8E=9F=E5=A7=8B=E6=B5=8B=E8=AF=95=EF=BC=8C=E9=80=9A?= =?UTF-8?q?=E8=BF=87=E7=BC=96=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index fda9e01..981cda2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -190,6 +190,8 @@ target_sources(leveldb "util/options.cc" "util/random.h" "util/status.cc" + "util/serialize_value.h" + "util/serialize_value.cc" # Only CMake 3.3+ supports PUBLIC sources in targets exported by "install". $<$:PUBLIC> @@ -517,3 +519,8 @@ if(LEVELDB_INSTALL) DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" ) endif(LEVELDB_INSTALL) + +add_executable(lab1_test + "${PROJECT_SOURCE_DIR}/test/lab1_test.cc" +) +target_link_libraries(lab1_test PRIVATE leveldb gtest) From ff5c0b84bf7d84219fcafcd7c8930e337aa9a3ca Mon Sep 17 00:00:00 2001 From: augurier <14434658+augurier@user.noreply.gitee.com> Date: Tue, 19 Nov 2024 16:14:08 +0800 Subject: [PATCH 02/14] =?UTF-8?q?=E8=A1=A5=E5=85=85=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ db/db_impl.cc | 22 ++++++++++++++++++++++ db/db_impl.h | 7 +++++++ db/db_test.cc | 12 ++++++++++++ include/leveldb/db.h | 8 ++++++++ test/lab1_test.cc | 46 ++++++++++++++++++++++++++++++++++++++++++++++ util/serialize_value.cc | 10 ++++++++++ util/serialize_value.h | 13 +++++++++++++ 8 files changed, 120 insertions(+) create mode 100644 test/lab1_test.cc create mode 100644 util/serialize_value.cc create mode 100644 util/serialize_value.h diff --git a/README.md b/README.md index ee9e43d..05ee763 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ # 实验报告 仓库地址 https://gitea.shuishan.net.cn/10225501448/leveldb_proj2 + +新建文件时 cmakelist 120行下面记得加进去 \ No newline at end of file diff --git a/db/db_impl.cc b/db/db_impl.cc index f96d245..f423733 100644 --- a/db/db_impl.cc +++ b/db/db_impl.cc @@ -34,6 +34,7 @@ #include "util/coding.h" #include "util/logging.h" #include "util/mutexlock.h" +#include "util/serialize_value.h" namespace leveldb { @@ -1164,6 +1165,18 @@ Status DBImpl::Get(const ReadOptions& options, const Slice& key, return s; } +Status DBImpl::GetFields(const ReadOptions& options, const Slice& key, + FieldArray* fields) { + std::string value; + Status s = DBImpl::Get(options, key, &value); + fields = ParseValue(value); + return s; +} + +std::vector DBImpl::FindKeysByField(Field &field){//todo + return std::vector(); +} + Iterator* DBImpl::NewIterator(const ReadOptions& options) { SequenceNumber latest_snapshot; uint32_t seed; @@ -1198,6 +1211,10 @@ Status DBImpl::Put(const WriteOptions& o, const Slice& key, const Slice& val) { return DB::Put(o, key, val); } +Status DBImpl::PutFields(const WriteOptions& o, const Slice& key, const FieldArray& fields) { + return DB::PutFields(o, key, fields); +} + Status DBImpl::Delete(const WriteOptions& options, const Slice& key) { return DB::Delete(options, key); } @@ -1491,6 +1508,11 @@ Status DB::Put(const WriteOptions& opt, const Slice& key, const Slice& value) { return Write(opt, &batch); } +Status DB::PutFields(const WriteOptions& opt, const Slice& key, const FieldArray& fields) { + std::string value = SerializeValue(fields); + DB::Put(opt, key, value); +} + Status DB::Delete(const WriteOptions& opt, const Slice& key) { WriteBatch batch; batch.Delete(key); diff --git a/db/db_impl.h b/db/db_impl.h index c7b0172..6848077 100644 --- a/db/db_impl.h +++ b/db/db_impl.h @@ -17,6 +17,7 @@ #include "leveldb/env.h" #include "port/port.h" #include "port/thread_annotations.h" +#include "util/serialize_value.h" namespace leveldb { @@ -38,10 +39,16 @@ class DBImpl : public DB { // Implementations of the DB interface Status Put(const WriteOptions&, const Slice& key, const Slice& value) override; + Status PutFields(const WriteOptions&, const Slice& key, + const FieldArray& fields) override; + Status Delete(const WriteOptions&, const Slice& key) override; Status Write(const WriteOptions& options, WriteBatch* updates) override; Status Get(const ReadOptions& options, const Slice& key, std::string* value) override; + Status GetFields(const ReadOptions& options, const Slice& key, + FieldArray* fields) override; + std::vector FindKeysByField(Field &field) override; Iterator* NewIterator(const ReadOptions&) override; const Snapshot* GetSnapshot() override; void ReleaseSnapshot(const Snapshot* snapshot) override; diff --git a/db/db_test.cc b/db/db_test.cc index a4a84cd..2a5668f 100644 --- a/db/db_test.cc +++ b/db/db_test.cc @@ -2117,6 +2117,9 @@ class ModelDB : public DB { Status Put(const WriteOptions& o, const Slice& k, const Slice& v) override { return DB::Put(o, k, v); } + Status PutFields(const WriteOptions& o, const Slice& k, const FieldArray& v) override { + return DB::PutFields(o, k, v); + } Status Delete(const WriteOptions& o, const Slice& key) override { return DB::Delete(o, key); } @@ -2125,6 +2128,15 @@ class ModelDB : public DB { assert(false); // Not implemented return Status::NotFound(key); } + Status GetFields(const ReadOptions& options, const Slice& key, + FieldArray* value) override { + assert(false); // Not implemented + return Status::NotFound(key); + } + std::vector FindKeysByField(Field &field) override { + assert(false); // Not implemented + return std::vector(); + } Iterator* NewIterator(const ReadOptions& options) override { if (options.snapshot == nullptr) { KVMap* saved = new KVMap; diff --git a/include/leveldb/db.h b/include/leveldb/db.h index a13d147..cbe69a7 100644 --- a/include/leveldb/db.h +++ b/include/leveldb/db.h @@ -11,6 +11,7 @@ #include "leveldb/export.h" #include "leveldb/iterator.h" #include "leveldb/options.h" +#include "util/serialize_value.h" namespace leveldb { @@ -66,6 +67,9 @@ class LEVELDB_EXPORT DB { virtual Status Put(const WriteOptions& options, const Slice& key, const Slice& value) = 0; + virtual Status PutFields(const WriteOptions&, const Slice& key, + const FieldArray& fields) = 0; + // Remove the database entry (if any) for "key". Returns OK on // success, and a non-OK status on error. It is not an error if "key" // did not exist in the database. @@ -86,7 +90,11 @@ class LEVELDB_EXPORT DB { // May return some other Status on an error. virtual Status Get(const ReadOptions& options, const Slice& key, std::string* value) = 0; + + virtual Status GetFields(const ReadOptions& options, const Slice& key, + FieldArray* fields) = 0; + virtual std::vector FindKeysByField(Field &field) = 0; // Return a heap-allocated iterator over the contents of the database. // The result of NewIterator() is initially invalid (caller must // call one of the Seek methods on the iterator before using it). diff --git a/test/lab1_test.cc b/test/lab1_test.cc new file mode 100644 index 0000000..d12239b --- /dev/null +++ b/test/lab1_test.cc @@ -0,0 +1,46 @@ +#include "gtest/gtest.h" +#include "leveldb/env.h" +#include "leveldb/db.h" +using namespace leveldb; +using Field = std::pair; // field_name:field_value +using FieldArray = std::vector>; + +Status OpenDB(std::string dbName, DB **db) { + Options options; + options.create_if_missing = true; + return DB::Open(options, dbName, db); +} + +TEST(TestLab1, Basic) { + DestroyDB("testdb",Options()); + DB *db; + + if(OpenDB("testdb", &db).ok() == false) { + std::cerr << "open db failed" << std::endl; + abort(); + } + std::string key = "k_1"; + + FieldArray fields = { + {"name", "Customer#000000001"}, + {"address", "IVhzIApeRb"}, + {"phone", "25-989-741-2988"} + }; + + // 序列化并插入 + db->PutFields(WriteOptions(), key, fields); + + // 读取并反序列化 + FieldArray fields_ret; + db->GetFields(ReadOptions(), key, &fields_ret); + + Field field = {"name", "Customer#000000001"}; + std::vector resKeys = db->FindKeysByField(field); +} + + +int main(int argc, char** argv) { + // All tests currently run with the same read-only file limits. + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/util/serialize_value.cc b/util/serialize_value.cc new file mode 100644 index 0000000..6a30e27 --- /dev/null +++ b/util/serialize_value.cc @@ -0,0 +1,10 @@ +#include "util/serialize_value.h" + +namespace leveldb{ + std::string SerializeValue(const FieldArray& fields){ + return ""; + } + FieldArray *ParseValue(const std::string& value_str){ + return new FieldArray; + } +} \ No newline at end of file diff --git a/util/serialize_value.h b/util/serialize_value.h new file mode 100644 index 0000000..6f9fb2c --- /dev/null +++ b/util/serialize_value.h @@ -0,0 +1,13 @@ +#ifndef STORAGE_LEVELDB_UTIL_SERIALIZE_VALUE_H_ +#define STORAGE_LEVELDB_UTIL_SERIALIZE_VALUE_H_ + +#include +#include +namespace leveldb{ + using Field = std::pair; // field_name:field_value + using FieldArray = std::vector>; + + std::string SerializeValue(const FieldArray& fields); + FieldArray *ParseValue(const std::string& value_str); +} +#endif \ No newline at end of file From c942a4415d4aa7a2c8248b1f46c07b8ce1416b7f Mon Sep 17 00:00:00 2001 From: augurier <14434658+augurier@user.noreply.gitee.com> Date: Tue, 19 Nov 2024 20:13:37 +0800 Subject: [PATCH 03/14] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=BA=86=EF=BC=88?= =?UTF-8?q?=E5=8F=8D=EF=BC=89=E5=BA=8F=E5=88=97=E5=8C=96=E5=AD=98=E5=82=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/db_impl.cc | 2 +- test/lab1_test.cc | 5 +++++ util/serialize_value.cc | 42 ++++++++++++++++++++++++++++++++++++++---- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/db/db_impl.cc b/db/db_impl.cc index f423733..aaff6fb 100644 --- a/db/db_impl.cc +++ b/db/db_impl.cc @@ -1169,7 +1169,7 @@ Status DBImpl::GetFields(const ReadOptions& options, const Slice& key, FieldArray* fields) { std::string value; Status s = DBImpl::Get(options, key, &value); - fields = ParseValue(value); + *fields = *ParseValue(value); return s; } diff --git a/test/lab1_test.cc b/test/lab1_test.cc index d12239b..94b0ee4 100644 --- a/test/lab1_test.cc +++ b/test/lab1_test.cc @@ -33,7 +33,12 @@ TEST(TestLab1, Basic) { // 读取并反序列化 FieldArray fields_ret; db->GetFields(ReadOptions(), key, &fields_ret); + // ASSERT_EQ(fields, fields_ret); 顺序不一样 + for (const Field& pairs : fields_ret) { + ASSERT_NE(std::find(fields.begin(), fields.end(), pairs), fields.end()); + } + //todo Field field = {"name", "Customer#000000001"}; std::vector resKeys = db->FindKeysByField(field); } diff --git a/util/serialize_value.cc b/util/serialize_value.cc index 6a30e27..28ab79a 100644 --- a/util/serialize_value.cc +++ b/util/serialize_value.cc @@ -1,10 +1,44 @@ #include "util/serialize_value.h" +#include +#include +#include "util/coding.h" +#include namespace leveldb{ - std::string SerializeValue(const FieldArray& fields){ - return ""; +bool compareByFirst(const Field& a, const Field& b) { + return a.first < b.first; // 按字段名升序排序 +} + +std::string SerializeValue(const FieldArray& fields){ + FieldArray sortFields = fields; + std::sort(sortFields.begin(), sortFields.end(), compareByFirst); + std::string result; + for (const Field& pairs : sortFields) { + PutLengthPrefixedSlice(&result, pairs.first); + PutLengthPrefixedSlice(&result, pairs.second); } - FieldArray *ParseValue(const std::string& value_str){ - return new FieldArray; + return result; +} + +FieldArray *ParseValue(const std::string& value_str){ + Slice valueSlice(value_str); + FieldArray *res = new FieldArray; + Slice nameSlice = Slice(); + Slice valSlice = Slice(); + std::string nameStr; + std::string valStr; + while(GetLengthPrefixedSlice(&valueSlice, &nameSlice)){ + nameStr = nameSlice.ToString(); + + if(GetLengthPrefixedSlice(&valueSlice, &valSlice)){ + valStr = valSlice.ToString(); + res->emplace_back(nameStr, valStr); + } else { + std::cout << "name and val not match!" << std::endl; + } + nameSlice = Slice(); + valSlice = Slice(); } + return res; +} } \ No newline at end of file From 2801f6c624f5c4b4645c0d772ab29df96e645089 Mon Sep 17 00:00:00 2001 From: augurier <14434658+augurier@user.noreply.gitee.com> Date: Sat, 23 Nov 2024 14:34:24 +0800 Subject: [PATCH 04/14] =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E5=88=9D=E7=A8=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 设计文档.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 设计文档.md diff --git a/设计文档.md b/设计文档.md new file mode 100644 index 0000000..45adb28 --- /dev/null +++ b/设计文档.md @@ -0,0 +1,54 @@ +# 1. 项目概述 +leveldb中的存储原本只支持简单的字节序列,在这个项目中我们对其功能进行拓展,使其可以包含多个字段,并通过这些字段实现类似数据库列查询的功能。但如果仅通过字段查找数据,需要对整个数据库的遍历,不够高效,因此还要新增二级索引,提高对特定字段的查询效率。 + +本文档涵盖的设计内容只是最初的设想,实现过程中大概率会进行调整甚至重构。第5、6、7部分也将在项目落实的过程中进行补充完善。 + +# 2. 功能设计 +## 2.1 字段设计 +设计目标:对value存储读取时进行序列化编码,使其支持字段。 + +实现思路:设计之初有考虑增加一些元数据(例如过滤器、字段偏移支持二分)来加速查询。但考虑到在数据库中kv的数量是十分庞大的,新加数据结构会带来巨大的空间开销。因此我们决定在这里牺牲时间换取空间,而将时间的加速放在索引中。 +在这一基础上,我们对序列化进行了简单的优化:将字段名排序后,一一调用leveldb中原本的编码方法`PutLengthPrefixedSlice`存入value。这样不会有额外的空间开销,而好处在于遍历一个value的字段时,如果得到的字段名比目标大,就可以提前结束遍历。 +最终db类提供了新接口`putFields`, `getFields`,分别对传入的字段序列化后调用原来的`put`, `get`接口。 +**`FindKeysByField`调用`NewIterator`遍历所有数据,field名和值符合则加入返回的key中。这里的问题在于遍历中可能符合条件的key之前已经被删了,所以还要维护一个被删的key数组?总感觉这个玩意性能有点太差了** + +## 2.2 二级索引 +设计目标:对某个字段或属性建立索引,提高对该字段的查询效率。需考虑索引的创建、删除、维护。 + +实现思路: 这一部分的难点主要在于,索引数据与原数据的存储需要进行隔离,不同操作之间存在同步与异步问题,还需要考虑两种数据间的一致性。为了使设计简洁化,避免不同模块耦合带来潜在的问题,我们的设计如下: +1. 总体上,我们对两种数据分别创建一个db类的对象`kvDb, indexDb`。对外的接口类`FieldDb`包含了这两个对象,提供原先的leveldb各种接口,以及新功能,并在这一层完成两个对象的管理。 +2. `kvDb`与原先的存储一致,`indexDb`中key是原key与索引字段的联合编码,value是空(详见数据结构设计),并且lab1中新增的接口也不会被调用到。 +3. 在open新的fieldDb时,会创建这两个对象并open他们。fieldDb会维护一个字符串数组`fieldWithIndex`,记录了当前哪些字段名拥有索引。一个当前状态成员`pair`类型的`nowTask`,表示数据库当前处于 空闲`free`、创建索引中`creating`、删除索引中`deleting`,及对应的字段名(free时为空)。一个`pair`的队列`taskQueue`, 维护了创建或者删除索引的任务请求。创建或删除索引时,把任务加入队尾并休眠。一个任务完成后唤醒队首继续下一个任务(类似write机制) +``` +class FieldDb { + Db *kvDb; + Db *indexDb; + string[] fieldWithIndex; + pair nowTask; + queue> taskQueue; + vector tmpAddKvs; + vector tmpDeleteKvs; + + //原db所有对外接口,下面没提就调用kvDb相应函数 +} +``` +4. 一个创建/删除索引任务开始时,先修改`nowTask`,再对`kvDb`调用`FindKvsByField`(类似lab1的`FindKeysByField`但也要返回field对应的值),对返回的数组(合并上5中提到的tmpAddKvs、tmpDeleteKvs)中每个元素一一与字段名编码成新key,调用`indexDb`中的put或delete。 +5. fieldDb也提供了`put`, `get`,`getFields`, `putFields`,`delete`接口。对前三者,简单调用`kvDb`中的对应接口(不涉及索引)。对`putFields`,先调用`kvDb`中的`putFields`,写完后如果写入的字段名中: +有`nowTask.fieldName`,则加入`tmpAddkvs`,保证创建删除索引时,新来的数据也被记录。 +有`fieldWithIndex`中有的字段,则直接对`kvDb`调用一个(多个)`put`。 +`delete`也是如此,创建删除索引时新来的要delete的数据由`tmpDeletekvs`维护。 +**只考虑到这,理论上每个数据库内部的日志、快照、合并等模块不会互相影响(创建的那些current、manifest文件名好像会冲突)** + +# 3. 数据结构设计 +`indexDb`的kv编码:**暂时考虑助教文档那种** +# 4. 接口/函数设计 + +# 5. 功能测试 + +# 6. 可能的挑战与解决方案 +已想到的部分在之前已阐述,其余待发现。 + +# 7. 分工与进度安排 + 功能 | 完成日期 | 分工 +:------|:---------|:------ +value序列化|11.19 | 李度 From 53da4d14ffe078ccdf38fec75b046c88f03a2430 Mon Sep 17 00:00:00 2001 From: cyq <1056374449@qq.com> Date: Sat, 23 Nov 2024 18:03:40 +0800 Subject: [PATCH 05/14] =?UTF-8?q?=E4=B8=80=E4=BA=9B=E6=83=B3=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 ++++ 设计文档.md | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c4b2425..9e34c6c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ # Build directory. build/ out/ + +# clangd +.cache/ +compile_commands.json \ No newline at end of file diff --git a/设计文档.md b/设计文档.md index 45adb28..63c6f71 100644 --- a/设计文档.md +++ b/设计文档.md @@ -11,7 +11,7 @@ leveldb中的存储原本只支持简单的字节序列,在这个项目中我 在这一基础上,我们对序列化进行了简单的优化:将字段名排序后,一一调用leveldb中原本的编码方法`PutLengthPrefixedSlice`存入value。这样不会有额外的空间开销,而好处在于遍历一个value的字段时,如果得到的字段名比目标大,就可以提前结束遍历。 最终db类提供了新接口`putFields`, `getFields`,分别对传入的字段序列化后调用原来的`put`, `get`接口。 **`FindKeysByField`调用`NewIterator`遍历所有数据,field名和值符合则加入返回的key中。这里的问题在于遍历中可能符合条件的key之前已经被删了,所以还要维护一个被删的key数组?总感觉这个玩意性能有点太差了** - +**cyq:如果考虑mvcc的话,感觉好像不用担心遍历过程中的问题** ## 2.2 二级索引 设计目标:对某个字段或属性建立索引,提高对该字段的查询效率。需考虑索引的创建、删除、维护。 @@ -38,6 +38,7 @@ class FieldDb { 有`fieldWithIndex`中有的字段,则直接对`kvDb`调用一个(多个)`put`。 `delete`也是如此,创建删除索引时新来的要delete的数据由`tmpDeletekvs`维护。 **只考虑到这,理论上每个数据库内部的日志、快照、合并等模块不会互相影响(创建的那些current、manifest文件名好像会冲突)** +**cyq:这个问题比较好解决,其实只要把两个数据库放到两个不同的子目录里面就可以了。 但是这里有个问题是,先putfields后,index加入tmpAddkvs前有对于索引的读取,那么会不会有不一致的问题** # 3. 数据结构设计 `indexDb`的kv编码:**暂时考虑助教文档那种** From 18634883f32890afa4ba75adc00194623272e8ee Mon Sep 17 00:00:00 2001 From: cyq <1056374449@qq.com> Date: Sat, 23 Nov 2024 18:24:51 +0800 Subject: [PATCH 06/14] =?UTF-8?q?=E4=B8=80=E4=BA=9B=E6=83=B3=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 设计文档.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/设计文档.md b/设计文档.md index 63c6f71..e2b22fd 100644 --- a/设计文档.md +++ b/设计文档.md @@ -11,7 +11,7 @@ leveldb中的存储原本只支持简单的字节序列,在这个项目中我 在这一基础上,我们对序列化进行了简单的优化:将字段名排序后,一一调用leveldb中原本的编码方法`PutLengthPrefixedSlice`存入value。这样不会有额外的空间开销,而好处在于遍历一个value的字段时,如果得到的字段名比目标大,就可以提前结束遍历。 最终db类提供了新接口`putFields`, `getFields`,分别对传入的字段序列化后调用原来的`put`, `get`接口。 **`FindKeysByField`调用`NewIterator`遍历所有数据,field名和值符合则加入返回的key中。这里的问题在于遍历中可能符合条件的key之前已经被删了,所以还要维护一个被删的key数组?总感觉这个玩意性能有点太差了** -**cyq:如果考虑mvcc的话,感觉好像不用担心遍历过程中的问题** +**cyq:如果考虑mvcc的话,感觉好像不用担心遍历过程中kv被修改的问题的问题** ## 2.2 二级索引 设计目标:对某个字段或属性建立索引,提高对该字段的查询效率。需考虑索引的创建、删除、维护。 @@ -38,7 +38,7 @@ class FieldDb { 有`fieldWithIndex`中有的字段,则直接对`kvDb`调用一个(多个)`put`。 `delete`也是如此,创建删除索引时新来的要delete的数据由`tmpDeletekvs`维护。 **只考虑到这,理论上每个数据库内部的日志、快照、合并等模块不会互相影响(创建的那些current、manifest文件名好像会冲突)** -**cyq:这个问题比较好解决,其实只要把两个数据库放到两个不同的子目录里面就可以了。 但是这里有个问题是,先putfields后,index加入tmpAddkvs前有对于索引的读取,那么会不会有不一致的问题** +**cyq:这个问题比较好解决,其实只要把两个数据库放到两个不同的子目录里面就可以了。 但是这里有个问题是,先putfields后,index加入tmpAddkvs前有对于索引的读取,那么会不会有不一致的问题。再或者putfields之后putindex之前数据库崩溃了,这种情况应该怎么办。总的来说,就是如果kv和index写入存在先后顺序,这之间发生了并发的读取、数据库崩溃如何保证一致性** # 3. 数据结构设计 `indexDb`的kv编码:**暂时考虑助教文档那种** From de6d67f12b56317450af24155c18ecd884cf4285 Mon Sep 17 00:00:00 2001 From: augurier <14434658+augurier@user.noreply.gitee.com> Date: Sun, 24 Nov 2024 15:40:13 +0800 Subject: [PATCH 07/14] =?UTF-8?q?=E8=A1=A5=E5=85=85=E4=BA=86=E4=B8=80?= =?UTF-8?q?=E4=BA=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 设计文档.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/设计文档.md b/设计文档.md index 45adb28..5ba094c 100644 --- a/设计文档.md +++ b/设计文档.md @@ -10,7 +10,7 @@ leveldb中的存储原本只支持简单的字节序列,在这个项目中我 实现思路:设计之初有考虑增加一些元数据(例如过滤器、字段偏移支持二分)来加速查询。但考虑到在数据库中kv的数量是十分庞大的,新加数据结构会带来巨大的空间开销。因此我们决定在这里牺牲时间换取空间,而将时间的加速放在索引中。 在这一基础上,我们对序列化进行了简单的优化:将字段名排序后,一一调用leveldb中原本的编码方法`PutLengthPrefixedSlice`存入value。这样不会有额外的空间开销,而好处在于遍历一个value的字段时,如果得到的字段名比目标大,就可以提前结束遍历。 最终db类提供了新接口`putFields`, `getFields`,分别对传入的字段序列化后调用原来的`put`, `get`接口。 -**`FindKeysByField`调用`NewIterator`遍历所有数据,field名和值符合则加入返回的key中。这里的问题在于遍历中可能符合条件的key之前已经被删了,所以还要维护一个被删的key数组?总感觉这个玩意性能有点太差了** +`FindKeysByField`调用`NewIterator`遍历所有数据,field名和值符合则加入返回的key中。 ## 2.2 二级索引 设计目标:对某个字段或属性建立索引,提高对该字段的查询效率。需考虑索引的创建、删除、维护。 @@ -32,15 +32,17 @@ class FieldDb { //原db所有对外接口,下面没提就调用kvDb相应函数 } ``` -4. 一个创建/删除索引任务开始时,先修改`nowTask`,再对`kvDb`调用`FindKvsByField`(类似lab1的`FindKeysByField`但也要返回field对应的值),对返回的数组(合并上5中提到的tmpAddKvs、tmpDeleteKvs)中每个元素一一与字段名编码成新key,调用`indexDb`中的put或delete。 +4. 一个创建/删除索引任务开始时,先修改`nowTask`,再对`kvDb`调用`FindKvsByField`(类似lab1的`FindKeysByField`但也要返回field对应的值),对返回的数组(合并上5中提到的tmpAddKvs、tmpDeleteKvs)中每个元素一一与字段名编码成新key,调用`indexDb`中的put或delete。当`indexdb`把所有操作执行完后,`FieldDb`把一个``写入一个文件`IndexLog`,并在`fieldWithIndex`中添加/删除这个`fieldName`。**这样分离了两个数据库的写,只有indexDb全写完才能读这个索引,在这之前崩溃了就回滚不要了。在数据库恢复时,先用kvdb的日志把kvdb恢复了,再根据IndexLog的内容重新创建索引,indexdb里的恢复就不需要了。另外这个地方的测试感觉也不好做** 5. fieldDb也提供了`put`, `get`,`getFields`, `putFields`,`delete`接口。对前三者,简单调用`kvDb`中的对应接口(不涉及索引)。对`putFields`,先调用`kvDb`中的`putFields`,写完后如果写入的字段名中: 有`nowTask.fieldName`,则加入`tmpAddkvs`,保证创建删除索引时,新来的数据也被记录。 有`fieldWithIndex`中有的字段,则直接对`kvDb`调用一个(多个)`put`。 `delete`也是如此,创建删除索引时新来的要delete的数据由`tmpDeletekvs`维护。 -**只考虑到这,理论上每个数据库内部的日志、快照、合并等模块不会互相影响(创建的那些current、manifest文件名好像会冲突)** +6. 对两个数据库的其他部分,理论上每个数据库内部的其他模块不会互相影响(注意创建的那些current、manifest文件名会冲突) + # 3. 数据结构设计 `indexDb`的kv编码:**暂时考虑助教文档那种** +`IndexLog`的编码:字段名 加 类型(创/删) # 4. 接口/函数设计 # 5. 功能测试 From 17ed0600c7699831f7cb4e625640dd163a935bd6 Mon Sep 17 00:00:00 2001 From: augurier <14434658+augurier@user.noreply.gitee.com> Date: Mon, 25 Nov 2024 16:41:58 +0800 Subject: [PATCH 08/14] =?UTF-8?q?=E5=A4=A7=E8=87=B4=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 设计文档.md | 60 ++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/设计文档.md b/设计文档.md index 5ba094c..12ad731 100644 --- a/设计文档.md +++ b/设计文档.md @@ -1,7 +1,7 @@ # 1. 项目概述 leveldb中的存储原本只支持简单的字节序列,在这个项目中我们对其功能进行拓展,使其可以包含多个字段,并通过这些字段实现类似数据库列查询的功能。但如果仅通过字段查找数据,需要对整个数据库的遍历,不够高效,因此还要新增二级索引,提高对特定字段的查询效率。 -本文档涵盖的设计内容只是最初的设想,实现过程中大概率会进行调整甚至重构。第5、6、7部分也将在项目落实的过程中进行补充完善。 +本文档涵盖的设计内容只是最初的设想,实现过程中大概率会进行调整甚至重构。各部分也将在项目落实的过程中进行补充完善。 # 2. 功能设计 ## 2.1 字段设计 @@ -18,37 +18,67 @@ leveldb中的存储原本只支持简单的字节序列,在这个项目中我 实现思路: 这一部分的难点主要在于,索引数据与原数据的存储需要进行隔离,不同操作之间存在同步与异步问题,还需要考虑两种数据间的一致性。为了使设计简洁化,避免不同模块耦合带来潜在的问题,我们的设计如下: 1. 总体上,我们对两种数据分别创建一个db类的对象`kvDb, indexDb`。对外的接口类`FieldDb`包含了这两个对象,提供原先的leveldb各种接口,以及新功能,并在这一层完成两个对象的管理。 2. `kvDb`与原先的存储一致,`indexDb`中key是原key与索引字段的联合编码,value是空(详见数据结构设计),并且lab1中新增的接口也不会被调用到。 -3. 在open新的fieldDb时,会创建这两个对象并open他们。fieldDb会维护一个字符串数组`fieldWithIndex`,记录了当前哪些字段名拥有索引。一个当前状态成员`pair`类型的`nowTask`,表示数据库当前处于 空闲`free`、创建索引中`creating`、删除索引中`deleting`,及对应的字段名(free时为空)。一个`pair`的队列`taskQueue`, 维护了创建或者删除索引的任务请求。创建或删除索引时,把任务加入队尾并休眠。一个任务完成后唤醒队首继续下一个任务(类似write机制) +3. 在open新的fieldDb时,会创建这两个对象并open他们。fieldDb会维护一个字符串数组`fieldWithIndex`,记录了当前哪些字段名拥有索引。一个`pair`的队列`taskQueue`, 维护了创建或者删除索引的任务请求。创建或删除索引时,把任务加入队尾并休眠。一个任务完成后唤醒队首继续下一个任务(类似write机制) ``` class FieldDb { Db *kvDb; Db *indexDb; string[] fieldWithIndex; - pair nowTask; - queue> taskQueue; - vector tmpAddKvs; - vector tmpDeleteKvs; + queue> taskQueue; + // 0建 or 1删,或把pair抽象成一个类 //原db所有对外接口,下面没提就调用kvDb相应函数 + bool CreateIndexOnField(const std::string& fieldName){、 + //加入taskQueue并休眠/拿写锁 + //kvdb->FindKvsByField得到数据信息 + //编码 + //kvdb->writeIndexLog写索引日志 + //indexDb->put存储索引 + //fieldWithIndex.insert(fieldName) + //唤醒taskQueue队首/释放写锁 + } + bool DeleteIndex(std::string &fieldName); //同上 + std::vector QueryByIndex(Field &field){ + //判断fieldWithIndex是否有 + //indexDb->iterator得到编码后的数据信息 + //解码返回 + } } ``` -4. 一个创建/删除索引任务开始时,先修改`nowTask`,再对`kvDb`调用`FindKvsByField`(类似lab1的`FindKeysByField`但也要返回field对应的值),对返回的数组(合并上5中提到的tmpAddKvs、tmpDeleteKvs)中每个元素一一与字段名编码成新key,调用`indexDb`中的put或delete。当`indexdb`把所有操作执行完后,`FieldDb`把一个``写入一个文件`IndexLog`,并在`fieldWithIndex`中添加/删除这个`fieldName`。**这样分离了两个数据库的写,只有indexDb全写完才能读这个索引,在这之前崩溃了就回滚不要了。在数据库恢复时,先用kvdb的日志把kvdb恢复了,再根据IndexLog的内容重新创建索引,indexdb里的恢复就不需要了。另外这个地方的测试感觉也不好做** -5. fieldDb也提供了`put`, `get`,`getFields`, `putFields`,`delete`接口。对前三者,简单调用`kvDb`中的对应接口(不涉及索引)。对`putFields`,先调用`kvDb`中的`putFields`,写完后如果写入的字段名中: -有`nowTask.fieldName`,则加入`tmpAddkvs`,保证创建删除索引时,新来的数据也被记录。 -有`fieldWithIndex`中有的字段,则直接对`kvDb`调用一个(多个)`put`。 -`delete`也是如此,创建删除索引时新来的要delete的数据由`tmpDeletekvs`维护。 -6. 对两个数据库的其他部分,理论上每个数据库内部的其他模块不会互相影响(注意创建的那些current、manifest文件名会冲突) +4. 一个创建/删除索引任务开始时,首先锁住写锁,再对`kvDb`调用`FindKvsByField`(类似lab1的`FindKeysByField`但也要返回field对应的值),对返回的数组中每个元素一一与字段名编码成新key,**合并在一个writebatch中写入日志**,再调用`indexDb`中的put或delete。当`indexdb`把所有操作执行完后,在`fieldWithIndex`中添加/删除这个`fieldName`,使得用户可以针对这个字段进行索引读,最后唤醒下一个任务,没有则释放写锁。 +5. fieldDb也提供了`put`, `get`,`getFields`, `putFields`,`delete`接口。对前三者,简单调用`kvDb`中的对应接口(不涉及索引)。 +对`putFields`,先判断是否有`fieldWithIndex`中有的字段,如果有,并对`kvDb`调用一个(多个)`put`,**但在写日志时一并加上索引日志写入**。 +`delete`逻辑一致。 +6. 针对索引的日志:为了保证两个数据库间的一致性,由`kvDb`的日志模块统一管理。这其中包含了两种chunk(kv写入和索引写入),在恢复时需要分别解析,决定往哪一个数据库中写入。索引写入的时机在4、5中的**加粗**部分,如何编码还有待设计。也就是说,`indexDb`本身的索引模块不再起到作用,项目后期可以修改关闭这一部分。 +7. 对两个数据库的其他部分,理论上每个数据库内部的其他模块不会互相影响。 # 3. 数据结构设计 -`indexDb`的kv编码:**暂时考虑助教文档那种** -`IndexLog`的编码:字段名 加 类型(创/删) +具体设计模块化,实现时再具体考虑。 +`indexDb`的kv编码:**暂时考虑助教文档那种** +区分日志中kv部分和index部分:**有待完成** + # 4. 接口/函数设计 +`FieldDb`的对外接口函数之前已展示,这里补充一些子数据库需提供给`FieldDb`的抽象功能(暂时想到的): +``` +class Db{ + //原有部分和lab1部分 + //kvdb需要: + pair FindKvsByField(string fieldName); //搜集索引需要的数据信息 + status writeIndexLog(string fieldName, pair); //向indexDb put前先写日志 +} +``` +类内部实现的功能函数,具体实现过程中再抽象。 # 5. 功能测试 +1. 基本的每个接口函数调用。 +2. 创建/删除索引时的并发读写、并发创/删。 +3. 数据库的恢复检查。 +4. 性能测试。 # 6. 可能的挑战与解决方案 -已想到的部分在之前已阐述,其余待发现。 +已想到的部分在之前已阐述,其余待发现。 +mark一些原代码(db中)的修改点:recover时日志解析修改,write时的日志写入可能要合并索引的写入。 # 7. 分工与进度安排 功能 | 完成日期 | 分工 From 1257726781750caa09425d532eb6967509d52bab Mon Sep 17 00:00:00 2001 From: cyq <> Date: Tue, 26 Nov 2024 02:31:11 +0800 Subject: [PATCH 09/14] =?UTF-8?q?=E5=B0=81=E8=A3=85=E4=BA=86InternalFieldA?= =?UTF-8?q?rray=EF=BC=8C=E5=88=9D=E6=AD=A5=E6=90=AD=E5=BB=BA=E4=BA=86Field?= =?UTF-8?q?DB=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fielddb/field_db.cpp | 88 +++++++++++++++++++++++++++++++++++++++++++++++++ fielddb/field_db.h | 33 +++++++++++++++++++ util/serialize_value.cc | 34 +++++++++++++++++++ util/serialize_value.h | 51 +++++++++++++++++++++++++--- 4 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 fielddb/field_db.cpp create mode 100644 fielddb/field_db.h diff --git a/fielddb/field_db.cpp b/fielddb/field_db.cpp new file mode 100644 index 0000000..1de6565 --- /dev/null +++ b/fielddb/field_db.cpp @@ -0,0 +1,88 @@ +#include "fielddb/field_db.h" +#include +#include +#include "leveldb/db.h" +#include "leveldb/env.h" +#include "leveldb/options.h" +#include "leveldb/status.h" +#include "util/serialize_value.h" + +namespace leveldb { +//TODO:打开fieldDB +static Status OpenFieldDB(const Options& options,const std::string& name,DB** dbptr) { + // options.env->CreateDir("./abc") + return Status::OK(); +} + + +Status FieldDB::Put(const WriteOptions &options, const Slice &key, const Slice &value) { + return kvDB->Put(options, key, value); +} + +// TODO:需要对是否进行index更新做处理 +Status FieldDB::PutFields(const WriteOptions &, const Slice &key, const FieldArray &fields) { + return Status::OK(); +} + +Status FieldDB::Delete(const WriteOptions &options, const Slice &key) { + return kvDB->Delete(options, key); +} +// TODO:根据updates里面的东西,要对是否需要更新index进行分别处理 +Status FieldDB::Write(const WriteOptions &options, WriteBatch *updates) { + return Status::OK(); +} + +Status FieldDB::Get(const ReadOptions &options, const Slice &key, std::string *value) { + return kvDB->Get(options, key, value); +} + +Status FieldDB::GetFields(const ReadOptions &options, const Slice &key, FieldArray *fields) { + std::string value; + Status status; + status = kvDB->Get(options, key, &value); + if(status.ok() == false) return status; + fields = ParseValue(value); + return status; +} + +std::vector FieldDB::FindKeysByField(Field &field) { + std::vector result; + auto iter = kvDB->NewIterator(ReadOptions()); + for(iter->SeekToFirst();iter->Valid();iter->Next()) { + InternalFieldArray fields(iter->value()); + if(fields.HasField(field)) { + result.push_back(iter->key().ToString()); + } + } + return result; +} + +Iterator * FieldDB::NewIterator(const ReadOptions &options) { + return kvDB->NewIterator(options); +} + +// TODO:使用统一seq进行snapshot管理 +const Snapshot * FieldDB::GetSnapshot() { + return kvDB->GetSnapshot(); +} +// TODO:同上 +void FieldDB::ReleaseSnapshot(const Snapshot *snapshot) { + kvDB->ReleaseSnapshot(snapshot); +} + +bool FieldDB::GetProperty(const Slice &property, std::string *value) { + return kvDB->GetProperty(property, value) | indexDB->GetProperty(property, value); +} + +void FieldDB::GetApproximateSizes(const Range *range, int n, uint64_t *sizes) { + uint64_t temp = 0; + kvDB->GetApproximateSizes(range, n, sizes); + indexDB->GetApproximateSizes(range, n, &temp); + *sizes += temp; +} + +void FieldDB::CompactRange(const Slice *begin, const Slice *end) { + kvDB->CompactRange(begin, end); +} + +} // end of namespace \ No newline at end of file diff --git a/fielddb/field_db.h b/fielddb/field_db.h new file mode 100644 index 0000000..23534aa --- /dev/null +++ b/fielddb/field_db.h @@ -0,0 +1,33 @@ +#include "db/db_impl.h" +#include "leveldb/db.h" +#include "leveldb/options.h" +#include "leveldb/status.h" +namespace leveldb{ +class FieldDB:leveldb::DB { +public: +/*lab1的要求*/ + Status Put(const WriteOptions &options, const Slice &key, const Slice &value) override; + Status PutFields(const WriteOptions &, const Slice &key, const FieldArray &fields) override; + Status Delete(const WriteOptions &options, const Slice &key) override; + Status Write(const WriteOptions &options, WriteBatch *updates) override; + Status Get(const ReadOptions &options, const Slice &key, std::string *value) override; + Status GetFields(const ReadOptions &options, const Slice &key, FieldArray *fields) override; + std::vector FindKeysByField(Field &field) override; + Iterator * NewIterator(const ReadOptions &options) override; + const Snapshot * GetSnapshot() override; + void ReleaseSnapshot(const Snapshot *snapshot) override; + bool GetProperty(const Slice &property, std::string *value) override; + void GetApproximateSizes(const Range *range, int n, uint64_t *sizes) override; + void CompactRange(const Slice *begin, const Slice *end) override; +/*与索引相关*/ + bool CreateIndexOnField(const std::string& field_name); + bool DeleteIndex(std::string &field_name); + std::vector QueryByIndex(Field &field); + +private: + static Status OpenFieldDB(const Options& options,const std::string& name,DB** dbptr); + + leveldb::DBImpl *indexDB; + leveldb::DBImpl *kvDB; +}; +} // end of namespace \ No newline at end of file diff --git a/util/serialize_value.cc b/util/serialize_value.cc index 28ab79a..a1bdb08 100644 --- a/util/serialize_value.cc +++ b/util/serialize_value.cc @@ -41,4 +41,38 @@ FieldArray *ParseValue(const std::string& value_str){ } return res; } + +void InternalFieldArray::Map() { + if(isMapped) return; + for(const Field& pair : fields) { + map[pair.first] = pair.second; + } + isMapped = true; +} + +std::string InternalFieldArray::Serialize() { + std::string result; + if(isMapped) { + for(auto pair : map) { + PutLengthPrefixedSlice(&result, pair.first); + PutLengthPrefixedSlice(&result, pair.second); + } + } else { + result = SerializeValue(fields); + } + return result; +} + +bool InternalFieldArray::HasField(const Field& field) { + if(isMapped) { + if(map.count(field.first) && map[field.first] == field.second) { + return true; + } + return false; + } + return std::find(fields.begin(),fields.end(),field) != fields.end(); +} + + + } \ No newline at end of file diff --git a/util/serialize_value.h b/util/serialize_value.h index 6f9fb2c..ff6e68c 100644 --- a/util/serialize_value.h +++ b/util/serialize_value.h @@ -1,13 +1,56 @@ #ifndef STORAGE_LEVELDB_UTIL_SERIALIZE_VALUE_H_ #define STORAGE_LEVELDB_UTIL_SERIALIZE_VALUE_H_ +#include #include #include +#include +#include "leveldb/slice.h" +#include "util/coding.h" namespace leveldb{ - using Field = std::pair; // field_name:field_value - using FieldArray = std::vector>; +using Field = std::pair; // field_name:field_value +using FieldArray = std::vector>; - std::string SerializeValue(const FieldArray& fields); - FieldArray *ParseValue(const std::string& value_str); +std::string SerializeValue(const FieldArray& fields); +FieldArray *ParseValue(const std::string& value_str); + +class InternalFieldArray { +public: +using FieldMap = std::map; + +InternalFieldArray(const FieldArray &fields, bool to_map = false):fields(fields),isMapped(false) { + if(to_map) Map(); +} + + +InternalFieldArray(const std::string& value_str) { + Slice valueSlice(value_str); + Slice nameSlice,valSlice; + while(GetLengthPrefixedSlice(&valueSlice, &nameSlice)) { + if(GetLengthPrefixedSlice(&valueSlice, &valueSlice)) { + map[nameSlice.ToString()] = valueSlice.ToString(); + } else { + std::cout << "name and val not match!" << std::endl; + } + nameSlice.clear(); + valSlice.clear(); + } +} + +InternalFieldArray(const Slice& slice):leveldb::InternalFieldArray(slice.ToString()) {} + +//将vector变为用map存 +void Map(); + +std::string Serialize(); + +bool HasField(const Field& field); + + +private: +bool isMapped; +const FieldArray fields; +FieldMap map; +}; } #endif \ No newline at end of file From abe34b2c6bd94d462def7d45aec003fb2c0d50ae Mon Sep 17 00:00:00 2001 From: augurier <14434658+augurier@user.noreply.gitee.com> Date: Tue, 26 Nov 2024 11:03:27 +0800 Subject: [PATCH 10/14] =?UTF-8?q?=E5=88=86=E5=B7=A5=E4=B8=8E=E8=A1=A5?= =?UTF-8?q?=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 设计文档.md | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/设计文档.md b/设计文档.md index 12ad731..babc37e 100644 --- a/设计文档.md +++ b/设计文档.md @@ -8,7 +8,19 @@ leveldb中的存储原本只支持简单的字节序列,在这个项目中我 设计目标:对value存储读取时进行序列化编码,使其支持字段。 实现思路:设计之初有考虑增加一些元数据(例如过滤器、字段偏移支持二分)来加速查询。但考虑到在数据库中kv的数量是十分庞大的,新加数据结构会带来巨大的空间开销。因此我们决定在这里牺牲时间换取空间,而将时间的加速放在索引中。 -在这一基础上,我们对序列化进行了简单的优化:将字段名排序后,一一调用leveldb中原本的编码方法`PutLengthPrefixedSlice`存入value。这样不会有额外的空间开销,而好处在于遍历一个value的字段时,如果得到的字段名比目标大,就可以提前结束遍历。 +在这一基础上,我们对序列化进行了简单的优化:将字段名排序后,一一调用leveldb中原本的编码方法`PutLengthPrefixedSlice`存入value。这样不会有额外的空间开销,而好处在于遍历一个value的字段时,如果得到的字段名比目标大,就可以提前结束遍历。 +``` +std::string SerializeValue(const FieldArray& fields){ + FieldArray sortFields = fields; + std::sort(sortFields.begin(), sortFields.end(), compareByFirst); + std::string result; + for (const Field& pairs : sortFields) { + PutLengthPrefixedSlice(&result, pairs.first); + PutLengthPrefixedSlice(&result, pairs.second); + } + return result; +} +``` 最终db类提供了新接口`putFields`, `getFields`,分别对传入的字段序列化后调用原来的`put`, `get`接口。 `FindKeysByField`调用`NewIterator`遍历所有数据,field名和值符合则加入返回的key中。 @@ -49,14 +61,14 @@ class FieldDb { 5. fieldDb也提供了`put`, `get`,`getFields`, `putFields`,`delete`接口。对前三者,简单调用`kvDb`中的对应接口(不涉及索引)。 对`putFields`,先判断是否有`fieldWithIndex`中有的字段,如果有,并对`kvDb`调用一个(多个)`put`,**但在写日志时一并加上索引日志写入**。 `delete`逻辑一致。 -6. 针对索引的日志:为了保证两个数据库间的一致性,由`kvDb`的日志模块统一管理。这其中包含了两种chunk(kv写入和索引写入),在恢复时需要分别解析,决定往哪一个数据库中写入。索引写入的时机在4、5中的**加粗**部分,如何编码还有待设计。也就是说,`indexDb`本身的索引模块不再起到作用,项目后期可以修改关闭这一部分。 +6. 针对索引的日志:为了保证两个数据库间的一致性,由`kvDb`的日志模块统一管理。这其中包含了两种chunk(kv写入和索引写入),在恢复时需要分别解析,决定往哪一个数据库中写入。索引写入的时机在4、5中的**加粗**部分,如何编码还有待设计。也就是说,`indexDb`本身的日志模块不再起到作用,项目后期可以修改关闭这一部分。 7. 对两个数据库的其他部分,理论上每个数据库内部的其他模块不会互相影响。 # 3. 数据结构设计 具体设计模块化,实现时再具体考虑。 `indexDb`的kv编码:**暂时考虑助教文档那种** -区分日志中kv部分和index部分:**有待完成** +区分日志中kv部分和index部分:思路是在writebatch中某个地方加个标识区分,每一类的编码与各自的key编码类似**细节有待完成** # 4. 接口/函数设计 `FieldDb`的对外接口函数之前已展示,这里补充一些子数据库需提供给`FieldDb`的抽象功能(暂时想到的): @@ -84,3 +96,9 @@ mark一些原代码(db中)的修改点:recover时日志解析修改,write时 功能 | 完成日期 | 分工 :------|:---------|:------ value序列化|11.19 | 李度 +fieldDb接口|11.25|陈胤遒 +lab1整体+测试|11.30|高宇菲 +fieldDb功能实现|12.10|李度 +kvdb功能实现与原代码修改|12.10|陈胤遒 +整体系统整合+测试|12.20|李度、陈胤遒、高宇菲 +性能测试|12.30|高宇菲 From f06fd7b5e5591d863a18332f2838353918577018 Mon Sep 17 00:00:00 2001 From: cyq <1056374449@qq.com> Date: Thu, 28 Nov 2024 20:12:12 +0800 Subject: [PATCH 11/14] =?UTF-8?q?3DB=E8=AE=BE=E8=AE=A1=E8=AE=BE=E6=83=B3?= =?UTF-8?q?=E8=8D=89=E7=A8=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3DB设计.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 3DB设计.md diff --git a/3DB设计.md b/3DB设计.md new file mode 100644 index 0000000..3e155a0 --- /dev/null +++ b/3DB设计.md @@ -0,0 +1,65 @@ +# 3DB +分别为kvDB,indexDB,metaDB。其中,前两个和之前存储内容相同,metaDB用来维护与并发控制相关的内容 +为了达成一致性,这里需要要有一个全局唯一的seq或者timestamp。这个信息编码在userkey中,比较器要进行对应修改,metaDB要按照先后顺序排列,另外两个不用考虑timestamp +# 具体操作 +这里将操作类型进行分类:普通put和get,索引put和get,创建(删除)索引。 +接下来将根据这个分类进行阐述 + +## 普通put和get +普通get直接调用kvDB的get +普通put要判断当前是否进行索引put和创建(删除)索引操作。如果有,则需要进入taskqueue等待。 + +## 索引put +如果当前字段中没有需要创建索引的,那么就是普通put + +如果当前没有正在创建(修改)的索引或者之前的对于同一个key的索引put,则首先向metaDB写入标记`(key,creating/deleting)`,表示事务的开始。然后构建请求,并向kvDB和indexDB分别写入。所有的请求的时间戳都和metaDB中标记的时间戳相同。全部完成之后,将之前在metaDB中的标记delete,表示事务的结束。 + +如果当前有正在创建(修改)的索引或者之前的对于同一个key的索引put,则判断本次put是否含有对应的索引,如果没有则按上面一段的操作进行。如果含有,则加入之前设计中的taskqueue,在索引创建完成后会进行处理。 + +我觉得:索引put涉及到了indexDB和kvDB两者之间的原子性 + +## 创建(删除)索引 +在进行这个操作之前对metaDB写入一个标记`(field,creating/deleting)`,表示在field上创建(删除)索引操作的事务的开始。(注:这里的key是包含了时间戳或者seq的)。 + +之后扫描kvDB,构建相应请求,对indexDB进行写入,这里也是通过writebatch写入的。写入完成之后,将之前的标记清除,表示当前的事务结束了。之后对于taskqueue里面的请求进行处理,完成之后,唤醒taskqueue中的请求。 + +我觉得:创建(删除)索引这个操作实际上只对indexDB进行了写入请求,并不涉及indexDB和kvDB两者之间的一致性 + +## 索引get +如果没有索引,则返回;如果索引正在创建,则存到taskqueue中,在索引创建完成之后进行处理(这里或许也可以直接返回);如果存在索引则将请求发往indexDB。 + +# 一致性,崩溃恢复(recovery) +## 创建(删除)索引 +如果meta中存在记录`(field,creating/deleting)`,则表示在创建(删除)索引的过程中的某个时间节点崩溃了。 + +如果在metaDB写入标记后崩溃,indexDB写入前崩溃。由于这个操作只涉及indexDB,且与当前创建(删除)索引相关的写入请求都被阻塞了,所以只要再扫描一遍全部的kvDB构造index写入请求就可以了。 + +如果是在indexDB写入完成之后崩溃,这实际上已经完成了创建(删除)索引操作,所以把metaDB中的标记清除即可。 + +这里最主要的问题在于如何判断崩溃时间点。对于写入标记前后通过metaDB中的记录判断。对于是否对indexDB完成写入通过能否在indexDB中找到对应的索引判断,因为索引是一个writebatch整体写入的,有原子性。 + +## 索引put +索引put涉及到kvDB和indexDB写入的一致性,这一点通过时间戳机制来保证。 + +如果metaDB中有记录`(key,creating/deleting)`,那么表明索引put过程中的某个时间节点崩溃了。由于kvDB和indexDB的写入是并发进行的,所以可能会出现四种情况: +1. kvDB和indexDB写入均未完成 +2. kvDB写入完成而indexDB未完成。 +3. kvDB写入未完成而indexDB写入完成 +4. 两者写入都完成,但是没有清除记录 + +kvDB的写入情况的判断如下:通过metaDB中的记录的key,查询kvDB。如果是creating操作且记录不存在或者得到的时间戳不等于metaDB记录的时间戳,则表明写入未完成;如果是deleting,如果存在记录,则表明写入未完成。 + +indexDB的写入情况判断如下:扫描indexDB,如果是creating操作且记录不存在或者得到的时间戳不等于metaDB记录的时间戳,则表明写入未完成;如果是deleting操作,如果存在对应的二级索引,则表明写入未完成。如果在kvDB中能够得到相应的kv,可以通过kvDB中的kv查询二级索引。 + +分别讨论creating和deleting操作下的四种情况的崩溃恢复的过程: +1. 如果是creating,则清除metaDB中的记录;如果是deleting,则继续delete +2. 如果是creating,则根据kvDB的写入构造请求写入indexDB;如果是deleting,则遍历indexDB删除对应的索引 +3. 如果是creating,且kvDB中有旧值,则将indexDB中所有相关的字段清除后根据旧值创建索引;如果是deleting,删除kvDB中的kv对 +4. 直接清除记录 + +当然,这些处理的方式会比较的细,总的来讲,只要kvDB完成写入,那么indexDB就可以完成更新;如果写入未完成,那么indexDB就需要用某种方式回滚。 + + + + + From 26bdb795905b5ba83e59acc07026041baad17f5a Mon Sep 17 00:00:00 2001 From: cyq <1056374449@qq.com> Date: Wed, 4 Dec 2024 11:50:11 +0800 Subject: [PATCH 12/14] =?UTF-8?q?=E4=B8=80=E4=BA=9B=E5=88=9D=E6=AD=A5?= =?UTF-8?q?=E7=9A=84=E6=83=B3=E6=B3=95=E5=92=8C=E5=AE=9E=E7=8E=B0=EF=BC=8C?= =?UTF-8?q?=E5=8F=AF=E8=83=BD=E9=9C=80=E8=A6=81=E5=A4=A7=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3DB设计.md | 7 +++++++ fielddb/field_db.cpp | 23 +++++++++++++++++++++-- fielddb/field_db.h | 36 +++++++++++++++++++++++++++++++----- fielddb/metakv.cpp | 20 ++++++++++++++++++++ fielddb/metakv.h | 26 ++++++++++++++++++++++++++ fielddb/request.cpp | 0 fielddb/request.h | 25 +++++++++++++++++++++++++ 7 files changed, 130 insertions(+), 7 deletions(-) create mode 100644 fielddb/metakv.cpp create mode 100644 fielddb/metakv.h create mode 100644 fielddb/request.cpp create mode 100644 fielddb/request.h diff --git a/3DB设计.md b/3DB设计.md index 3e155a0..b37079f 100644 --- a/3DB设计.md +++ b/3DB设计.md @@ -59,6 +59,13 @@ indexDB的写入情况判断如下:扫描indexDB,如果是creating操作且 当然,这些处理的方式会比较的细,总的来讲,只要kvDB完成写入,那么indexDB就可以完成更新;如果写入未完成,那么indexDB就需要用某种方式回滚。 +# 全写入方案 +不用时间戳,全部写入metaDB作为log,然后再写入kvDB和indexDB + +# 整体架构 +采用多线程架构 +由于二级索引理论上是幂等的操作,所以或许不用taskqueue来阻塞创建之后的写入? +如果这么看的话,其实创建(删除)索引的操作也不需要 diff --git a/fielddb/field_db.cpp b/fielddb/field_db.cpp index 1de6565..5d28813 100644 --- a/fielddb/field_db.cpp +++ b/fielddb/field_db.cpp @@ -1,5 +1,6 @@ #include "fielddb/field_db.h" #include +#include #include #include "leveldb/db.h" #include "leveldb/env.h" @@ -7,13 +8,31 @@ #include "leveldb/status.h" #include "util/serialize_value.h" -namespace leveldb { +namespace fielddb { +using namespace leveldb; //TODO:打开fieldDB -static Status OpenFieldDB(const Options& options,const std::string& name,DB** dbptr) { +Status FieldDB::OpenFieldDB(const Options& options,const std::string& name,DB** dbptr) { // options.env->CreateDir("./abc") + *dbptr = new FieldDB(options,name); return Status::OK(); } +Status FieldDB::Recover() { + +} + +FieldDB::FieldDB(const Options& options,const std::string& name) { + Status status; + status = Open(options, name+"_indexDB", &indexDB); + if(!status.ok()) return; + status = Open(options, name+"_kvDB", &kvDB); + if(!status.ok()) return; + status = Open(options, name+"_metaDB", &metaDB); + if(!status.ok()) return; + + Recover(); +} + Status FieldDB::Put(const WriteOptions &options, const Slice &key, const Slice &value) { return kvDB->Put(options, key, value); diff --git a/fielddb/field_db.h b/fielddb/field_db.h index 23534aa..411f564 100644 --- a/fielddb/field_db.h +++ b/fielddb/field_db.h @@ -1,10 +1,21 @@ #include "db/db_impl.h" +#include +#include +#include +#include #include "leveldb/db.h" #include "leveldb/options.h" +#include "leveldb/slice.h" #include "leveldb/status.h" -namespace leveldb{ -class FieldDB:leveldb::DB { +#include "port/port_stdcxx.h" +#include "fielddb/request.h" + +namespace fielddb { +using namespace leveldb; +class FieldDB : leveldb::DB { public: + FieldDB() = default; + FieldDB(const Options& options,const std::string& name); /*lab1的要求*/ Status Put(const WriteOptions &options, const Slice &key, const Slice &value) override; Status PutFields(const WriteOptions &, const Slice &key, const FieldArray &fields) override; @@ -24,10 +35,25 @@ public: bool DeleteIndex(std::string &field_name); std::vector QueryByIndex(Field &field); -private: static Status OpenFieldDB(const Options& options,const std::string& name,DB** dbptr); - leveldb::DBImpl *indexDB; - leveldb::DBImpl *kvDB; +private: + //根据metaDB的内容进行恢复 + Status Recover(); + +private: + leveldb::DB *metaDB; + leveldb::DB *indexDB; + leveldb::DB *kvDB; + + enum IndexStatus{ + Creating, + Deleting, + Exist + }; + std::map index; + port::Mutex _mutex; // mutex for taskqueue + std::deque taskqueue; + }; } // end of namespace \ No newline at end of file diff --git a/fielddb/metakv.cpp b/fielddb/metakv.cpp new file mode 100644 index 0000000..819030f --- /dev/null +++ b/fielddb/metakv.cpp @@ -0,0 +1,20 @@ +#include "fielddb/metakv.h" +#include "util/coding.h" +#include + +namespace fielddb { +using namespace leveldb; + +Slice MetaKV::metaKey() { + std::string buf; + PutLengthPrefixedSlice(&buf, Key); + PutFixed64(&buf, meta_seq); + PutFixed32(&buf, tag); + return Slice(buf); +} + +Slice MetaKV::metaValue() { + return Slice(SerializeValue(Fields)); +} + +} \ No newline at end of file diff --git a/fielddb/metakv.h b/fielddb/metakv.h new file mode 100644 index 0000000..f976830 --- /dev/null +++ b/fielddb/metakv.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include "leveldb/slice.h" +#include "util/serialize_value.h" +namespace fielddb { +using namespace leveldb; +/*根据写入的流程可以推断,需要存在metaDB中的数据其实都是带索引的数据,也就是FieldArray*/ +class MetaKV { + MetaKV(Slice &Key,FieldArray Fields): + Key(Key),Fields(Fields),tag(0),meta_seq(0) { } + inline int get_seq() { return meta_seq; } + inline void set_seq(int meta_seq) { this->meta_seq = meta_seq; } + inline void setPut() { tag = PUT; } + inline void setDelete() { tag = DELETE; } + Slice metaKey(); + Slice metaValue(); +private: + enum {PUT = 0x0,DELETE = 0x1}; + uint64_t meta_seq; + uint8_t tag; + Slice &Key; + FieldArray Fields; +}; +} \ No newline at end of file diff --git a/fielddb/request.cpp b/fielddb/request.cpp new file mode 100644 index 0000000..e69de29 diff --git a/fielddb/request.h b/fielddb/request.h new file mode 100644 index 0000000..3831fed --- /dev/null +++ b/fielddb/request.h @@ -0,0 +1,25 @@ +#include +#include "port/port_stdcxx.h" +#include "util/mutexlock.h" +#include "util/serialize_value.h" +namespace fielddb { +using namespace leveldb; +// 在taskqueue中的Request,由taskqueue最开始的线程处理一批Request +// 这个思路与write写入的思路类似 +class Request { +public: + Request(std::string *Key,std::string *Value,port::Mutex *mu): + Key(Key),Value(Value),hasFields(false),_cond(mu) { } + Request(std::string *Key,FieldArray *Fields,port::Mutex *mu): + Key(Key),Fields(Fields),hasFields(false),_cond(mu) { } + +private: + bool done; + port::CondVar _cond; + + bool hasFields; + std::string *Key; + std::string *Value; + FieldArray *Fields; +}; +} \ No newline at end of file From d6995373eadc459a536b8aaea0d7ca6f0e371c32 Mon Sep 17 00:00:00 2001 From: augurier <14434658+augurier@user.noreply.gitee.com> Date: Thu, 5 Dec 2024 14:19:56 +0800 Subject: [PATCH 13/14] =?UTF-8?q?=E4=BF=AE=E5=AE=8Cbug=EF=BC=8C=E8=A1=A5?= =?UTF-8?q?=E5=85=85lab1=E6=B5=8B=E8=AF=95=EF=BC=8C=E8=B7=91=E9=80=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 + db/db_impl.cc | 15 +++++-- fielddb/field_db.cpp | 89 +++++++++++++++++++------------------ fielddb/field_db.h | 39 +++++++++------- test/lab1_test.cc | 116 ++++++++++++++++++++++++++++++++++++------------ util/serialize_value.cc | 4 +- util/serialize_value.h | 49 ++++++++++---------- 7 files changed, 197 insertions(+), 117 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 981cda2..31536ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -192,6 +192,8 @@ target_sources(leveldb "util/status.cc" "util/serialize_value.h" "util/serialize_value.cc" + "fielddb/field_db.cpp" + "fielddb/field_db.h" # Only CMake 3.3+ supports PUBLIC sources in targets exported by "install". $<$:PUBLIC> diff --git a/db/db_impl.cc b/db/db_impl.cc index aaff6fb..4a7d25c 100644 --- a/db/db_impl.cc +++ b/db/db_impl.cc @@ -1173,8 +1173,17 @@ Status DBImpl::GetFields(const ReadOptions& options, const Slice& key, return s; } -std::vector DBImpl::FindKeysByField(Field &field){//todo - return std::vector(); +std::vector DBImpl::FindKeysByField(Field &field){ + std::vector result; + auto iter = NewIterator(ReadOptions()); + for(iter->SeekToFirst();iter->Valid();iter->Next()) { + std::string k = iter->key().ToString(); + InternalFieldArray fields(iter->value()); + if(fields.HasField(field)) { + result.push_back(iter->key().ToString()); + } + } + return result; } Iterator* DBImpl::NewIterator(const ReadOptions& options) { @@ -1510,7 +1519,7 @@ Status DB::Put(const WriteOptions& opt, const Slice& key, const Slice& value) { Status DB::PutFields(const WriteOptions& opt, const Slice& key, const FieldArray& fields) { std::string value = SerializeValue(fields); - DB::Put(opt, key, value); + return DB::Put(opt, key, value); } Status DB::Delete(const WriteOptions& opt, const Slice& key) { diff --git a/fielddb/field_db.cpp b/fielddb/field_db.cpp index 5d28813..d867411 100644 --- a/fielddb/field_db.cpp +++ b/fielddb/field_db.cpp @@ -11,40 +11,53 @@ namespace fielddb { using namespace leveldb; //TODO:打开fieldDB -Status FieldDB::OpenFieldDB(const Options& options,const std::string& name,DB** dbptr) { +Status FieldDB::OpenFieldDB(const Options& options, + const std::string& name, FieldDB** dbptr) { // options.env->CreateDir("./abc") - *dbptr = new FieldDB(options,name); - return Status::OK(); -} - -Status FieldDB::Recover() { - -} + if(*dbptr == nullptr){ + return Status::NotSupported(name, "new a fieldDb first\n"); + } -FieldDB::FieldDB(const Options& options,const std::string& name) { Status status; - status = Open(options, name+"_indexDB", &indexDB); - if(!status.ok()) return; - status = Open(options, name+"_kvDB", &kvDB); - if(!status.ok()) return; - status = Open(options, name+"_metaDB", &metaDB); - if(!status.ok()) return; - - Recover(); + DB *indexdb, *kvdb, *metadb; + status = Open(options, name+"_indexDB", &indexdb); + if(!status.ok()) return status; + + status = Open(options, name+"_kvDB", &kvdb); + if(!status.ok()) return status; + status = Open(options, name+"_metaDB", &metadb); + if(!status.ok()) return status; + + (*dbptr)->indexDB_ = indexdb; + (*dbptr)->kvDB_ = kvdb; + (*dbptr)->metaDB_ = metadb; + (*dbptr)->dbname_ = name; + + status = (*dbptr)->Recover(); + return status; } +// todo +Status FieldDB::Recover() { + // + return Status::OK(); +} Status FieldDB::Put(const WriteOptions &options, const Slice &key, const Slice &value) { - return kvDB->Put(options, key, value); + return kvDB_->Put(options, key, value); } // TODO:需要对是否进行index更新做处理 -Status FieldDB::PutFields(const WriteOptions &, const Slice &key, const FieldArray &fields) { - return Status::OK(); +Status FieldDB::PutFields(const WriteOptions &Options, + const Slice &key, const FieldArray &fields) { + // + return kvDB_->PutFields(Options, key, fields); } +// todo: 删除有索引的key时indexdb也要同步 Status FieldDB::Delete(const WriteOptions &options, const Slice &key) { - return kvDB->Delete(options, key); + // + return kvDB_->Delete(options, key); } // TODO:根据updates里面的东西,要对是否需要更新index进行分别处理 Status FieldDB::Write(const WriteOptions &options, WriteBatch *updates) { @@ -52,56 +65,44 @@ Status FieldDB::Write(const WriteOptions &options, WriteBatch *updates) { } Status FieldDB::Get(const ReadOptions &options, const Slice &key, std::string *value) { - return kvDB->Get(options, key, value); + return kvDB_->Get(options, key, value); } Status FieldDB::GetFields(const ReadOptions &options, const Slice &key, FieldArray *fields) { - std::string value; - Status status; - status = kvDB->Get(options, key, &value); - if(status.ok() == false) return status; - fields = ParseValue(value); - return status; + return kvDB_->GetFields(options, key, fields); + } std::vector FieldDB::FindKeysByField(Field &field) { - std::vector result; - auto iter = kvDB->NewIterator(ReadOptions()); - for(iter->SeekToFirst();iter->Valid();iter->Next()) { - InternalFieldArray fields(iter->value()); - if(fields.HasField(field)) { - result.push_back(iter->key().ToString()); - } - } - return result; + return kvDB_->FindKeysByField(field); } Iterator * FieldDB::NewIterator(const ReadOptions &options) { - return kvDB->NewIterator(options); + return kvDB_->NewIterator(options); } // TODO:使用统一seq进行snapshot管理 const Snapshot * FieldDB::GetSnapshot() { - return kvDB->GetSnapshot(); + return kvDB_->GetSnapshot(); } // TODO:同上 void FieldDB::ReleaseSnapshot(const Snapshot *snapshot) { - kvDB->ReleaseSnapshot(snapshot); + kvDB_->ReleaseSnapshot(snapshot); } bool FieldDB::GetProperty(const Slice &property, std::string *value) { - return kvDB->GetProperty(property, value) | indexDB->GetProperty(property, value); + return kvDB_->GetProperty(property, value) | indexDB_->GetProperty(property, value); } void FieldDB::GetApproximateSizes(const Range *range, int n, uint64_t *sizes) { uint64_t temp = 0; - kvDB->GetApproximateSizes(range, n, sizes); - indexDB->GetApproximateSizes(range, n, &temp); + kvDB_->GetApproximateSizes(range, n, sizes); + indexDB_->GetApproximateSizes(range, n, &temp); *sizes += temp; } void FieldDB::CompactRange(const Slice *begin, const Slice *end) { - kvDB->CompactRange(begin, end); + kvDB_->CompactRange(begin, end); } } // end of namespace \ No newline at end of file diff --git a/fielddb/field_db.h b/fielddb/field_db.h index 411f564..6a3c845 100644 --- a/fielddb/field_db.h +++ b/fielddb/field_db.h @@ -1,3 +1,7 @@ +# ifndef FIELD_DB_H +# define FIELD_DB_H + +#include "port/port_stdcxx.h" #include "db/db_impl.h" #include #include @@ -7,16 +11,16 @@ #include "leveldb/options.h" #include "leveldb/slice.h" #include "leveldb/status.h" -#include "port/port_stdcxx.h" + #include "fielddb/request.h" namespace fielddb { using namespace leveldb; -class FieldDB : leveldb::DB { +class FieldDB : DB { public: - FieldDB() = default; - FieldDB(const Options& options,const std::string& name); -/*lab1的要求*/ + //用的时候必须FieldDB *db = new FieldDB()再open,不能像之前一样DB *db + FieldDB() : indexDB_(nullptr), kvDB_(nullptr), metaDB_(nullptr) {}; +/*lab1的要求,作为db派生类要实现的虚函数*/ Status Put(const WriteOptions &options, const Slice &key, const Slice &value) override; Status PutFields(const WriteOptions &, const Slice &key, const FieldArray &fields) override; Status Delete(const WriteOptions &options, const Slice &key) override; @@ -31,29 +35,32 @@ public: void GetApproximateSizes(const Range *range, int n, uint64_t *sizes) override; void CompactRange(const Slice *begin, const Slice *end) override; /*与索引相关*/ - bool CreateIndexOnField(const std::string& field_name); - bool DeleteIndex(std::string &field_name); - std::vector QueryByIndex(Field &field); + Status CreateIndexOnField(const std::string& field_name); + Status DeleteIndex(std::string &field_name); + std::vector QueryByIndex(Field &field, Status *s); - static Status OpenFieldDB(const Options& options,const std::string& name,DB** dbptr); + static Status OpenFieldDB(const Options& options,const std::string& name,FieldDB** dbptr); private: //根据metaDB的内容进行恢复 Status Recover(); private: - leveldb::DB *metaDB; - leveldb::DB *indexDB; - leveldb::DB *kvDB; + std::string dbname_; + + leveldb::DB *metaDB_; + leveldb::DB *indexDB_; + leveldb::DB *kvDB_; enum IndexStatus{ Creating, Deleting, Exist }; - std::map index; - port::Mutex _mutex; // mutex for taskqueue - std::deque taskqueue; + std::map index_; + leveldb::port::Mutex mutex_; // mutex for taskqueue + std::deque taskqueue_; }; -} // end of namespace \ No newline at end of file +} // end of namespace +# endif \ No newline at end of file diff --git a/test/lab1_test.cc b/test/lab1_test.cc index 94b0ee4..7c9a50d 100644 --- a/test/lab1_test.cc +++ b/test/lab1_test.cc @@ -1,46 +1,104 @@ #include "gtest/gtest.h" -#include "leveldb/env.h" -#include "leveldb/db.h" -using namespace leveldb; -using Field = std::pair; // field_name:field_value -using FieldArray = std::vector>; +// #include "leveldb/env.h" +// #include "leveldb/db.h" +#include "fielddb/field_db.h" +using namespace fielddb; -Status OpenDB(std::string dbName, DB **db) { +constexpr int value_size = 2048; +constexpr int data_size = 128 << 20; +std::vector cities = { + "Beijing", "Shanghai", "Guangzhou", "Shenzhen", "Hangzhou", + "Chengdu", "Chongqing", "Wuhan", "Suzhou", "Tianjin" + }; +std::vector shanghaiKeys; + +Status OpenDB(std::string dbName, FieldDB **db) { Options options; options.create_if_missing = true; - return DB::Open(options, dbName, db); + return FieldDB::OpenFieldDB(options, dbName, db); } -TEST(TestLab1, Basic) { - DestroyDB("testdb",Options()); - DB *db; +void ClearDB(FieldDB *db){ + //destroy和恢复没做前先用这个清理数据库,否则跑不同的数据多做几次测试会污染 + WriteOptions writeOptions; + int key_num = data_size / value_size; + for (int i = 0; i < key_num; i++) { + int key_ = i+1; + std::string key = std::to_string(key_); + Status s = db->Delete(WriteOptions(), key); + ASSERT_TRUE(s.ok()); + } +} - if(OpenDB("testdb", &db).ok() == false) { - std::cerr << "open db failed" << std::endl; - abort(); - } - std::string key = "k_1"; - +void InsertFieldData(FieldDB *db) { + WriteOptions writeOptions; + int key_num = data_size / value_size; + srand(0); + + for (int i = 0; i < key_num; i++) { + int randThisTime = rand(); //确保读写一个循环只rand一次,否则随机序列会不一致 + int key_ = randThisTime % key_num+1; + std::string key = std::to_string(key_); + + std::string name = "customer#" + std::to_string(key_); + std::string address = cities[randThisTime % cities.size()]; FieldArray fields = { - {"name", "Customer#000000001"}, - {"address", "IVhzIApeRb"}, - {"phone", "25-989-741-2988"} + {"name", name}, + {"address", address} }; + if (address == "Shanghai") { + shanghaiKeys.push_back(key); + } + Status s = db->PutFields(WriteOptions(), key, fields); + ASSERT_TRUE(s.ok()); + } +} - // 序列化并插入 - db->PutFields(WriteOptions(), key, fields); - - // 读取并反序列化 +void GetFieldData(FieldDB *db) { + ReadOptions readOptions; + int key_num = data_size / value_size; + + // 点查 + srand(0); + for (int i = 0; i < 100; i++) { + int randThisTime = rand(); + int key_ = randThisTime % key_num+1; + std::string key = std::to_string(key_); FieldArray fields_ret; - db->GetFields(ReadOptions(), key, &fields_ret); - // ASSERT_EQ(fields, fields_ret); 顺序不一样 + Status s = db->GetFields(readOptions, key, &fields_ret); + ASSERT_TRUE(s.ok()); for (const Field& pairs : fields_ret) { - ASSERT_NE(std::find(fields.begin(), fields.end(), pairs), fields.end()); + if (pairs.first == "name"){ + + } else if (pairs.first == "address"){ + std::string city = pairs.second; + ASSERT_NE(std::find(cities.begin(), cities.end(), city), cities.end()); + } else assert(false); } + } +} - //todo - Field field = {"name", "Customer#000000001"}; - std::vector resKeys = db->FindKeysByField(field); +void findKeysByCity(FieldDB *db) { + Field field = {"address", "Shanghai"}; + std::vector resKeys = db->FindKeysByField(field); + // std::cout << shanghaiKeys.size() << " " << resKeys.size() << std::endl; + for (const std::string &key : resKeys){ + ASSERT_NE(std::find(shanghaiKeys.begin(), shanghaiKeys.end(), key), shanghaiKeys.end()); + } +} + +TEST(TestLab1, Basic) { + // DestroyDB("testdb",Options()); + FieldDB *db = new FieldDB(); + + if(OpenDB("testdb", &db).ok() == false) { + std::cerr << "open db failed" << std::endl; + abort(); + } + // ClearDB(db); + InsertFieldData(db); + GetFieldData(db); + findKeysByCity(db); } diff --git a/util/serialize_value.cc b/util/serialize_value.cc index a1bdb08..b93d8b7 100644 --- a/util/serialize_value.cc +++ b/util/serialize_value.cc @@ -36,8 +36,8 @@ FieldArray *ParseValue(const std::string& value_str){ } else { std::cout << "name and val not match!" << std::endl; } - nameSlice = Slice(); - valSlice = Slice(); + nameSlice.clear(); + valSlice.clear(); } return res; } diff --git a/util/serialize_value.h b/util/serialize_value.h index ff6e68c..a1ca30a 100644 --- a/util/serialize_value.h +++ b/util/serialize_value.h @@ -16,41 +16,44 @@ FieldArray *ParseValue(const std::string& value_str); class InternalFieldArray { public: -using FieldMap = std::map; + using FieldMap = std::map; -InternalFieldArray(const FieldArray &fields, bool to_map = false):fields(fields),isMapped(false) { - if(to_map) Map(); -} + InternalFieldArray(const FieldArray &fields, bool to_map = false): + fields(fields),isMapped(false) { + if(to_map) Map(); + } -InternalFieldArray(const std::string& value_str) { - Slice valueSlice(value_str); - Slice nameSlice,valSlice; - while(GetLengthPrefixedSlice(&valueSlice, &nameSlice)) { - if(GetLengthPrefixedSlice(&valueSlice, &valueSlice)) { - map[nameSlice.ToString()] = valueSlice.ToString(); - } else { - std::cout << "name and val not match!" << std::endl; + InternalFieldArray(const Slice value_slice) { + Slice valueSlice = value_slice; + Slice nameSlice, valSlice; + while(GetLengthPrefixedSlice(&valueSlice, &nameSlice)) { + if(GetLengthPrefixedSlice(&valueSlice, &valSlice)) { + map[nameSlice.ToString()] = valSlice.ToString(); + } else { + std::cout << "name and val not match!" << std::endl; + } + nameSlice.clear(); + valSlice.clear(); } - nameSlice.clear(); - valSlice.clear(); + isMapped = true; } -} -InternalFieldArray(const Slice& slice):leveldb::InternalFieldArray(slice.ToString()) {} + InternalFieldArray(const std::string& value_str) + :leveldb::InternalFieldArray(Slice(value_str)) {} -//将vector变为用map存 -void Map(); + //将vector变为用map存 + void Map(); -std::string Serialize(); + std::string Serialize(); -bool HasField(const Field& field); + bool HasField(const Field& field); private: -bool isMapped; -const FieldArray fields; -FieldMap map; + bool isMapped; + const FieldArray fields; + FieldMap map; }; } #endif \ No newline at end of file From 099d8183448d6fe2d38f21bfe768b1f5651324ae Mon Sep 17 00:00:00 2001 From: augurier <14434658+augurier@user.noreply.gitee.com> Date: Fri, 6 Dec 2024 16:57:00 +0800 Subject: [PATCH 14/14] =?UTF-8?q?lab2=E9=83=A8=E5=88=86=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=EF=BC=88=E6=B2=A1=E6=9C=89=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E5=92=8Cmetadb)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 +- db/db_impl.cc | 2 +- fielddb/encode_index.h | 37 +++++++++++ fielddb/field_db.cpp | 86 ++++++++++++++++++++++++++ fielddb/field_db.h | 8 ++- test/basic_function_test.cc | 146 ++++++++++++++++++++++++++++++++++++++++++++ test/lab1_test.cc | 109 --------------------------------- util/serialize_value.cc | 18 +++++- util/serialize_value.h | 2 +- 9 files changed, 294 insertions(+), 116 deletions(-) create mode 100644 fielddb/encode_index.h create mode 100644 test/basic_function_test.cc delete mode 100644 test/lab1_test.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 31536ca..b70e461 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -523,6 +523,6 @@ if(LEVELDB_INSTALL) endif(LEVELDB_INSTALL) add_executable(lab1_test - "${PROJECT_SOURCE_DIR}/test/lab1_test.cc" + "${PROJECT_SOURCE_DIR}/test/basic_function_test.cc" ) target_link_libraries(lab1_test PRIVATE leveldb gtest) diff --git a/db/db_impl.cc b/db/db_impl.cc index 4a7d25c..49db131 100644 --- a/db/db_impl.cc +++ b/db/db_impl.cc @@ -1177,7 +1177,7 @@ std::vector DBImpl::FindKeysByField(Field &field){ std::vector result; auto iter = NewIterator(ReadOptions()); for(iter->SeekToFirst();iter->Valid();iter->Next()) { - std::string k = iter->key().ToString(); + // std::string k = iter->key().ToString(); InternalFieldArray fields(iter->value()); if(fields.HasField(field)) { result.push_back(iter->key().ToString()); diff --git a/fielddb/encode_index.h b/fielddb/encode_index.h new file mode 100644 index 0000000..eceeab4 --- /dev/null +++ b/fielddb/encode_index.h @@ -0,0 +1,37 @@ +#ifndef ENCODE_INDEX_H +#define ENCODE_INDEX_H + +#include "leveldb/slice.h" +#include "util/coding.h" +namespace fielddb{ +using namespace leveldb; + + +struct ParsedInternalIndexKey { //key : {name : val} + Slice user_key_; + Slice name_; + Slice val_; + + ParsedInternalIndexKey() {} // Intentionally left uninitialized (for speed) + ParsedInternalIndexKey(const Slice& user_key, const Slice& name, const Slice& val) + : user_key_(user_key), name_(name), val_(val) {} +}; + +bool ParseInternalIndexKey(Slice input, ParsedInternalIndexKey* result); +void AppendIndexKey(std::string* result, const ParsedInternalIndexKey& key); + + +inline bool ParseInternalIndexKey(Slice input, ParsedInternalIndexKey* result){ + return GetLengthPrefixedSlice(&input, &result->name_) && + GetLengthPrefixedSlice(&input, &result->val_) && + GetLengthPrefixedSlice(&input, &result->user_key_); +} + +inline void AppendIndexKey(std::string* result, const ParsedInternalIndexKey& key){ + PutLengthPrefixedSlice(result, key.name_); + PutLengthPrefixedSlice(result, key.val_); + PutLengthPrefixedSlice(result, key.user_key_); +} + +} +#endif \ No newline at end of file diff --git a/fielddb/field_db.cpp b/fielddb/field_db.cpp index d867411..637046e 100644 --- a/fielddb/field_db.cpp +++ b/fielddb/field_db.cpp @@ -6,7 +6,9 @@ #include "leveldb/env.h" #include "leveldb/options.h" #include "leveldb/status.h" +#include "db/write_batch_internal.h" #include "util/serialize_value.h" +#include "fielddb/encode_index.h" namespace fielddb { using namespace leveldb; @@ -18,6 +20,7 @@ Status FieldDB::OpenFieldDB(const Options& options, return Status::NotSupported(name, "new a fieldDb first\n"); } + // Status status; DB *indexdb, *kvdb, *metadb; status = Open(options, name+"_indexDB", &indexdb); @@ -77,6 +80,89 @@ std::vector FieldDB::FindKeysByField(Field &field) { return kvDB_->FindKeysByField(field); } +std::vector> FieldDB::FindKeysAndValByFieldName ( + const std::string &fieldName){ + std::vector> result; + auto iter = kvDB_->NewIterator(ReadOptions()); + std::string val; + for(iter->SeekToFirst();iter->Valid();iter->Next()) { + InternalFieldArray fields(iter->value()); + val = fields.ValOfName(fieldName); + if(!val.empty()) { + result.push_back(std::make_pair(iter->key().ToString(), val)); + } + } + return result; +} + +Status FieldDB::CreateIndexOnField(const std::string& field_name) { + //taskQueue相关 + //写锁 是不是只需要给putfields设置一把锁就行 + + std::vector> keysAndVal = + FindKeysAndValByFieldName(field_name); + WriteBatch writeBatch; + Slice value = Slice(); + for (auto &kvPair : keysAndVal){ + std::string indexKey; + AppendIndexKey(&indexKey, + ParsedInternalIndexKey(kvPair.first, field_name, kvPair.second)); + writeBatch.Put(indexKey, value); + } + Status s = indexDB_->Write(WriteOptions(), &writeBatch); + if (!s.ok()) return s; + + index_[field_name] = Exist; + //唤醒taskqueue + +} + +Status FieldDB::DeleteIndex(const std::string &field_name) { + //taskQueue相关 + //写锁 + std::vector> keysAndVal = + FindKeysAndValByFieldName(field_name); + WriteBatch writeBatch; + for (auto &kvPair : keysAndVal){ + std::string indexKey; + AppendIndexKey(&indexKey, + ParsedInternalIndexKey(kvPair.first, field_name, kvPair.second)); + writeBatch.Delete(indexKey); + } + Status s = indexDB_->Write(WriteOptions(), &writeBatch); + if (!s.ok()) return s; + + index_.erase(field_name); + //唤醒taskqueue +} + +std::vector FieldDB::QueryByIndex(const Field &field, Status *s) { + if (index_.count(field.first) == 0 || index_[field.first] != Exist){ + *s = Status::NotFound(Slice()); + return std::vector(); + } + std::string indexKey; + AppendIndexKey(&indexKey, + ParsedInternalIndexKey(Slice(), field.first, field.second)); + Iterator *indexIterator = indexDB_->NewIterator(ReadOptions()); + indexIterator->Seek(indexKey); + + std::vector result; + for (; indexIterator->Valid(); indexIterator->Next()) { + ParsedInternalIndexKey iterKey; + if (ParseInternalIndexKey(indexIterator->key(), &iterKey)){ + if (iterKey.name_ == field.first && iterKey.val_ == field.second){ + result.push_back(iterKey.user_key_.ToString()); + continue; //查到说明在范围里,否则break + } + } + break; + } + + *s = Status::OK(); + return result; +} + Iterator * FieldDB::NewIterator(const ReadOptions &options) { return kvDB_->NewIterator(options); } diff --git a/fielddb/field_db.h b/fielddb/field_db.h index 6a3c845..5c7e8d5 100644 --- a/fielddb/field_db.h +++ b/fielddb/field_db.h @@ -36,8 +36,8 @@ public: void CompactRange(const Slice *begin, const Slice *end) override; /*与索引相关*/ Status CreateIndexOnField(const std::string& field_name); - Status DeleteIndex(std::string &field_name); - std::vector QueryByIndex(Field &field, Status *s); + Status DeleteIndex(const std::string &field_name); + std::vector QueryByIndex(const Field &field, Status *s); static Status OpenFieldDB(const Options& options,const std::string& name,FieldDB** dbptr); @@ -57,10 +57,12 @@ private: Deleting, Exist }; - std::map index_; + std::map index_; leveldb::port::Mutex mutex_; // mutex for taskqueue std::deque taskqueue_; + std::vector> FindKeysAndValByFieldName ( + const std::string &fieldName); }; } // end of namespace # endif \ No newline at end of file diff --git a/test/basic_function_test.cc b/test/basic_function_test.cc new file mode 100644 index 0000000..4915fe4 --- /dev/null +++ b/test/basic_function_test.cc @@ -0,0 +1,146 @@ +#include "gtest/gtest.h" +// #include "leveldb/env.h" +// #include "leveldb/db.h" +#include "fielddb/field_db.h" +using namespace fielddb; + +constexpr int value_size = 2048; +constexpr int data_size = 128 << 20; +std::vector cities = { + "Beijing", "Shanghai", "Guangzhou", "Shenzhen", "Hangzhou", + "Chengdu", "Chongqing", "Wuhan", "Suzhou", "Tianjin" + }; +std::vector shanghaiKeys; + +Status OpenDB(std::string dbName, FieldDB **db) { + Options options; + options.create_if_missing = true; + return FieldDB::OpenFieldDB(options, dbName, db); +} + +void ClearDB(FieldDB *db){ + //destroy和恢复没做前先用这个清理数据库,否则跑不同的数据多做几次测试会污染 + WriteOptions writeOptions; + int key_num = data_size / value_size; + for (int i = 0; i < key_num; i++) { + int key_ = i+1; + std::string key = std::to_string(key_); + Status s = db->Delete(WriteOptions(), key); + ASSERT_TRUE(s.ok()); + } +} + +void InsertFieldData(FieldDB *db) { + WriteOptions writeOptions; + int key_num = data_size / value_size; + srand(0); + + for (int i = 0; i < key_num; i++) { + int randThisTime = rand(); //确保读写一个循环只rand一次,否则随机序列会不一致 + int key_ = randThisTime % key_num+1; + std::string key = std::to_string(key_); + + std::string name = "customer#" + std::to_string(key_); + std::string address = cities[randThisTime % cities.size()]; + FieldArray fields = { + {"name", name}, + {"address", address} + }; + if (address == "Shanghai") { + shanghaiKeys.push_back(key); + } + Status s = db->PutFields(WriteOptions(), key, fields); + ASSERT_TRUE(s.ok()); + } +} + +void GetFieldData(FieldDB *db) { + ReadOptions readOptions; + int key_num = data_size / value_size; + + // 点查 + srand(0); + for (int i = 0; i < 100; i++) { + int randThisTime = rand(); + int key_ = randThisTime % key_num+1; + std::string key = std::to_string(key_); + FieldArray fields_ret; + Status s = db->GetFields(readOptions, key, &fields_ret); + ASSERT_TRUE(s.ok()); + for (const Field& pairs : fields_ret) { + if (pairs.first == "name"){ + + } else if (pairs.first == "address"){ + std::string city = pairs.second; + ASSERT_NE(std::find(cities.begin(), cities.end(), city), cities.end()); + } else assert(false); + } + } +} + +void findKeysByCity(FieldDB *db) { + Field field = {"address", "Shanghai"}; + std::vector resKeys = db->FindKeysByField(field); + std::cout << shanghaiKeys.size() << " " << resKeys.size() << std::endl; + for (const std::string &key : resKeys){ + ASSERT_NE(std::find(shanghaiKeys.begin(), shanghaiKeys.end(), key), shanghaiKeys.end()); + } +} + +void findKeysByCityIndex(FieldDB *db, bool expect) { + Field field = {"address", "Shanghai"}; + Status s; + std::vector resKeys = db->QueryByIndex(field, &s); + if (expect) ASSERT_TRUE(s.ok()); + else { + ASSERT_TRUE(s.IsNotFound()); + return; + } + std::cout << shanghaiKeys.size() << " " << resKeys.size() << std::endl; + for (const std::string &key : resKeys){ + ASSERT_NE(std::find(shanghaiKeys.begin(), shanghaiKeys.end(), key), shanghaiKeys.end()); + } +} + +TEST(TestLab1, Basic) { + // DestroyDB("testdb",Options()); + FieldDB *db = new FieldDB(); + + if(OpenDB("testdb", &db).ok() == false) { + std::cerr << "open db failed" << std::endl; + abort(); + } + // ClearDB(db); + InsertFieldData(db); + GetFieldData(db); + findKeysByCity(db); + delete db; +} + +TEST(TestLab2, Basic) { + //destroy + FieldDB *db = new FieldDB(); + + if(OpenDB("testdb2", &db).ok() == false) { + std::cerr << "open db failed" << std::endl; + abort(); + } + // ClearDB(db); + shanghaiKeys.clear(); + InsertFieldData(db); + // GetFieldData(db); + // findKeysByCity(db); + db->CreateIndexOnField("address"); + findKeysByCityIndex(db, true); + db->DeleteIndex("address"); + findKeysByCityIndex(db, false); + + delete db; +} + + +int main(int argc, char** argv) { + // All tests currently run with the same read-only file limits. + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/test/lab1_test.cc b/test/lab1_test.cc deleted file mode 100644 index 7c9a50d..0000000 --- a/test/lab1_test.cc +++ /dev/null @@ -1,109 +0,0 @@ -#include "gtest/gtest.h" -// #include "leveldb/env.h" -// #include "leveldb/db.h" -#include "fielddb/field_db.h" -using namespace fielddb; - -constexpr int value_size = 2048; -constexpr int data_size = 128 << 20; -std::vector cities = { - "Beijing", "Shanghai", "Guangzhou", "Shenzhen", "Hangzhou", - "Chengdu", "Chongqing", "Wuhan", "Suzhou", "Tianjin" - }; -std::vector shanghaiKeys; - -Status OpenDB(std::string dbName, FieldDB **db) { - Options options; - options.create_if_missing = true; - return FieldDB::OpenFieldDB(options, dbName, db); -} - -void ClearDB(FieldDB *db){ - //destroy和恢复没做前先用这个清理数据库,否则跑不同的数据多做几次测试会污染 - WriteOptions writeOptions; - int key_num = data_size / value_size; - for (int i = 0; i < key_num; i++) { - int key_ = i+1; - std::string key = std::to_string(key_); - Status s = db->Delete(WriteOptions(), key); - ASSERT_TRUE(s.ok()); - } -} - -void InsertFieldData(FieldDB *db) { - WriteOptions writeOptions; - int key_num = data_size / value_size; - srand(0); - - for (int i = 0; i < key_num; i++) { - int randThisTime = rand(); //确保读写一个循环只rand一次,否则随机序列会不一致 - int key_ = randThisTime % key_num+1; - std::string key = std::to_string(key_); - - std::string name = "customer#" + std::to_string(key_); - std::string address = cities[randThisTime % cities.size()]; - FieldArray fields = { - {"name", name}, - {"address", address} - }; - if (address == "Shanghai") { - shanghaiKeys.push_back(key); - } - Status s = db->PutFields(WriteOptions(), key, fields); - ASSERT_TRUE(s.ok()); - } -} - -void GetFieldData(FieldDB *db) { - ReadOptions readOptions; - int key_num = data_size / value_size; - - // 点查 - srand(0); - for (int i = 0; i < 100; i++) { - int randThisTime = rand(); - int key_ = randThisTime % key_num+1; - std::string key = std::to_string(key_); - FieldArray fields_ret; - Status s = db->GetFields(readOptions, key, &fields_ret); - ASSERT_TRUE(s.ok()); - for (const Field& pairs : fields_ret) { - if (pairs.first == "name"){ - - } else if (pairs.first == "address"){ - std::string city = pairs.second; - ASSERT_NE(std::find(cities.begin(), cities.end(), city), cities.end()); - } else assert(false); - } - } -} - -void findKeysByCity(FieldDB *db) { - Field field = {"address", "Shanghai"}; - std::vector resKeys = db->FindKeysByField(field); - // std::cout << shanghaiKeys.size() << " " << resKeys.size() << std::endl; - for (const std::string &key : resKeys){ - ASSERT_NE(std::find(shanghaiKeys.begin(), shanghaiKeys.end(), key), shanghaiKeys.end()); - } -} - -TEST(TestLab1, Basic) { - // DestroyDB("testdb",Options()); - FieldDB *db = new FieldDB(); - - if(OpenDB("testdb", &db).ok() == false) { - std::cerr << "open db failed" << std::endl; - abort(); - } - // ClearDB(db); - InsertFieldData(db); - GetFieldData(db); - findKeysByCity(db); -} - - -int main(int argc, char** argv) { - // All tests currently run with the same read-only file limits. - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} \ No newline at end of file diff --git a/util/serialize_value.cc b/util/serialize_value.cc index b93d8b7..562360b 100644 --- a/util/serialize_value.cc +++ b/util/serialize_value.cc @@ -73,6 +73,22 @@ bool InternalFieldArray::HasField(const Field& field) { return std::find(fields.begin(),fields.end(),field) != fields.end(); } - +std::string InternalFieldArray::ValOfName(const std::string &name) { + if(isMapped) { + if(map.count(name)) { + return map[name]; + } + return std::string(); + } + + for (auto iter = fields.begin(); iter != fields.end(); iter++){ + if (iter->first == name) { + return iter->second; + } else if (iter->first > name) { + return std::string(); + } + } + return std::string(); +} } \ No newline at end of file diff --git a/util/serialize_value.h b/util/serialize_value.h index a1ca30a..b769fb8 100644 --- a/util/serialize_value.h +++ b/util/serialize_value.h @@ -48,7 +48,7 @@ public: std::string Serialize(); bool HasField(const Field& field); - + std::string ValOfName(const std::string& name); private: bool isMapped;