Archive for April, 2011

ODB 1.4.0 released

Wednesday, April 27th, 2011

ODB 1.4.0 was released today.

In case you are not familiar with ODB, it is an object-relational mapping (ORM) system for C++. It allows you to persist C++ objects to a relational database without having to deal with tables, columns, or SQL, or manually writing any of the mapping code.

This version includes a number of major new features, small improvements, and bug fixes. For an exhaustive list of changes, see the official ODB 1.4.0 release announcement. As usual, below I am going to examine the most notable new features in more detail.

Qt profile

Two versions ago we announced the Boost profile. Now it is Qt’s turn. As you may remember, ODB profiles are a generic mechanism for integrating ODB with widely-used C++ frameworks and libraries. A profile provides glue code which allows us to seamlessly persist various components, such as smart pointers, containers, and value types found in these frameworks and libraries.

In this initial release the Qt profile covers the most commonly used basic types (QString and QByteArray), date-time types (QDate, QTime, and QDateTime), smart pointers (QSharedPointer and QWeakPointer plus their lazy variants, QLazySharedPointer and QLazyWeakPointer), and containers (QVector, QList, QLinkedList, QSet, QMap, QMultiMap, QHash, and QMultiHash). For example, now we can write:

#pragma db object
class Employee
{
  ...
 
  QString first_name_;
  QString last_name_;
 
  QDate born_;
 
  QSet<QString> emails_;
  QByteArray publicKey_;
 
  QSharedPointer<Employer> employer_;
};

As is evident from the code fragment above, we don’t need to do anything special to use Qt types in our persistent classes. All we need are these three simple steps:

  1. Download and build the Qt profile library, libodb-qt
  2. Specify the Qt profile when invoking the ODB compiler. For example:
    odb -d mysql --profile qt employee.hxx
    
  3. Link the profile library to your application.

And that’s it. That’s all we need to do. For more detailed information on ODB profiles in general, refer to Chapter 13, “Profiles Introduction” in the ODB Manual. For more information on the Qt profile, see Chapter 15, “Qt Profile”.

Inheritance

ODB now supports persistent class inheritance. In this version only non-polymorphic, reuse-style inheritance is supported with each class mapped to a separate database table that contains all the data members, including those inherited from base classes.

It is now also possible to declare a persistent class abstract. Instances of such a class are not stored in the database. Rather, it is used as a base for other persistent classes. For example:

#pragma db object abstract
class person
{
  ...
 
  std::string first_;
  std::string last_;
};
 
#pragma db object
class employee: public person
{
  ...
 
  #pragma db id auto
  unsigned long id_;
};
 
#pragma db object
class contractor: public person
{
  ...
 
  #pragma db id
  std::string email_;
};

For the above example the database schema will contain only two tables, employee and contractor, with the following definitions:

CREATE TABLE employee (
  first TEXT NOT NULL,
  last TEXT NOT NULL,
  id BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT);
 
CREATE TABLE contractor (
  first TEXT NOT NULL,
  last TEXT NOT NULL,
  email VARCHAR (255) NOT NULL PRIMARY KEY);

For more details on inheritance, refer to Chapter 8, “Inheritance” in the ODB Manual as well as the inheritance example in the odb-examples package. Support for polymorphic inheritance is coming in future versions of ODB.

C++ enum mapping

ODB can now automatically map C++ enumerations to suitable database types. Let’s say we define C++ type color like this:

enum color {red, green, blue};

If we are targeting the MySQL database, which supports ENUM SQL type, then color will be automatically mapped to ENUM('red', 'green', 'blue'). If the target database does not support enumerations, such as SQLite, then the ODB compiler will map color to a suitable integer type.

It is also easy to override this mapping. Let’s say we always want to store color as an integer, even in databases such as MySQL. Then we can specify the desired type explicitly:

#pragma db value type("INT UNSIGNED")
enum color {red, green, blue};

Or, maybe, our convention is to capitalize the enumerators in the database. We can do that as well:

#pragma db value type("ENUM ('RED', 'GREEN', 'BLUE')")
enum color {red, green, blue};

There are also other important features in this release, including, the new id_type pragma and consistent size_t type mapping. Refer to the official ODB 1.4.0 release announcement for more details on these and other features.

Performance of ODB vs C# ORMs

Wednesday, April 6th, 2011

You might have heard the story of Stack Overflow switching to their own C# ORM framework because all the existing ones are too slow. Yesterday this framework was named Dapper and published as an open-source project. Dapper also includes a simple performance benchmark and the results for pretty much all the popular C# ORMs. This caught my attention and I thought, why not implement the same benchmark for ODB C++ ORM and see how it stacks up against the C# crowd?

In a nutshell, the idea of the benchmark is to measure the time it takes to pull 500 random post objects from the database. The post object here is meant to simulate a Stack Overflow question. Its C++ version is shown below:

#pragma db object
class post
{
public:
  #pragma db id
  unsigned long id;
 
  std::string text;
 
  boost::posix_time::ptime creation_date;
  boost::posix_time::ptime last_change_date;
 
  int counter1;
  int counter2;
  int counter3;
  int counter4;
  int counter5;
  int counter6;
  int counter7;
  int counter8;
  int counter9;
};

