ODB 2.0.0 released

ODB 2.0.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, and manually writing any of the mapping code. ODB natively supports SQLite, PostgreSQL, MySQL, Oracle, and Microsoft SQL Server.

This release packs a number of major new features, including support for C++11, polymorphism, and composite object ids, as well as a few backwards-incompatible changes (thus the major version bump). We have also added GCC 4.7 and Clang 3.0 to the list of compilers that we use for testing each release. Specifically, the ODB compiler has been updated to be compatible with the GCC 4.7 series plugin API. There is also an interesting addition (free proprietary licence) to the licensing terms. As usual, below I am going to examine these and other notable new features in more detail. For the complete list of changes, see the official ODB 2.0.0 announcement.

C++11 support

This is a big feature so I wrote a separate post about C++11 support in ODB a couple of weeks ago. It describes in detail what is now possible when using ODB in the C++11 mode. Briefly, this release adds integration with the new C++11 standard library components, specifically smart pointers and containers. We can now use std::unique_ptr and std::shared_ptr as object pointers (their lazy versions are also provided). On the containers front, support was added for std::array, std::forward_list, and the unordered containers.

One C++11 language feature that really stands out when dealing with query results is the range-based for-loop. Compare the C++98 way:

 
typedef odb::query<employee> query;
typedef odb::result<employee> result;
 
result r (db.query<employee> (query::first == "John"));
 
for (result::iterator i (r.begin ()); i != r.end (); ++i)
  cout << i->first () << ' ' << i->last () << endl;
 

To the C++11 way:

 
typedef odb::query<employee> query;
 
auto r (db.query<employee> (query::first == "John"));
 
for (employee& e: r)
  cout << e.first () << ' ' << e.last () << endl;
 

If you are interested in more information on C++11 support, do read that post, it has much more detail and code samples.

Polymorphism support

Another big feature in this release is support for polymorphism. Now we can declare a persistent class hierarchy as polymorphic and then persist, load, update, erase, and query objects of derived classes using their base class interfaces. Consider this hierarchy as an example:

#pragma db object polymorphic pointer(std::shared_ptr)
class person
{
  ...
 
  virtual void print () = 0;
 
  std::string first_;
  std::string last_;
};
 
#pragma db object
class employee: public person
{
  ...
 
  virtual void print ()
  {
    cout << (temporary_ ? "temporary" : "permanent")
         << " employee " << first_ << ' ' << last_;
  }
 
  bool temporary_;
};
 
#pragma db object
class contractor: public person
{
  ...
 
  virtual void print ()
  {
    cout << "contractor " << first_ << ' ' << last_
         << ' ' << email_;
  }
 
  std::string email_;
};

Now we can work with the employee and contractor objects polymorphically using their person base class:

unsigned long id1, id2;
 
// Persist.
//
{
  shared_ptr<person> p1 (new employee ("John", "Doe", true));
  shared_ptr<person> p2 (new contractor ("Jane", "Doe", "j@d.eu"));
 
  transaction t (db.begin ());
  id1 = db.persist (p1); // Stores employee.
  id2 = db.persist (p2); // Stores contractor.
  t.commit ();
}
 
// Load.
//
{
  shared_ptr<person> p;
 
  transaction t (db.begin ());
  p = db.load<person> (id1); // Loads employee.
  p = db.load<person> (id2); // Loads contractor.
  t.commit ();
}
 
// Update.
//
{
  shared_ptr<person> p;
  shared_ptr<employee> e;
 
  transaction t (db.begin ());
 
  e = db.load<employee> (id1);
  e->temporary (false);
  p = e;
  db.update (p); // Updates employee.
 
  t.commit ();
}
 
// Erase.
//
{
  shared_ptr<person> p;
 
  transaction t (db.begin ());
  p = db.load<person> (id1); // Loads employee.
  db.erase (p);              // Erases employee.
  db.erase<person> (id2);    // Erases contractor.
  t.commit ();
}

Polymorphic behavior is also implemented in queries, for example:

 
typedef odb::query<person> query;
 
transaction t (db.begin ());
 
auto r (db.query<person> (query::last == "Doe"));
 
for (person& p: r) // Can be employee or contractor.
  p.print ();
 
t.commit ();
 

The above query will select person objects that have the Doe last name, that is, any employee or contractor with this name. While the result set is defined in terms of the person interface, the actual objects (i.e., their dynamic types) that it will contain are employee or contractor. Given the above persist() calls, here is what this code fragment will print:

permanent employee John Doe
contractor Jane Doe j@d.eu

There are several alternative ways to map a polymorphic hierarchy to a relational database model. ODB implements the so-called table-per-difference mapping where each derived class is mapped to a separate table that contains only columns corresponding to the data members added by this derived class. This approach is believed to strike the best balance between flexibility, performance, and space efficiency. In the future we will consider supporting other mappings (e.g, table-per-hierarchy), depending on user demand.

For more detailed information on polymorphism support, refer to Chapter 8, “Inheritance” in the ODB Manual. There is also the inheritance/polymorphism example in the odb-examples package.

Composite object ids

ODB now supports composite object ids (translated to composite primary keys in the relational database). For example:

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

For more information on this feature, refer to Section 7.2.1, “Composite Object Ids” in the ODB manual as well as the composite example in the odb-examples package.

Optional session support

The most important backwards-incompatible change in this release is making session support optional (the other has to do with the database operations callbacks; see the official announcement for details). As you may remember, session is a persistent object cache which is often useful to minimize the number of database operations and can be required in order to load some bidirectional object relationships.

With ODB we try to follow the “you don’t pay for things you don’t use” principle. So support for things that are not needed by all the applications (e.g., query) is not included into the generated code by default. This is particularly important for mobile/embedded applications that need to minimize code size as well as memory and CPU usage. Session support was an exception to this rule and we’ve decided to fix it in this release.

Now there are several ways to enable/disable session support for persistent classes. It can be done on the per object basis or at the namespace level using the new session pragma. It can also be enabled by default for all the objects using the --generate-session ODB compiler option. Thus to get the old behavior where all the objects were session-enabled, simply add --generate-session to your ODB compiler command line. For more information, refer to Chapter 10, “Session” in the ODB manual.

Free proprietary licence

To conclude, I would also like to mention a change to the ODB licensing terms. In addition to all the licensing options we currently have (open source and commercial proprietary licenses), we now offer a free proprietary license for small object models. This license allows you to use ODB in a proprietary (closed-source) application free of charge and without any of the GPL restrictions provided that the amount of the generated database support code does not exceed 10,000 lines. The ODB compiler now includes the --show-sloc command line option that can be used to show the amount of code being generated.

How much is 10,000 lines? While it depends on the optional features used (e.g., query support, views, containers, etc.), as a rough guide, 10,000 lines of code are sufficient to handle an object model with 10-20 persistent classes each with half a dozen data members.

For more information on the free proprietary license, including a Q&A section, refer to the ODB Licensing page.

Comments are closed.