65.9K
CodeProject 正在变化。 阅读更多。
Home

C++ 对象关系映射 (ORM)- Eating the Bun - 第 1 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.15/5 (20投票s)

2016年5月17日

BSD

10分钟阅读

viewsIcon

106596

downloadIcon

853

在 SQL 数据库之上为 C++ 创建一个简单的 ORM

引言

对象关系映射 (Object Relational Mapping) 是将面向对象的语言(如 C++)中的数据类型映射到关系型系统(如 SQL)中的过程。那么挑战在哪里呢?C++ 有不同类型的基本类型,例如 intcharfloatdouble 以及它们的变体。因此,将所有这些映射到实际的 SQL 类型是一项真正的挑战。可能存在也可能不存在与 C++ 类型相似的精确类型。例如,对于 float,C++ 和 SQL 可能支持不同类型的标准。因此,有不同的工具来完成这项工作。市场上也有许多成熟的库。 ODB 就是其中一个非常不错的库。

为了帮助我日常工作,我创建了一个简单的 C++ 库,名为 Bun

有什么新功能?

  • * Bun 1.5.0 将带 vector 的对象转换为 JSON 和 Msgpack,并从包含 vector 的 JSON 创建对象。注意:尚不包含 vector 的持久化。仍在开发中。
  • Bun 1.4.0 支持将对象转换为 JSON 并从 JSON 创建对象。它能够将 对象转换为 Message pack 并从 message pack 构建对象。
  • Bun 1.3 支持对象懒惰迭代基于范围的 for 循环支持。键值存储也支持同样的功能。
  • Bun 1.2 支持嵌入式键值存储。但默认情况下,键值存储基于 Unqlite

特点

  • 易于使用
  • 使用纯 C++ 对象 (POCO)
  • 对象持久化 - 您可以直接持久化 C++ 对象
  • 非侵入性 - 您无需修改类即可使其持久化
  • 在纯 C++ 中指定约束
  • 持久化嵌套对象
  • EDSL 对象查询语言 (无需 SQL 查询)
  • 编译时 EDSL 语法检查以确保类型安全 - 在执行开始前捕获错误
  • 多数据库支持 - SQLite、Postgres、MySQL
  • 易于使用的嵌入式键值存储
  • 将 C++ 对象转换为 JSON 并从 JSON 创建 C++ 对象。
  • 将 C++ 对象转换为 Message Pack 并从 Message Pack 创建 C++ 对象。
  • STL 友好。这些是常规的 C++ 对象。因此可以用于 C++ STL 算法。

谁在使用 Bun?

本节介绍谁在使用 Bun 以及在什么上下文中。如果您发现 Bun 有用并正在使用它,请告诉我,我会将其添加到此处。

  1. 与 PI 的一次小冒险。

背景

在我的许多工具应用程序中,我使用 SQLite 作为主要数据库。每次使用 SQL 查询时,我都觉得在与实际用例无关的任务上浪费了很多精力。因此,我想创建一个用于这些类型自动映射的框架。该库的标准如下:

  1. 可免费用于任何类型的项目 (BSD 许可证)
  2. 易于使用 (无需 SQL 查询知识)
  3. 提供字段的唯一键约束等约束
  4. 无需 SQL 查询。EDSL 查询。
  5. 非侵入性
  6. 表达性强
  7. 应该是 C++ 的 DSL,因此查询语法可以由 C++ 编译器检查
  8. 无需自定义编译器 (C++11 及以上)
  9. 高性能
  10. 支持多种数据库后端,如 SQLite、Postgres、MySQL
  11. 易于使用的嵌入式键值存储

所有这些目前尚未实现。最终,我将解决所有这些问题。目前,只开发了库的基本版本。

Using the Code

Bun 对象存储接口

在深入了解其内部细节之前,在这第一篇文章中,让我们看看如何使用该库。

Bun 拥有 BSD 3-Clause 许可证。它依赖于以下开源和免费库:

  1. boost (我已在 1.61 版本上测试过,Boost 许可证)
  2. fmt (小巧、安全、快速的格式化库,BSD 许可证)
  3. spdlog (快速 C++ 日志记录,MIT 许可证)
  4. SQLite (独立、无服务器、零配置、事务性 SQL 数据库引擎,公共领域)
  5. SOCI (C++ 数据库层,BSL 许可证)
  6. JSON for modern C++ (C++ JSON 和 Message pack 工具,MIT 许可证)
  7. Rapid JSON (快速 C++ JSON 库,参见许可证)

