ODB 1.6.0 released
Tuesday, October 4th, 2011ODB 1.6.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 large number of major new features, small improvements, and bug fixes. For an exhaustive list of changes, see the official ODB 1.6.0 release announcement. As usual, below I am going to examine the most notable new features in more detail.
Views
No doubt the biggest feature in this release is the introduction of the view concept. An ODB view is a C++ class
that embodies a light-weight, read-only projection of one or more persistent objects or database tables or the result of a native SQL query execution.
Some of the common applications of views include loading a subset of data members from objects or columns from database tables, executing and handling results of arbitrary SQL queries, including aggregate queries, as well as joining multiple objects and/or database tables using object relationships or custom join conditions.
Many relational databases also define the concept of views. Note, however, that ODB views are not mapped to database views. Rather, by default, an ODB view is mapped to an SQL SELECT
query. However, if desired, it is easy to create an ODB view that is based on a database view.
As an example, consider a simple person
persistent class:
#pragma db object class person { ... #pragma db id auto unsigned long id_; std::string first_; std::string last_; unsigned short age_; };
Let’s say we want to define a view that returns the number of people stored in our database:
#pragma db view object(person) struct person_count { #pragma db column("count(" + person::id_ + ")") std::size_t count; };
And here is how we can use this view to get the total head count:
odb::result<person_count> r (db.query<person_count> ()); const person_count& c (*r.begin ()); // Exactly one element. cout << c.count << endl;
Or we can count people that match only certain criteria. For example, here is how we can find out how many people in our database are younger than 30:
typedef odb::query<person_count> query; typedef odb::result<person_count> result; result r (db.query<person_count> (query::age < 30)); const person_count& c (*r.begin ()); cout << c.count << endl;
ODB views can be defined in terms of one or more persistent objects, database tables, a combination of the two, or as a native SQL query. As a result, there are a lot of different things that can be achieved with views. If you would like to learn more, refer to Chapter 9, “Views” in the ODB Manual. There is also the view
example in the odb-examples
package.
NULL Semantics
ODB now supports the so-called NULL
semantics wrappers which allow us to transform any value type to a type that can have the special NULL
state. We can use the standard smart pointers as well as the odb::nullable
“optional” container as NULL
wrappers. The Boost profile adds support for boost::shared_ptr
and boost::optional
while the Qt
profile adds support for QSharedPointer
. We can also use our own smart pointers or “optional” containers as NULL
wrappers.
As an example, let’s say we would like to store the optional middle name in our person
class from the previous section. Here is how we can do it using std::auto_ptr
:
#pragma db object class person { ... std::string first_; #pragma db null std::auto_ptr<std::string> middle_; std::string last_; };
Now, if we don’t want to incur a dynamic memory allocation just to get the NULL
semantics, we can use the odb::nullable
container instead:
#include <odb/nullable.hxx> #pragma db object class person { ... std::string first_; odb::nullable<std::string> middle_; std::string last_; };
Note that here we don’t need the db null
pragma since odb::nullable
enables NULL
by default.
We could also use boost::optional
instead of odb::nullable
, provided we enable the Boost profile (-p boost
ODB compiler option):
#include <boost/optional.hpp> #pragma db object class person { ... std::string first_; boost::optional<std::string> middle_; std::string last_; };
For more information on this feature, refer to Section 7.3, “NULL Value Semantics” in the ODB manual.
Erase Query
The new erase_query()
function allows us to delete the database state of multiple objects matching certain criteria. It uses the same query expression as the query()
function. For example, this is how we can delete all the people in our database that are younger than 30:
db.erase_query<person> (odb::query<person>::age < 30)
For more information on this feature, refer to Section 3.10, “Deleting Persistent Objects” in the ODB manual.
BLOB Handling
It is now possible to use the std::vector<char>
type to store BLOB data in the database. Note, however, that to enable this mapping, we need to explicitly specify the database type, for example:
#pragma db object class person { ... #pragma db type("BLOB") std::vector<char> public_key_; };
Alternatively, we can do it on the per-type basis, for example:
typedef std::vector<char> buffer; #pragma db value(buffer) type("BLOB") #pragma db object class person { ... buffer public_key_; // Mapped to BLOB. };
Expressive Query Syntax
Prior to this release we used the scope resolution operator (::
) when referring to members inside composite values and pointed-to objects in query expressions. For example:
db.query<person> (query::employer::name == "Example, Inc");
The problem with this approach is that it is impossible to say whether the member is inside a composite value or an object just by looking at the expression. In the above example, employee
could be a composite value type or a pointer to an object. To make the queries more expressive, we have changed the syntax to use the member access operator (.
) when referring to members inside composite value types and to use the member access operator via pointer (->
) when referring to members inside related objects. As a result, the above query will look like this if employee
is a composite value:
db.query<person> (query::employer.name == "Example, Inc");
And like this, if it is a pointer to an object:
db.query<person> (query::employer->name == "Example, Inc");
Other interesting new features in this release include the --table-prefix
ODB compiler option, the odb::connection
interface, and support for multiplexing several transactions on the same thread. For more information on these and other features, see the official ODB 1.6.0 release announcement.