Archive for the ‘ORM’ Category

ODB 1.2.0 released

Wednesday, March 16th, 2011

ODB 1.2.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.2.0 release announcement. In this post I am going to examine the most notable new features in more detail.

But before we move to the technical matters, I would like to make a different kind of announcement: Constantin Michael has joined the ODB development team. For this release he focused on the Boost profile, discussed next.

Boost profile

You might have noticed that the previous release added profiles as a new feature. We didn’t discuss it much then because there were no profile implementations available at that time. It was just the ODB compiler infrastructure that we were preparing for the first profile implementation. Well, the 1.2.0 release adds the first profile implementation to ODB: the Boost profile.

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 Boost profile covers the most commonly used types from the smart_ptr, unordered, and date_time Boost libraries. For example, now we can write:

#pragma db object
class employee
{
  ...
 
  boost::gregorian::date born_;
  boost::unordered_set<std::string> emails_;
  boost::shared_ptr<employer> employer_;
};

As is evident from the code fragment above, we don’t need to do anything special to use Boost types in our persistent classes. Are there any other actions that we need to perform for the above code to work?

As mentioned above, ODB profiles are a generic mechanism that can be used to integrate ODB with a type-system of any third-party library. For example, we are currently working on another profile, this time for the Qt framework. So the ODB compiler and its runtime library don’t know anything about, say, Boost or Qt. Rather, they provide general integration support. The code that is necessary to implement a profile is packaged into a separate library called a profile library. The Boost profile implementation is provided by the libodb-boost profile library.

Now, back to our question. What do we need to do to be able to write the above code in an application that uses ODB? There are three simple steps:

  1. Download and build the profile library.
  2. Specify the profile you would like to use when invoking the ODB compiler. For example:
    odb -d mysql --profile boost 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 11, “Profiles Introduction” in the ODB Manual. For more information on the Boost profile see Chapter 12, “Boost Profile”.

Embedded database schema

ODB now supports embedding the database schema into the generated C++ code in addition to generating the schema as a standalone SQL file. The new ODB compiler option that allows you to select between the two approaches is --schema-format. For example:

odb -d mysql --generate-schema --schema-format embedded ...

The API usage for creating the database schema from within the application looks like this:

#include <odb/schema-catalog.hxx>
 
database& db = ...
 
transaction t (db.begin ());
schema_catalog::create_schema (db);
t.commit ();

For more details refer to Section 3.3, “Database” in the ODB Manual as well as the schema/embedded example in the odb-examples package.

There are also other important features in this release, including, transparent database reconnection, recoverable exceptions (connection_lost, timeout, and deadlock), and support for the default ODB compiler options file which can be used for installation-wide customizations. Refer to the official ODB 1.2.0 release announcement for more details on these and other features.

ODB 1.1.0 released

Wednesday, January 26th, 2011

ODB 1.1.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 took a bit longer than we initially planned, mainly because many of the new features depended on each other and, once we implemented one, then it also made sense to implement the others. For example, bidirectional object relationships (see below) are pretty much useless in any real-world scenario without support for lazy loading.

But, in the end, the result is a version that is packed with major new features, which I am going to examine next.

Containers

It is now possible to store containers in the database. Built-in support is provided for most standard C++ containers, namely, vector, list, map, multimap, set, and multiset. For example, now you can write:

#pragma db object
class person
{
  ...
 
  std::set<std::string> emails_;
};

And that’s it. That’s all you need to do. It is also very easy to add support for custom container types. All you need to do is provide a traits specialization for your container (see the ODB manual for details). Plus we are working on profile libraries that will provide support for containers found in popular framework and libraries, such as Boost and Qt.

Relationships

ODB now supports unidirectional (to-one, to-many) and bidirectional (one-to-one, one-to-many, many-to-many) object relationships. For example:

#pragma db object
class employer
{
  ...
 
  #pragma db inverse(employer_)
  std::vector<lazy_weak_ptr<employee> > employees_;
};
 
#pragma db object
class employee
{
  ...
 
  shared_ptr<employer> employer_;
};

In the above code fragment we used lazy_weak_ptr, which is a lazy pointer. The object that it points to is only loaded when requested.