GitHub 页面包含所有必需的依赖项。它还包含一个 Visual Studio 2015 解决方案文件,以便于使用。不包含 Boost 和 SOCI。要下载项目,请将 boost 头文件放在“include”目录下或更改解决方案文件中的路径。 构建 SOCI (使用 cmake 非常容易构建) 并将库链接到 Bun。

#include "blib/bun/bun.hpp"

namespace test {
  // Class that needs to be persisted
  struct Person {
    std::string name;
    std::string uname;
    int age;
    float height;
  };
}

/// @class Child 
struct Child {
    int cf1;
    Child(const int cf = -1) : cf1(cf) {}
    Child& operator=(const int i) {
        cf1 = i;
        return *this;
    }
};

/// @class Paret
struct Parent {
    int f1;
    std::string f2;
    // Nested object
    Child f3;
    Parent() :f1(-1), f2("-1"), f3(-1) {}
};

// Both should be persistable
SPECIALIZE_BUN_HELPER((Child, cf1));
SPECIALIZE_BUN_HELPER((Parent, f1, f2, f3));

/////////////////////////////////////////////////
/// Generate the database bindings at compile time.
/////////////////////////////////////////////////
SPECIALIZE_BUN_HELPER( (test::Person, name, uname, age, height) );

int main() {
  namespace bun = blib::bun;
  namespace query = blib::bun::query;

  // Connect the db. If the db is not there it will be created.
  // It should include the whole path
  // For SQLite
  //bun::connect( "objects.db" );
  // For PostGres
  bun::connect("postgresql:///postgres?user=postgres&password=postgres");
  // Get the fields of the Person. This will be useful in specifying constraints and also
  // querying the object.
  using PersonFields = query::F<test::Person>;

  // Generate the configuration. By default it does nothing.
  blib::bun::Configuration<test::Person> person_config;
  // This is a unique key constraints that is applied.
  // Constraint are applied globally. They need to be set before the
  // execution of the create schema statement
  // The syntax is Field name = Constraint
  // We can club multiple Constraints as below in the same statement.
  // There is no need for multiple set's to be called. This is how
  // We can chain different constraints in the same statement
  person_config.set(PersonFields::name = blib::bun::unique_constraint)
                   (PersonFields::uname = blib::bun::unique_constraint);
  
  // Create the schema. We can create the schema multiple times. If its already created
  // it will be safely ignored. The constraints are applied to the table.
  // Adding constraints don't have effect if the table is already created
  bun::createSchema<test::Person>();
  
  // Start transaction
  bun::Transaction t;
  // Create some entries in the database
  for (int i = 1; i < 1000; ++i) {
    // PRef is a reference to the persistent object.
    // PRef keeps the ownership of the memory. Release the memory when it is destroyed.
    // Internally it holds the object in a unique_ptr
    // PRef also has a oid associated with the object
    bun::PRef<test::Person> p = new test::Person;

    // Assign the members values
    p->age = i + 10;
    p->height = 5.6;
    p->name = fmt::format( "Brainless_{}", i );
    // Persist the object and get a oid for the persisted object.
    const bun::SimpleOID oid = p.persist();

    //Getting the object from db using oid.
    bun::PRef<test::Person> p1( oid );
  }
  // Commit the transaction
  t.commit();

  // To get all the object oids of a particular object.
  // person_oids is a vector of type std::vector<blib::bun<>SimpleOID<test::Person>>
  const auto person_oids = bun::getAllOids<test::Person>();

  // To get the objects of a particular type
  // std::vector<blib::bun::Pref<test::Person>>
  const auto person_objs = bun::getAllObjects<test::Person>();

  // EDSL QUERY LANGUAGE ----------------------
  // Powerful EDSL object query syntax that is checked for syntax at compile time.
  // The compilation fails at the compile time with a message "Syntax error in Bun Query"
  using FromPerson = query::From<test::Person>;
  FromPerson fromPerson;
  // Grammar are checked for validity of syntax at compile time itself.
  // Currently only &&, ||, <, <=, >, >=, ==, != are supported. They have their respective meaning
  // Below is a valid query grammar
  auto valid_query = PersonFields::age > 10 && PersonFields::name != "Brainless_0";
  std::cout << "Valid Grammar?: " << query::IsValidQuery<decltype(valid_query)>::value << std::endl;

  // Oops + is not a valid grammar
  auto invalid_query = PersonFields::age + 10 && 
  PersonFields::name != "Brainless_0";
  std::cout << "Valid Grammar?: " << 
  query::IsValidQuery<decltype(invalid_query)>::value << std::endl;

  // Now let us execute the query.
  // The where function also checks for the validity of the query, and fails at compile time
  const auto objs = fromPerson.where( valid_query ).where( valid_query ).objects();
  // Can even use following way of query
  // As you see we can join queries 
  const auto q = PersonFields::age > 21 && PersonFields::name == "test";
  const auto objs_again = FromPerson().where( q ).objects();
  const auto objs_again_q = FromPerson().where( PersonFields::age > 21 
  && PersonFields::name == "test" ).objects()
  // Not going to compile if you enable the below line. 
  // Will get the "Syntax error in Bun Query" compile time message.
  // const auto objs1 = FromPerson.where( invalid_query ).objects();

  // Check the query generated. It does not give the sql query.
  std::cout << fromPerson.query() << std::endl;

  // Support for Nested object persistence and retrieval
  blib::bun::createSchema<Child>();
  blib::bun::createSchema<Parent>();
  std::cout << "How many objects to insert? " << std::endl;
  int count = 0;
  std::cin >> count;
  for (int i = 0; i < count; ++i) {
      blib::bun::l().info("===============Start===================");
      blib::bun::PRef<Parent> p = new Parent;
      p->f1 = i;
      p->f2 = i % 2 ? "Delete Me" : "Do not Delete Me";
      p->f3 = 10 * i;
      // Persists the Parent and the Nested Child
      p.persist();
      std::cout << "Added to db: \n" << p.toJson() << std::endl;
      blib::bun::l().info("===============End===================\n");
    }
    
    std::cout << "Get all objects and show" << std::endl;
    auto parents = blib::bun::getAllObjects<Parent>();
    // Iterate and delete the Parent and the nested Child
    // Here p is a PRef type. We can modify the object and persist 
    // the changes if needed.
    for (auto p : parents) {
        std::cout << p.toJson() << std::endl;
        p.del();
    }

  return 0;
}

