SQLite
C++ ORM framework for SQLite
Over the past week I’ve been rewriting my rather dated SQLite wrapper to have an efficient, modern C++ feel. The basic wrapper is there, but I was looking for something a little more this time.
While looking at the problem I decided I was spending too much time writing boilerplate SQL for all my types so I decided to look at existing ORM frameworks. I’m pretty picky about my C++ though, and couldn’t find anything I liked so I started writing my own. Instead of creating a tool to generate C++, I wanted to take a pure approach using native C++ types and template metaprogramming.
What I ended up with is not a full ORM framework, and I’m not particularly interested in making it one. All I’m aiming for is removing boilerplate code while leaving it easy to extend it for more complex queries. Here’s what I’ve got so far:
struct my_object { int id; std::string value; boost::posix_time::ptime time; }; typedef boost::mpl::vector< sqlite3x::column< my_object, int, &my_object::id, sqlite3x::primary_key, sqlite3x::auto_increment >, sqlite3x::column< my_object, std::string, &my_object::value, sqlite3x::unique_key >, sqlite3x::column< my_object, boost::posix_time::ptime, &my_object::time > > my_object_columns; typedef sqlite3x::table< my_object, my_object_columns > my_object_table;
Using it is pretty simple. It uses the primary key as expected, generating the proper WHERE
conditions and even extracting the type to let find()
and others specify only the primary key:
sqlite3x::connection con("test.db3"); my_object_table my_objects(con, "t_objects"); my_objects.add(my_object()); my_objects.edit(my_object()); my_objects.remove(int()); my_objects.exists(int()); my_objects.find(int());
One benefit of the approach taken is it makes working with single- and multiple-inheritance just as easy:
struct my_derived : my_object { float extra; }; typedef boost::mpl::copy< boost::mpl::vector< sqlite3x::column<my_derived, float, &my_object::extra> >, boost::mpl::back_inserter<my_object_columns> > my_derived_columns; typedef sqlite3x::table< my_derived, my_derived_columns > my_object_table;
The next thing on the list was supporting types not known natively to sqlite3x. I did not want to have the headache of sub-tables, so I took the easy route and implemented basic serialization support:
struct my_derived : my_object { std::vector<boost::uuid> uuids; }; struct uuids_serializer { static void serialize(std::vector<boost::uint8_t> &buffer, const std::vector<boost::uuid> &uuids); template<typename Iterator> static Iterator deserialize(std::vector<boost::uuid> &uuids, Iterator first, Iterator last); }; typedef boost::mpl::copy< boost::mpl::vector< sqlite3x::column< my_derived, float, &my_object::extra, sqlite3x::serializer<uuids_serializer> > >, boost::mpl::back_inserter<my_object_columns> > my_derived_columns;
A few things aren’t finished, like specifying indexes and support for multi-column primary keys.
Overall though, I’m pretty happy with it. The majority of what I use SQLite for doesn’t require many complex queries, so this should greatly help lower the amount of code I have to manage.
Best of all this ORM code is in an entirely isolated header file—if you don’t want it, just don’t include it and you’ll still have access to all the basic SQLite functions. Even with it included I kept to the C++ mantra of “dont pay for what you don’t use”—as it is entirely template-driven, code will only be generated if you actually use it.
Once I’m finished the code will replace what I have up on the SQLite wrapper page, but until then it will exist in the subversion repository only.