In bidirectional relationships it is also possible to declare one side inverse, as shown in the code above. This results in the canonical relational model where only one table contains a reference to the other table.

Similar to containers, ODB provides built-in support (and the corresponding lazy pointers) for standard C++ pointers, namely, the raw pointer, auto_ptr, and TR1 shared_ptr/lazy_ptr. At the same time, profile libraries will provide support for smart pointers found in popular framework and libraries, such as Boost shared_ptr and Qt QSharedPointer. It is also easy to add support for your own smart pointers, as described in the ODB manual.

Composite Values

ODB prior to 1.1.0 only supported simple (single-column) value types. Now you can declare and use composite (multi-column) value types, for example:

#pragma db value
class name
{
  ...
 
  std::string first_;
  std::string last_;
};
 
#pragma db object
class person
{
  ...
 
  name name_;
};

Of course, all these features work well with each other. For example, you can have a container of object pointer or composite value types. Similarly, a composite value type can have members that are other composite value types, object pointers, or containers. Plus, these features are integrated into the query language so that you can use data members from composite values or related objects in query expressions, for example:

typedef odb::query<person> person_query;
typedef odb::query<employee> employee_query;
 
db.query<person> (person_query::name::last == "Doe");
db.query<employee> (employee_query::employer::name == "Example Inc");

There are also other important features in this release, including, optional object cache (session), customizable object pointers, native SQL statement execution, etc. For an exhaustive list of new features see the official ODB 1.1.0 release announcement.

ODB - compiler-based ORM for C++

Wednesday, September 29th, 2010

If you have read my earlier posts on parsing C++ with GCC plugins (Part 1, Part 2, and Part 3), then you might remember that I mentioned a secret project that I have been working on. You might also have noticed that I’ve been neglecting this blog lately. Well, today is the day to unveil this secret project, which is what kept me busy for these past several months.

The project is called ODB and it is a compiler-based 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.

You might have already used other ORM implementations for C++. And if you have been exposed to ORM systems for other mainstream languages, such as Hibernate for Java, the C++ versions must have felt pretty inferior. The major sore point is the need to write some sort of serialization or registration code for each and every data member in each and every persistent class. Forgot to register a new member? Say good bye to your data.

The primary goal of the ODB project is to change that. It takes a different approach and uses a C++ compiler to parse your classes and automatically generate the database conversion code. Or, more precisely, it uses the new GCC plugin architecture to re-use the tried and tested GCC compiler frontend to parse C++. As a result, ODB is capable of handling any C++ code. While the ODB compiler uses GCC internally, its output is standard C++ which means that you can use any C++ compiler to build the generated code and your application.

Let’s see how a persistent class declaration will look in ODB:

  #pragma db object
  class person
  {
    ...
 
  private:
    friend class odb::access;
    person ();
 
    #pragma db id auto
    unsigned long id_;
 
    string first_;
    string last_;
    unsigned short age_;
  };

ODB is not a framework. It does not dictate how you should write your application. Rather, it is designed to fit into your style and architecture by only handling C++ object persistence and not interfering with any other functionality. As you can see, existing classes can be made persistent with only a few modifications.

Given the above class, we can perform various database operations with its objects:

  person john ("John", "Doe", 31);
  person jane ("Jane", "Doe", 29);
 
  transaction t (db.begin ());
 
  db.persist (john);
  db.persist (jane);
 
  result r (db.query<person> (query::age < 30));
  copy (r.begin (),
        r.end (),
        ostream_iterator<person> (cout, "n"));
 
  jane.age (jane.age () + 1);
  db.update (jane);
 
  t.commit ();

ODB is written in portable C++ and you should be able to use it with any modern C++ compiler. We have tested this release on GNU/Linux (x86/x86-64), Windows (x86/x86-64), Mac OS X, and Solaris (x86/x86-64/SPARC) with GNU g++ 4.2.x-4.5.x, MS Visual C++ 2008 and 2010, and Sun Studio 12. The dependency-free ODB compiler binaries are available for all of the above platforms. The initial release only supports MySQL as the underlying database. Support for other database systems is in the works.

Well, I hope this sounds as exciting to you as it does to me. And I hope you will enjoy playing around with ODB (check out the Hello World Example if nothing else) while I go catch up on some sleep.