这就是我们如何持久化对象。运行此代码后,SQLite 数据库中会创建以下列表:

现在让我们仔细看看其中的一些元素。架构的 DDL 如下:

CREATE TABLE "test::Person" (object_id INTEGER NOT NULL, name TEXT, age INTEGER, height REAL);

此架构由库内部创建。我仅在此显示以供参考。

数据如下:

持久化存储

oid 名称 age height
90023498019372 Brainless_1 11 5.6
90023527619226 Brainless_2 12 5.6
90023537497149 Brainless_3 13 5.6
90023553459526 Brainless_4 14 5.6
90023562946990 Brainless_5 15 5.6

基于范围的迭代

Bun 还支持使用 C++ 中的 range based for 循环来迭代对象。以下给出了一个简单的示例,说明这将如何工作。

    // Iterate the parent with range based for loop
    using FromParents = query::From<Parent>;
    using ParentFields = query::F<Parent>;
    FromParents from_parents;
    // Select the query which you want to execute
    auto parents_where = from_parents.where(ParentFields::f2 == "Delete Me");
    // Fetch all the objects satisfying the query. This is a lazy fetch. It will be fetched
    // only when it is called. And not all the objects are fetched.
    // Here v is a PRef so it can be used to modify and persist the object.
    for(auto v : parents_where) {
        std::cout << v.toJson() << std::endl;
    }

JSON 和 Message pack 转换 (到对象和从对象)

现在我们可以将 C++ 对象转换为 JSON 并从 JSON 创建 C++ 对象。我们甚至可以将 C++ 对象转换为 Message Pack 并从 message pack 创建 C++ 对象。

这非常简单,只需专门化 bun 辅助函数,然后就如同玩耍一样简单。

namespace dbg {
    struct C1 {
        int c1;
        C1() :c1(2) {}
    };

    struct C {
        int c;
        C1 c1;
        C(const int i = 1) :c(i) {}
    };

    struct P {
        std::string p;
        C c;
        P() :p("s1"), c(1) {}
    };
}
SPECIALIZE_BUN_HELPER((dbg::C1, c1));
SPECIALIZE_BUN_HELPER((dbg::C, c, c1));
SPECIALIZE_BUN_HELPER((dbg::P, p, c));