Once the above class is compiled with the ODB compiler, the resulting database schema looks like this, which is identical to the one used in Dapper’s benchmark:

CREATE TABLE post (
  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
  text TEXT NOT NULL,
  creation_date DATETIME,
  last_change_date DATETIME,
  counter1 INT NOT NULL,
  counter2 INT NOT NULL,
  counter3 INT NOT NULL,
  counter4 INT NOT NULL,
  counter5 INT NOT NULL,
  counter6 INT NOT NULL,
  counter7 INT NOT NULL,
  counter8 INT NOT NULL,
  counter9 INT NOT NULL)

First, the ODB benchmark loads 10000 objects into the database:

transaction t (db->begin ());
 
for (unsigned long i (0); i < total_objects; ++i)
{
  post p;
 
  p.id = i;
  p.text = text;
  p.creation_date = second_clock::local_time () -
    time_duration (i, 0, 0);
  p.last_change_date = second_clock::local_time ();
 
  p.counter1 = i + 1;
  p.counter2 = i + 2;
  p.counter3 = i + 3;
  p.counter4 = i + 4;
  p.counter5 = i + 5;
  p.counter6 = i + 6;
  p.counter7 = i + 7;
  p.counter8 = i + 8;
  p.counter9 = i + 9;
 
  db->persist (p);
}
 
t.commit ();

Then it runs the following iteration a couple of hundred times while measuring the time:

void
test (database& db)
{
  post p;
 
  transaction t (db.begin ());
 
  for (unsigned long i (0); i < 500; ++i)
  {
    unsigned long id (rand () % total_objects);
    db.load (id, p);
  }
 
  t.commit ();
}

I used the MySQL database server to run this benchmark (the C# test uses Microsoft SQL Server). In my case, both the database and the benchmark were running on the same multi-core, 64-bit Linux machine. As you can see on Dapper’s web site, the best performance one can get with C# is 47ms (hand-coded) per 500 iterations with Dapper coming second at 49ms. ODB does it in 24ms. Half the time of the hand-coded C# version is not bad! As a bonus, I also ran the ODB test with SQLite (just had to recompile with different options — no source code changes required). The same benchmark using SQLite takes 7ms (14μs per object)! Now, that is fast.

UPDATE: As was pointed out by a reader, it would be useful to know the hardware that was used for the benchmarks. Unfortunately, Dapper’s results don’t mention the test hardware.ODB results are for a Xeon E5520 2.27GHz machine.

ODB 1.3.0 released

Wednesday, April 6th, 2011

ODB 1.3.0 was released today.

In case you are not familiar with ODB, it is an object-relational mapping (ORM) system for C++. It allows you to persist C++ objects to a relational database without having to deal with tables, columns, or SQL, or manually writing any of the mapping code.

As usual, for the complete list of changes see the official ODB 1.3.0 announcement. But the most important new feature in this release is no doubt support for the SQLite embedded database. Below I am going to examine this in more detail.

Support for SQLite is provided by the libodb-sqlite runtime library. Pretty much all the standard ODB functionality is available to you when using SQLite, including support for containers, object relationships, queries, date-time types in the Boost profile, etc. There are a few limitations, however, that are discussed in Chapter 11, “SQLite Database” in the ODB Manual.

If you request the generation of the database schema (--generate-schema ODB compiler option), then for SQLite, by default, the schema is embedded into the generated C++ code since most applications that use SQLite probably don’t want to carry a .sql file around. See the schema/embedded example in the odb-examples package for more information on how to use embedded schemas.

If you have used SQLite before, you are probably aware of the peculiar ways in which it manages access to the same database from multiple threads and processes. While more traditional database systems will make concurrent access pretty much transparent to the user (except for an occasional deadlock or timeout), SQLite simply returns an error if the database is used by someone else. There are some advanced mechanisms available, such as the shared cache mode and unlock notifications, which allow the user to implement more traditional (i.e., blocking) concurrent access to the database from within a multi-threaded program. But it is still quite a lot of low-level, non-trivial code that one has to write.

The good news is that ODB takes care of all these details and allows you to access the same database from multiple threads in the same way as you would with any other database system. For connection management, ODB provides three built-in connection factories (you can also provide your own if so desired): single_conection_factory, new_conection_factory, and conection_pool_factory.

The single connection factory shares a single connection among all the callers. So if one thread is using the connection, all the others requesting a connection will be blocked until it is done.

The new connection factory creates a new connection whenever one is requested. Once the connection is no longer needed, it is closed.

The connection pool factory maintains a pool of connections and you can specify the min and max connection counts for each pool created. This factory is the default choice when creating a database instance.

The new and pool factories are the best options for multi-threaded applications. By default they enable the SQLite shared cache mode and use the unlock notifications to aid concurrency.

Another SQLite-specific feature worth mentioning is support for starting immediate and exclusive SQLite transactions. This is accomplished with two additional odb::sqlite::database functions: begin_immediate() and begin_exclusive(). These functions are primarily useful for avoiding deadlocks.

If you would like to learn more about SQLite support in ODB, the best place to start is Chapter 11, “SQLite Database” in the ODB Manual.