Bun is a simple to use C++ Object Relational Mapper (ORM) library and much more (key/value store, JSON conversion and Message Pack conversion). Following is the features of bun:
With Bun you can persist C++ objects (POD) directly. The magic happens by registring the C++ structure/class with Bun and you are good to go.
#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 Parent
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://localhost/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.
bun::Configuration<test::Person> person_config;
// This is a unique key constraint 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 in the same statement as shown below.
// 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 = bun::unique_constraint)
(PersonFields::uname = bun::unique_constraint);
// Create the schema. We can create the schema multiple times.
// If it already exists it will be safely ignored.
// The constraints are applied to the table.
// Adding constraints doesn't have an effect if the table already exists.
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 and releases the memory when it is destroyed.
// Internally it holds the object in a unique_ptr
// PRef also has an 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 an 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<bun::SimpleOID<test::Person>>
const auto person_oids = bun::getAllOids<test::Person>();
// To get the objects of a particular type
// std::vector<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 is checked for validity of syntax directly at compile time.
// 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
bun::createSchema<Child>();
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) {
bun::l().info("===============Start===================");
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;
bun::l().info("===============End===================\n");
}
std::cout << "Get all objects and show" << std::endl;
auto parents = 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;
}
Its easy to retrieve values from database and iterate over them. The iteration is lazy, which means that the values are not retrieved all at once but paginated.
// 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;
}
With Bun you can do the following too:
The following code demonstrates that:
#include "blib/bun/bun.hpp"
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;
bun::PRef<dbg::P> p = new dbg::P{};
p->p = "s11";
p->c.c = 10;
p->c.c1.c1 = 12;
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
bun::PRef<dbg::P> p1;
p1.fromJson(json_string);
const auto msgpack = p1.toMesssagepack();
// Construct another object out of messagepack
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;
return 1;
}
Bun has a key/value store. Currently the key/value store supported is Unqlite. Following example explains how to use it:
#include "blib/bun/bun.hpp"
/// @fn kvTest
/// @brief A test program for using the key/value store
int kvTest() {
/// @var db
/// @brief Create the database.
/// If the database already exists it opens the database.
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;
}
Considering the work needed to take this library further I will be needing any help available. Help is needed in the following areas.
Implementing the Bun EDSL