int jsonTest() {
    namespace bun = blib::bun;

    blib::bun::PRef<dbg::P> p = new dbg::P;
    p->p = "s11";
    p->c.c = 10;

    p->c.c1.c1 = 12;


    blib::bun::PRef<dbg::C> c = new dbg::C;
    c->c = 666;
    // Convert the object to JSON
    const std::string json_string = p.toJson();
    // Construct the new object out of JSON
    blib::bun::PRef<dbg::P> p1;
    p1.fromJson(json_string);
    const auto msgpack = p1.toMesssagepack();
    // Construct another object out of messagepack
    blib::bun::PRef<dbg::P> p2;
    p2.fromMessagepack(p1.toMesssagepack());
    // messagepack to string
    std::string msgpack_string;
    for (auto c : msgpack) {
        msgpack_string.push_back(c);
    }
    std::cout << "1. Original object Object:" << json_string << std::endl;
    std::cout << "2. Object from JSON      :" << p1.toJson() << std::endl;
    std::cout << "3. Object to Messagepack :" << msgpack_string << std::endl;
    std::cout << "4. Object from Messagepck:" << p2.toJson() << std::endl;
    std::cout << "=== Vector JSON Conversion ===" << std::endl;
    blib::bun::PRef<bakery::B> b = new bakery::B;
    b->j = "test";
    b->i.push_back(12);
    b->i.push_back(23);
    std::cout << "5. Object with Vector: " << b.toJson() << std::endl;
    blib::bun::PRef<bakery::B> b1 = new bakery::B;
    b1.fromJson(b.toJson());
    std::cout << "6. Object copy with Vector: " << b1.toJson();
    return 1;
}

键值存储

Bun 拥有一个嵌入式键值存储。默认实现基于 Unqlite。

        /// @class KVDb
        /// @brief The main class for the key value store
        template<typename T = DBKVStoreUnqlite>
        class KVDb {
        public:
            /// @fn KVDb
            /// @param param
            /// @brief The constructor for the KV class
            KVDb(std::string const& param);

            /// @fn KVDb
            /// @param other. The other KVDb from which we can copy values.
            /// @brief The copy constructor for the KV class
            KVDb(KVDb const& other);

            /// @fn ~KVDb
            /// @brief destructor for the KV class
            ~KVDb();

            /// @fn ok
            /// @brief Returns Ok
            bool ok() const;

            std::string last_status() const;

            /// @fn put
            /// @param key The key
            /// @param value the value that needs to be stored
            /// @details Put stores the key and value and returns true of the store is done, 
            /// else it returns false
            ///          All primary C++ data types including std::string is supported as key and value
            template<typename Key, typename Value>
            bool put(Key const& key, Value const& value);

            /// @fn get
            /// @param key The key
            /// @param value the value is of type ByteVctorType. This carries the out value
            /// @details Gets the value corresponding the key. 
            /// If the retrieval it returns true else it returns false.
            ///          All primary C++ data types including std::string is supported as key. 
            ///          The value is a byte (std::uint8_t) value
            template<typename Key>
            bool get(Key const& key, ByteVctorType& value);

            /// @fn get
            /// @param key The key
            /// @param value the value is of type ByteVctorType. This carries the out value
            /// @details Gets the value corresponding the key. If the retrieval it returns true 
            /// else it returns false.
            ///          All primary C++ data types including std::string is supported as key. 
            ///          The value C++ primary datatype.
            ///          This function is a wrapper on top of the previous function 
            ///          which returns the byte vector.
            template<typename Key, typename Value>
            bool get(Key const& key, Value& value);

            /// @fn del
            /// @param key The key
            /// @details Delete the value corresponding to key. 
            /// If delete is success then returns true else returns false.
            ///          All primary C++ data types including std::string is supported as key. 
            template<typename Key>
            bool del(Key const& key);
        };

以下是我们可以使用它的方式:

/// @fn kvTest
/// @brief A test program for 
int kvTest() {
    /// @var db
    /// @brief Create the database. If the database already exists 
    /// it opens the database but creates if it doesnt exist
    blib::bun::KVDb<> db("kv.db");
    /// @brief put a value in database.
    db.put("test", "test");
    std::string val;
    /// @brief get the value. We need to pass a variable by reference to get the value.
    db.get("test", val);
    std::cout << val << std::endl;
    
    const int size = 10000;
    for (int i = 0; i < size; ++i) {
        const std::string s = fmt::format("Value: {}", i);
        db.put(i, s);
    }

    for (int i = 0; i < size; ++i) {
        std::string val;
        db.get(i, val);
        std::cout << val << std::endl;
    }
    
    return 1;
}

键值的基于范围的迭代

Bun 支持对 kv 存储中元素的键值进行基于范围的迭代。这种迭代类似于 map 的迭代。键和值都作为 pair 返回。如果您在下面看到 kv 是一个 pair,kv.first 包含键值,kv.second 包含值。kv.firstkv.second 的值是字节向量

    // ========= KV Store
    blib::bun::KVDb<> db("kv.db");

    const int size = 3;
    for (int i = 0; i < size; ++i) {
        const std::string s = fmt::format("storing number: {}", i);
        db.put(i, s);
    }

    std::cout << "Start iteration Via size "<< std::endl;
    for (int i = 0; i < size; ++i) {
        std::string val;
        db.get(i, val);
        std::cout << val << std::endl;
    }

    std::cout << "Start iteration via foreach "<< std::endl;
    count = 0;
    // Iterate the key value store using foreach.
    // We have both the key and value here. So we can change the value at the key
    for (auto kv : db) {
        int key = 0;
        blib::bun::from_byte_vec(kv.first, key);

        std::string value;
        blib::bun::from_byte_vec(kv.second, value);
        std::cout << count++ << ")> key: "<< key << "\n Value: " << value << std::endl;
    }

内部

ORM 的一些内部机制如下:

Reflection(反射)

Bun 内部使用简单的反射来生成并处理编译时类型信息。有一个计划将其扩展一点,使其更有用。

SPECIALIZE_BUN_HELPER

此宏将在编译时生成对象的所有绑定。所有模板特化都使用此宏创建。在多个头文件或 CPP 文件中使用此宏应该是安全的。

应将以下内容传递给宏:

(<类名,应包含命名空间详细信息>,要持久化的成员...)

成员列表也可以是部分类成员。例如,如果我们有一个对象句柄,就没有必要将其存储在数据库中。在这种情况下,我们可以省略句柄,持久化所有其他特性。这样,只有给定的字段会被填充。

约束

Bun 中应用约束非常简单。以下示例进行了说明。

// Get the fields of the Person. This will be useful in specifying constraints and also
// querying the object.
using PersonFields = query::F<test::Person>;

// Generate the configuration. By default it does nothing.
blib::bun::Configuration<test::Person> person_config;
// This is a unique key constrains thats applied.
// Constraint are applied globally. They need to be set before the
// execution of the create schema statement
// The syntax is Field name = Constraint
// Here is how we can chain the different constraints in a single set statement
person_config.set(PersonFields::name = blib::bun::unique_constraint)
                 (PersonFields::uname = blib::bun::unique_constraint);

正如您所见,创建唯一约束非常简单。如上所述,我们可以使用重载的 () 运算符组合多个约束,而不是多次调用 set。

需要记住的事情

  • 目前,约束只能在表创建之前应用。创建表后,这些语句无效。
  • 仅支持唯一键。

在后续版本中,我将消除这些限制。

PRef

PRef 是库的核心元素之一。它保存需要持久化的对象。它还包含对象的 oid,这与实际对象无关。使对象持久化的一些规则:

  • 需要持久化的成员必须是public
  • PRef 维护对象的拥有权,并在对象离开作用域时删除该对象。
  • 如果我们为另一个 PRef 分配一个 PRef,则前者将失去对象的拥有权。就像 unique_ptr 一样。实际上,PRef 在内部将对象存储在 unique_ptr 中。
  • 在持久化对象之前,我们必须创建架构 (使用 blib::bun::createSchema<>()) 并生成绑定 (using SPECIALIZE_BUN_HELPER( (test::Person, name, age, height) );)
  • 它还包含特定实例的对象的 md5 校验和。因此,如果对象没有变化,它就不会持久化它。在我自己的用法中,我保留了更新的时间戳。我不想每次都更新对象。对于这个公开版本,我省略了时间戳。

插入或更新

库如何知道我们要插入还是更新数据库?这取决于对象的 md5。如果 md5 有值,那么它是一个 update,否则它是一个 insert。以下查询会自动为 insert 生成:

INSERT INTO 'test::Person' (object_id,name,age,height) VALUES(91340162041484,'Brainless_4',14,5.6)

搜索

在 Bun 中搜索非常简单。有不同的搜索机制。

  • Oid 搜索:我们可以使用以下方法获取所有 Oids
    // The return type is std::vector<blib::bun<SimpleOID<test::Person>>
    const auto person_oids = blib::bun::getAllOids<test::Person>();
  • 搜索特定类型的对象:我们可以将数据库中的所有对象作为对象向量获取:
    // std::vector<blib::bun::Pref<test::Person>>
    const auto person_objs = blib::bun::getAllObjects<test::Person>();
  • 对象 EDSL:我们可以通过 Bun 提供的 EDSL 查询进行搜索。EDSL 是使用 boost proto 库实现的。查询在编译时由 C++ 编译器检查。调用 SPECIALIZE_BUN_HELPER 时,它会创建一些特殊变量。

    例如:对于 Person 类,SPECIALIZE_BUN_HELPER 会生成以下内容:

    bun::query::F<test::Person>::name
    bun::query::F<test::Person>::age
    bun::query::F<test::Person>::heigh

Bun 的 bun::query::F 类将使用 Person 类的所有字段进行特化。

要应用任何类型的过滤器,您只需使用“where”函数,例如:

// The where(Query) is a lazy function, it does not query the db.
// The actual execution is done in the object() function 
const auto objs_again = bun::query::From<test::Person>().where( valid_query ).objects();
// We can also join queries or filters using && or the || operator
const auto objs_again = bun::query::From<test::Person>().where( valid_query && valid_query ).objects();

讨论论坛

  • Gitter.im:您可以在这里快速提问,或者在我们有空时与我们聊天。
  • GitHub issues:在此处创建问题。

历史

  • Alpha 1 (2016年5月16日)
    • 库的初始版本
  • Alpha 2 (2016年7月2日)
    • 实现 Bun EDSL
  • Alpha 3 (2018年3月14日)
    • 集成了 SOCI 作为数据库交互层。这使得该库可以使用任何 SQL 数据库,如 SQLite、Postgres、MySQL。它主要支持 SOCI 支持的其他数据库,但尚未进行测试。
    • 使用 Boost Fusion。代码更清晰,预处理器宏更少。代码更易于调试。
    • 支持使用 Transaction 类进行事务处理
    • 更好的错误处理和错误日志记录
    • 添加了大量注释以帮助用户
  • Alpha 4 (2018年3月5日)
    • 支持嵌套对象
    • SimpleOID 现在使用 boost UUID 生成唯一标识符
    • 其他注释
    • 小的性能提升
  • Alpha 5 (2018年5月19日)
    • 支持表创建前的约束
  • Alpha 6 (2018年7月18日)
    • 为 bun 添加键值功能
  • Alpha 7 (2018年8月11日)
    • 为对象迭代添加了基于范围的 for 循环支持。
    • 为键值存储迭代添加了基于范围的 for 循环支持。
    • 这两种迭代都是惰性迭代。
  • Alpha 8 (2018年10月19日)
    • 添加了从 JSON 字符串创建 C++ 对象的支持
    • 添加了从 C++ 对象创建 Message Pack 的支持
    • 添加了从 Message Pack 创建 C++ 对象的支持
  • Alpha 9 (2018年1月13日)
    • 添加了支持将包含 vector 的 C++ 对象转换为 JSON 和 Msgpack
    • 添加了支持将包含 vector 的 JSON 或 Msgpack 转换为 C++ 对象。

后续功能

  • 添加 C++ vector 持久化
  • 基于迭代器的惰性数据拉取
  • 自定义 Oid 类支持
  • 支持 ElasticSearch
  • 改进错误处理
  • EDSL 查询语言增强
  • 表创建后的约束修改
  • 支持其他约束
  • 索引支持
  • 支持对象处理的前置和后置钩子
  • 持久化 std::vector 成员
  • 单元测试实现
  • 支持 Leveldb
  • 键值迭代器
  • 支持复合类型 (完成)。

需要帮助

大家好,
考虑到让该库更加丰富的所需工作,我将需要任何必要的帮助。在以下领域需要帮助:

  1. 增强功能
  2. 修复 bug。
  3. 重构和清理代码。
  4. 增强文档。
  5. 建设性批评和功能建议。
  6. 编写测试。
  7. 使用 Bun

任何大小的贡献都将受到赞赏。

© . All rights reserved.