From d455b7c1c09d0b40d71b06d1d3eb5dcad97c81ee Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 17 Jan 2011 16:23:44 +0200 Subject: Final order, TOC for new chapters --- doc/manual.xhtml | 3870 +++++++++++++++++++++++++++--------------------------- 1 file changed, 1961 insertions(+), 1909 deletions(-) (limited to 'doc') diff --git a/doc/manual.xhtml b/doc/manual.xhtml index 8fe19f0..ecfde89 100644 --- a/doc/manual.xhtml +++ b/doc/manual.xhtml @@ -325,62 +325,115 @@ for consistency. - 5ODB Pragma Language + 5Containers + + + + + +
5.1Ordered Containers
5.2Set and Multiset Containers
5.3Map and Multimap Containers
5.4Using Custom Containers
+ + + + + 6Relationships + + + + + + + + + +
6.1Unidirectional Relationships + + + +
6.1.1To-One Relationships
6.1.2To-Many Relationships
+
6.2Bidirectional Relationships + + + + +
6.2.1One-to-One Relationships
6.2.2One-to-Many Relationships
6.2.3Many-to-Many Relationships
+
6.3Lazy Pointers
6.4Using Custom Smart Pointers
+ + + + + 7Composite Value Types + + +
7.1Composite Value Column and Table Names
+ + + + + 8Session + + +
8.1Object Cache
+ + + + + 9ODB Pragma Language - - - - @@ -389,15 +442,15 @@ for consistency. - - + - +
5.1Object Type Pragmas + 9.1Object Type Pragmas - - + +
5.1.1table
5.1.2pointer
9.1.1table
9.1.2pointer
5.2Value Type Pragmas + 9.2Value Type Pragmas - - - - - - - - - - + + + + + + + + + +
5.2.1type
5.2.2not_null
5.2.3unordered
5.2.4index_type
5.2.5key_type
5.2.6value_type
5.2.7id_column
5.2.8index_column
5.2.9key_column
5.2.10value_column
9.2.1type
9.2.2not_null
9.2.3unordered
9.2.4index_type
9.2.5key_type
9.2.6value_type
9.2.7id_column
9.2.8index_column
9.2.9key_column
9.2.10value_column
5.3Data Member Pragmas + 9.3Data Member Pragmas - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + +
5.3.1id
5.3.2auto
5.3.3type
5.3.4column
5.3.5transient
5.3.6not_null
5.3.7inverse
5.3.8unordered
5.3.9table
5.3.10index_type
5.3.11key_type
5.3.12value_type
5.3.13id_column
5.3.14index_column
5.3.15key_column
5.3.16value_column
9.3.1id
9.3.2auto
9.3.3type
9.3.4column
9.3.5transient
9.3.6not_null
9.3.7inverse
9.3.8unordered
9.3.9table
9.3.10index_type
9.3.11key_type
9.3.12value_type
9.3.13id_column
9.3.14index_column
9.3.15key_column
9.3.16value_column
5.4C++ Compiler Warnings + 9.4C++ Compiler Warnings - - - - - + + + + +
5.4.1GNU C++
5.4.2Visual C++
5.4.3Sun C++
5.4.4IBM XL C++
5.4.5HP aC++
9.4.1GNU C++
9.4.2Visual C++
9.4.3Sun C++
9.4.4IBM XL C++
9.4.5HP aC++
6Database Systems + 10Database Systems - @@ -1084,7 +1137,7 @@ main (int argc, char* argv[]) database name, etc., from the command line. In your own applications you may prefer to use other mysql::database constructors which allow you to pass this information directly - (see Section 6.1.2, "MySQL Database Class").

+ (see Section 10.1.2, "MySQL Database Class").

Next, we create three person objects. Right now they are transient objects, which means that if we terminate the application @@ -1552,9 +1605,9 @@ Hello, Joe! object and doesn't have its own unique identifier.

An object consists of data members which are either values, pointers - to other objects (Chapter Y, "Relationships"), or - containers of values or pointers to other objects (Chapter - X, "Containers"). Pointers to other objects and containers can + to other objects (Chapter 6, "Relationships"), or + containers of values or pointers to other objects (Chapter + 5, "Containers"). Pointers to other objects and containers can be viewed as special kinds of values since they also can only be stored in the database as part of an object.

@@ -1636,7 +1689,7 @@ class person

These two pragmas are the minimum required to declare a persistent class. Other pragmas can be used to fine-tune the database-related properties of a class and its - members (see Chapter 5, "ODB Pragma Language").

+ members (see Chapter 9, "ODB Pragma Language").

Normally, an object class should define the default constructor. The generated database support code uses this constructor when @@ -1682,8 +1735,8 @@ private: is unknown to the ODB compiler then we will need to provide the mapping to the database system type and, possibly, the code to convert between the two. For more information on how to achieve - this refer to the db type pragma (Section - 5.2.1, "type"). Similar to object types, composite + this refer to the db type pragma (Section + 9.2.1, "type"). Similar to object types, composite value types have to be explicitly declared as persistent using the db value pragma, for example:

@@ -1698,8 +1751,8 @@ class name }; -

Composite values are discussed in greater detail in Chapter - Z, "Composite Value Types".

+

Composite values are discussed in greater detail in Chapter + 7, "Composite Value Types".

Normally, you would use object types to model real-world entities, things that have their own identity. For example, in the @@ -1750,8 +1803,8 @@ class name create dynamically allocated instances of persistent classes and return pointer to these instances. As we will see in later chapters, pointers are also used to establish relationships between objects - (Chapter Y, Realtionships) as well as to cache - persistent object in a session (Chapter Q, Session).

+ (Chapter 6, Realtionships) as well as to cache + persistent object in a session (Chapter 8, Session).

By default, all these mechanisms use raw pointers to return, pass, and cache objects. This is normally sufficient for applications @@ -1795,7 +1848,7 @@ class person }; -

Refer to Section 5.1.2, "pointer" +

Refer to Section 9.1.2, "pointer" for more information on this pragma.

Built-in support, provided by the ODB runtime, library allows us to use @@ -1804,7 +1857,7 @@ class person commonly used frameworks and libraries (such as Boost and Qt), provide support for smart pointers found in these frameworks and libraries. It is also easy to add support for our own smart pointers, - as described in Section Y.4, "Using Custom Smart + as described in Section 6.4, "Using Custom Smart Pointers".

3.3 Database

@@ -1848,7 +1901,7 @@ auto_ptr<odb::database> db ( as well as the next chapter which is dedicated to the topic of querying the database for persistent objects. For details on the system-specific database classes, refer to - Chapter 6, "Database Systems".

+ Chapter 10, "Database Systems".

3.4 Transactions

@@ -2094,7 +2147,7 @@ update_age (database& db, person& p)

The first persist() function expects a constant reference to an instance being persisted. The second function expects a constant object pointer. Both of these functions can only be used on objects with - application-assigned object ids (Section 5.3.2, + application-assigned object ids (Section 9.3.2, "auto").

The second and third persist() versions are similar to the @@ -2460,7 +2513,7 @@ namespace odb

The null_pointer exception is thrown when a pointer to a persistent object declared non-NULL with the db not_null pragma has the NULL - value. See Chapter Y, "Relationships" for details.

+ value. See Chapter 6, "Relationships" for details.

The next three exceptions (already_in_transaction, not_in_transaction, @@ -2472,7 +2525,7 @@ namespace odb not_in_session, and const_object) are thrown by the odb::session class and are discussed - in Chapter Q, "Session".

+ in Chapter 8, "Session".

The deadlock exception is thrown when a transaction deadlock is detected by the database system. It can thrown by any @@ -2497,8 +2550,8 @@ namespace odb

The database_exception is a base class for all database system-specific exceptions that are thrown by the - database system-specific runtime library. See Chapter - 6, "Database Systems" for more information.

+ database system-specific runtime library. See Chapter + 10, "Database Systems" for more information.

The odb::exception class is defined in the <odb/exception.hxx> header file. All the @@ -2514,1020 +2567,930 @@ namespace odb -

X Containers

- -

The ODB runtime library provides built-in persistence support for - all commonly used standard C++ containers, namely, - std::vector, std::list, std::set, - std::multiset, std::map, and - std::multimap. Plus, ODB profile libraries, that are - available for commonly used frameworks and libraries (such as Boost - and Qt), provide persistence support for containers found in these - frameworks and libraries. It is also easy to persist custom - container types as discussed later in Section X.4, - "Using Custom Containers".

- -

We don't need to do anything special to declare a member of a - container type in a persistent class. For example:

- -
-#pragma db object
-class person
-{
-  ...
-private:
-  std::vector<std::name> nicknames_;
-  ...
-};
-  
- -

A data member in a persistent class that is of a container type - behaves like a value type. That is, when an object is made persistent, - the elements of the container are store in the database. Similarly, - when a persistent object is loaded from the database, the contents - of the container are automatically loaded as well.

-

While an ordinary member is mapped to one or more columns in the - object's table, a member of a container type is mapped to a seperate - table. The exact schema of such a table depends on the kind of - a container. ODB defines the following container kinds: ordered, - set, multiset, map, and multimap. The container kinds and the - contents of the tables to which they are mapped are discussed - in detail in the following sections.

+

4 Querying the Database

-

Containers in ODB can contain simple value types, composite value - types (see @@), and pointers to objects (see @@). Containers of - containers, either directly or indirectly via a composite value - type, are not allowed. A key in map and multimap containers can - be a simple or composite value type but not a pointer to an object. - An index in the ordered container should be a simple integer type.

+

If we don't know the identifiers of the objects that we are looking + for, we can use queries to search the database for objects matching + certain criteria. The ODB query facility is optional and we need to + explicitly request the generation of the necessary database support + code with the --generate-query ODB compiler option.

-

The value type in the ordered, set, and map containers as well as - the key type in the map containers should be default-constructible. - The default constructor in these types can be made private in which - case the odb::access class should be made a friend of - the value or key type. For example:

+

ODB provides a flexible query API that offers two distinct levels of + abstraction from the database system query language such as SQL. + At the high level we are presented with an easy to use yet powerful + object-oriented query language, called ODB Query Language. This + query language is modeled after and is integrated into C++ allowing + us to write expressive and safe queries that look and feel like + ordinary C++. We have already seen examples of these queries in the + introductory chapters. Below is another, more interesting, example:

-#pragma db value
-class name
-{
-public:
-  name (const std::string&, const std::string&);
-  ...
-private:
-  friend class odb::access;
-  name ();
-  ...
-};
+  typedef odb::query<person> query;
+  typedef odb::result<person> result;
 
-#pragma db object
-class person
-{
-  ...
-private:
-  #pragma db id auto
-  unsigned long id_;
+  unsigned short age;
+  query q (query::first == "John" && query::age < query::_ref (age));
 
-  std::vector<name> aliases_;
-  ...
-};
+  for (age = 10; age < 100; age += 10)
+  {
+    result r (db.query<person> (q));
+    ...
+  }
   
+

At the low level, queries can be written as predicates using + the database system-native query language such as the + WHERE predicate from the SQL SELECT + statement. This language will be referred to as native query + language. At this level ODB still takes care of converting + query parameters from C++ to the database system format. Below + is the re-implementation of the above example using SQL as + the native query language:

-

X.1 Ordered Containers

+
+  query q ("first = 'John' AND age = " + query::_ref (age));
+  
-

In ODB an ordered container is any container that maintains (explicitly - or implicitly) an order of its elements in the form of an integer index. - Standard C++ containers that are ordered include std::vector - and std::list. While elements in std::set - are also kept in a specific order, this order is not based on an - integer index but rather on the relationship between elements. As - a result, std::set is not considered an ordered - container for the purpose of persistence.

+

Note that at this level we lose the static typing of + query expressions. For example, if we wrote something like this:

-

The database table for an ordered container consists of at least - three columns. The first column contains the object id of a - persistent class instance of which the container is a member. - The second column contains the element index within a container. - And the last column contains the element value. If the object - id or element value are composite, then instead of a single - column they can occupy multiple columns.

+
+  query q (query::first == 123 && query::agee < query::_ref (age));
+  
-

Consider the following persistent object as an example:

+

We would get two errors during the C++ compilation. The first would + indicate that we cannot compare query::first to an + integer and the second would pick the misspelling in + query::agee. On the other hand, if we wrote something + like this:

-#pragma db object
-class person
-{
-  ...
-private:
-  #pragma db id auto
-  unsigned long id_;
-
-  std::vector<std::string> nicknames_;
-  ...
-};
+  query q ("first = 123 AND agee = " + query::_ref (age));
   
-

The resulting database table (called person_nicknames) will - contain the object id column of type unsigned long - (called object_id), the index column of an integer type - (called index), and the value column of type - std::string (called value).

+

It would compile fine and would trigger an error only when executed + by the database system.

-

A number of ODB pragmas allow us to customize the table name, - column names, and native database types for the container both on - the per-container and per-member basis. For more information on - these pragmas, refer to Chapter 5, "ODB Pragma - Language". The following example shows some of the possible - customizations:

+

We can also combine the two query languages in a single query, for + example:

-#pragma db object
-class person
-{
-  ...
-private:
-  #pragma db id auto
-  unsigned long id_;
-
-  #pragma db table("nicknames")                        \
-             id_column ("person_id")                   \
-             index_type ("SMALLINT UNSIGNED NOT NULL") \
-             index_column ("nickname_number")          \
-             value_type ("VARCHAR(255) NOT NULL")      \
-             value_column ("nickname")
-  std::vector<std::string> nicknames_;
-  ...
-};
+  query q ("first = 'John'" + (query::age < query::_ref (age)));
   
-

While the C++ container used in the persistent class may be ordered, - sometimes we may wish to store such a container in the database without - the order information. In the example above, for instance, the order - of person's nicknames is probably not important. To instruct the ODB - compiler to ignore the order in ordered containers we can use the - unordered pragma (see Chapter 5, "ODB - Pragma Language" for details). For example:

+

4.1 ODB Query Language

+ +

An ODB query is an expression that tells the database system whether + any given object matches the desired criteria. As such, a query expression + always evaluates as true or false. At + the higher level, an expression consists of other expressions + combined with logical operators such as && (AND), + || (OR), and ! (NOT). For example:

-#pragma db object
-class person
-{
-  ...
-private:
-  #pragma db id auto
-  unsigned long id_;
+  typedef odb::query<person> query;
 
-  #pragma db unordered
-  std::vector<std::string> nicknames_;
-  ...
-};
+  query q (query::first == "John" || query::age == 31);
   
-

The table for the ordered container that is marked unordered will - miss the index column and the order in which elements are retrieved - from the database may not be the same as the order in which they - were stored.

+

At the core of every query expression lie simple expressions which + involve one or more object members, values, or parameters. To + refer to an object member we use an expression such as + query::first above. The names of members in the + query class are derived from the names of data members + in the object class by removing the common member name decorations, + such as leading and trailing underscores, the m_ prefix, + etc.

-

X.2 Set and Multiset Containers

+

In a simple expression an object member can be compared to a value, + parameter, or another member using a number of predefined operators + and functions. The following table gives an overview of the available + expressions:

-

In ODB set and multiset containers (referred to as just set - containers) are associative containers that contain elements - based on some relationship between them. A set container may - or may not guarantee a particular order of the elements that - it stores. Standard C++ containers that are considered set - containers for the purpose of persistence include - std::set and std::multiset.

- -

The database table for a set container consists of at least - two columns. The first column contains the object id of a - persistent class instance of which the container is a member. - And the second column contains the element value. If the object - id or element value are composite, then instead of a single - column they can occupy multiple columns.

+ +
6.1MySQL Database + 10.1MySQL Database - - - - + + + +
6.1.1MySQL Type Mapping
6.1.2MySQL Database Class
6.1.3Connection Factory
6.1.4MySQL Exceptions
10.1.1MySQL Type Mapping
10.1.2MySQL Database Class
10.1.3Connection Factory
10.1.4MySQL Exceptions
+ + + + + -

Consider the following persistent object as an example:

+ + + + + -
-#pragma db object
-class person
-{
-  ...
-private:
-  #pragma db id auto
-  unsigned long id_;
+    
+ + + + - std::set<std::string> emails_; - ... -}; - + + + + + -

The resulting database table (called person_emails) will - contain the object id column of type unsigned long - (called object_id) and the value column of type - std::string (called value).

+ + + + + -

A number of ODB pragmas allow us to customize the table name, - column names, and native database types for the container both on - the per-container and per-member basis. For more information on - these pragmas, refer to Chapter 5, "ODB Pragma - Language". The following example shows some of the possible - customizations:

+ + + + + -
-#pragma db object
-class person
-{
-  ...
-private:
-  #pragma db id auto
-  unsigned long id_;
+    
+ + + + - #pragma db table("emails") \ - id_column ("person_id") \ - value_type ("VARCHAR(255) NOT NULL") \ - value_column ("email") - std::set<std::string> emails_; - ... -}; - + + + + + -

X.3 Map and Multimap Containers

+ + + + + -

In ODB map and multimap containers (referred to as just set - containers) are associative containers that contain key-value - elemenst based on some relationship between keys. A map container - may or may not guarantee a particular order of the elements that - it stores. Standard C++ containers that are considered map - containers for the purpose of persistence include - std::map and std::multimap.

+ + + + + -

The database table for a map container consists of at least - three columns. The first column contains the object id of a - persistent class instance of which the container is a member. - The second column contains the element key. And the last column - contains the element value. If the object id, element key, or - element value are composite, then instead of a single column - they can occupy multiple columns.

+ + + + + +
OperatorDescriptionExample
==equalquery::age == 31
!=unequalquery::age != 31
<less thanquery::age < 31
>greater thanquery::age > 31
<=less than or equalquery::age <= 31
>=greater than or equalquery::age >= 31
in()one of the valuesquery::age.in (30, 32, 34)
in_range()one of the values in rangequery::age.in_range (begin, end)
is_null()value is NULLquery::age.is_null ()
is_not_null()value is not NULLquery::age.is_not_null ()
-

Consider the following persistent object as an example:

+

The in() function accepts a maximum of five arguments. + Use the in_range() function if you need to compare + to more than five values. This function accepts a pair of + standard C++ iterators and compares to all the values from + the begin position inclusive and until and + excluding the end position. The following + code fragment shows how we can use these functions:

-#pragma db object
-class person
-{
-  ...
-private:
-  #pragma db id auto
-  unsigned long id_;
-
-  std::map<unsigned short, float> age_weight_map_;
-  ...
-};
-  
- -

The resulting database table (called person_age_weight_map) - will contain the object id column of type unsigned long - (called object_id), the key column of type - unsigned short (called key), and the value - column of type std::string (called value).

- -

A number of ODB pragmas allow us to customize the table name, - column names, and native database types for the container both on - the per-container and per-member basis. For more information on - these pragmas, refer to Chapter 5, "ODB Pragma - Language". The following example shows some of the possible - customizations:

+ std::vector<string> names; -
-#pragma db object
-class person
-{
-  ...
-private:
-  #pragma db id auto
-  unsigned long id_;
+  names.push_back ("John");
+  names.push_back ("Jack");
+  names.push_back ("Jane");
 
-  #pragma db table("weight_map")                \
-             id_column ("person_id")            \
-             key_type ("INT UNSIGNED NOT NULL") \
-             key_column ("age")                 \
-             value_type ("DOUBLE NOT NULL")     \
-             value_column ("weight")
-  std::map<unsigned short, float> age_weight_map_;
-  ...
-};
+  query q1 (query::first.in ("John", "Jack", "Jane"));
+  query q2 (query::first.in_range (names.begin (), names.end ()));
   
-

X.4 Using Custom Containers

- -

While the ODB runtime and profile libraries provide support for - a wide range of containers, it is also easy to persist custom - container types.

- -

To achieve this you will need to implement the - container_traits class template specialization for - your container. First determine the container kind (ordered, set, - multiset, map, or multimap) for your container type. Then use a - specialization for one of the standard C++ containers found in - the common ODB runtime library (libodb) as a base - for your own implementation.

- -

Once the container traits specialization is ready for your container, - you will need to include it into the ODB compilation process using - the --odb-epilogue option and into the generated header - file with the --hxx-prologue option. As an example, - suppose we have a hash table container for which we have the traits - specialization implemented in the hashtable-traits.hxx - file. Then, we can create an ODB compiler options file for this - container and save it to hashtable.options:

-
-# Options file for the hash table container.
-#
---odb-epilogue '#include "hashtable-traits.hxx"'
---hxx-prologue '#include "hashtable-traits.hxx"'
-  
-

Now, whenever we compile a header file that uses the hashtable - container, we can pass the following option to make sure it - is recognized by the ODB compiler as a container and the traits - file is included in the generated code:

+

The operator precedence in the query expressions are the same + as for equivalent C++ operators. We can use parentheses to + make sure the expression is evaluated in the desired order. + For example:

-
---options-file hashtable.options
+  
+  query q ((query::first == "John" || query::first == "Jane") &&
+           query::age < 31);
   
- - - -

Y Relationships

- -

Relationships between persistent objects are expressed with pointers or - containers of pointers. The ODB runtime library provides built-in - support for the TR1 shared_ptr/weak_ptr, - std::auto_ptr, and raw pointers. Plus, ODB profile - libraries, that available for commonly used frameworks and libraries - (such as Boost and Qt), provide support for smart pointers found - in these frameworks and libraries. It is also easy to add support - for a custom smart pointer as discussed later in - Section Y.4, "Using Custom Smart Pointers". Any supported - smart pointer can be used in a data member as long as it can be - explicitly constructed from the canonical object pointer (@@ ref). - For example, we can use weak_ptr if the object pointer - is shared_ptr.

+

4.2 Parameter Binding

-

When an object containing a pointer to another object is loaded, - the pointed-to object is loaded as well. In some situations this - eager loading of the relationships is undesirable since it - can lead to a large number of otherwise unused objects being - instantiated from the database. To support finer control - over relationships loading, the ODB runtime and profile - libraries provide the so-called lazy versions of - the supported pointers. An object pointed-to by a lazy pointer - is not loaded automatically when the containing object is loaded. - Instead, we have to explicitly request the instantiation of the - pointed-to object. Lazy pointers are discussed in - detail in Section Y.3, "Lazy Pointers".

+

An instance of the odb::query class encapsulates two + parts of information about the query: the query expression and + the query parameters. Parameters can be bound to C++ variables + either by value or by reference.

-

As a simple example, consider the following employee-employer - relationship. Code examples presented in this chapter - will use the shared_ptr and weak_ptr - smart pointers from the TR1 (std::tr1) namespace.

+

If a parameter is bound by value, then the value for this parameter + is copied from the C++ variable to the query instance at the query + construction time. On the other hand, if a parameter is bound by + reference, then the query instance stores a reference to the + bound variable. The actual value of the parameter is only extracted + at the query execution time. Consider, for example, the following + two queries:

-#pragma db object
-class employer
-{
-  ...
-
-  #pragma db id
-  std::string name_;
-};
-
-#pragma db object
-class employee
-{
-  ...
+  string name ("John");
 
-  #pragma db id
-  unsigned long id_;
+  query q1 (query::first == query::_val (name));
+  query q2 (query::first == query::_ref (name));
 
-  std::string first_name_;
-  std::string last_name_;
+  name = "Jane";
 
-  shared_ptr<employer> employer_;
-};
+  db.query<person> (q1); // Find John.
+  db.query<person> (q2); // Find Jane.
   
-

By default, an object pointer can be NULL. To - specify that a pointer always point to a valid object we can - use the not_null pragma, for example:

+

The odb::query class provides two special functions, + _val() and _ref(), that allow us to + bind the parameter either by value or by reference, respectively. + In the ODB query language, if the binding is not specified + explicitly, the value semantic is used by default. In the + native query language, binding must always be specified + explicitly. For example:

-#pragma db object
-class employee
-{
-  ...
+  query q1 (query::age < age);                // By value.
+  query q2 (query::age < query::_val (age));  // By value.
+  query q3 (query::age < query::_ref (age));  // By reference.
 
-  #pragma db not_null
-  shared_ptr<employer> employer_;
-};
+  query q4 ("age < " + age);                  // Error.
+  query q5 ("age < " + query::_val (age));    // By value.
+  query q6 ("age < " + query::_ref (age));    // By reference.
   
-

In this case, if we perform a database operation on the - employee object and the employer_ - pointer is NULL, then the odb::null_pointer - exception will be thrown.

- -

We don't need to do anything special to establish or navigate a - relationship between two persistent objects, as shown in the - following code fragment:

- -
-// Create an employer and a few employees.
-//
-unsigned long john_id, jane_id;
-{
-  shared_ptr<employer> er (new employer ("Example Inc"));
-  shared_ptr<employee> john (new employee ("John", "Doe"));
-  shared_ptr<employee> jane (new employee ("Jane", "Doe"));
-
-  john->employer_ = er;
-  jane->employer_ = er;
-
-  transaction t (db.begin ());
-
-  db.persist (er);
-  john_id = db.persist (john);
-  jane_id = db.persist (jane);
-
-  t.commit ();
-}
+  

A query that only has by-value parameters does not depend on any + other variables and is self-sufficient once constructed. A query + that has one or more by-reference parameters depends on the + bound variables until the query is executed. If one such variable + goes out of scope and we execute the query, the behavior is + undefined.

-// Load a few employee objects and print their employer. -// -{ - session s; - transaction t (db.begin ()); +

4.3 Executing a Query

- shared_ptr<employee> john (db.load<employee> (john_id)); - shared_ptr<employee> jane (db.load<employee> (jane_id)); +

Once we have the query instance ready and by-reference parameters + initialized, we can execute the query using the + database::query() function template. It has two + overloaded versions:

- cout << john->employer_->name_ << endl; - cout << jane->employer_->name_ << endl; +
+  template <typename T>
+  result<T>
+  query (bool cache = true);
 
-  t.commit ();
-}
+  template <typename T>
+  result<T>
+  query (const odb::query<T>&, bool cache = true);
   
-

The only notable line in the above code is the creation of a - session before the second transaction starts. As discussed in - Chapter @@ ref, a session acts as a cache of persistent objects. - By creating a session before loading the employee - objects we make sure that their employer_ pointers - point to the same employer object. Without a - session, each employee would have ended up pointing - to its own, private instance of the Example Inc employer.

- -

As a general guideline, you should use a session when loading - objects that have pointers to other persistent objects. A - session makes sure that for a given object id, a single instance - is shared among all other objects that relate to it.

+

The first query() function is used to return all the + persistent objects of a given type stored in the database. + The second function uses the passed query instance to only return + objects matching the query criteria. The cache argument + determines whether the objects' states should be cached in the + application's memory or if they should be returned by the database + system one by one as the iteration over the result progresses. The + result caching is discussed in detail in the next section.

-

We can also use the data members from the pointed-to - objects in database queries (Chapter 4, "Querying the - Database"). For each pointer in a persistent class, the query - class defines a nested scope containing members corresponding - to the data members in the pointed-to object. For example, the - query class for the employee object contains - the employer scope (derived from the employer_ - pointer) which in turn contains the name member - (derived from the employer::name_ data member of the - pointed-to object). As a result, we can use the - query::employer::name expression while querying - the database for the employee objects. For example, - the following transaction finds all the employees of Example Inc - that have the Doe last name:

+

When calling the query() function, we have to + explicitly specify the object type we are querying. For example:

-typedef odb::query<employee> query;
-typedef odb::result<employee> result;
+  typedef odb::query<person> query;
+  typedef odb::result<person> result;
 
-session s;
-transaction t (db.begin ());
+  result all (db.query<person> ());
+  result johns (db.query<person> (query::first == "John"));
+  
-result r (db->query<employee> ( - query::employer::name == "Example Inc" && query::last == "Doe")); +

Note that it is not required to explicitly create a named + query variable before executing it. For example, the following + two queries are equivalent:

-for (result::iterator i (r.begin ()); i != r.end (); ++i) - cout << i->first_ << " " << i->last_ << endl; +
+  query q (query::first == "John");
 
-t.commit ();
+  result r1 (db.query<person> (q));
+  result r1 (db.query<person> (query::first == "John"));
   
+

Normally we would create a named query instance if we are + planning to run the same query multiple times and would use the + in-line version for those that are executed only once.

-

An important concept to keep in mind when working with object - relationships is the independence of persistent objects. In particular, - when an object containing a pointer to another object is made persistent - or is updated, the pointed-to object is not automatically persisted - or updated. Rather, only a reference to the object (in the form of the - object id) is stored for the pointed-to object in the database. - The pointed-to object itself is a separate entity and should - be made persistent or updated independently.

+

It is also possible to create queries from other queries by + combining them using logical operators. For example:

-

When persisting or updating an object containing a pointer to another - object, the pointed-to object must have a valid object id. This, - however, may not always be easy to achieve in complex relationships that - involve objects with automatically assigned identifiers. In such - cases it may be necessary to first persist an object with a pointer - set to NULL and then, once the pointed-to object is - made persistent and its identifier assigned, set the pointer - to the correct value and update the object in the database.

+
+result
+find_minors (database& db, const query& name_query)
+{
+  return db.query<person> (name_query && query::age < 18);
+}
 
-  

Persistent object relationships can be divided into two groups: - unidirectional and bidirectional. Each group in turn contains - several configurations that vary depending on the cardinality - of the sides of the relationship. All possible unidirectional - and bidirectional configurations are discussed in the following - sections.

+result r (find_underage (db, query::first == "John")); +
-

Y.1 Unidirectional Relationships

+

4.4 Query Result

-

In unidirectional relationships we are only interested in navigating - from object to object in one direction. Because there is no interest - in navigating in the opposite direction, the cardinality of the other - end of the relationship is unimportant. As a result, there are only - two possible unidirectional relationships: to-one and to-many. Each - of these relationships is described in the following sections. For - sample code that shows how to work with these relationships, refer - to the relationship example in the odb-examples - package.

+

The result of executing a query is zero, one, or more objects + matching the query criteria. The result is returned as an instance + of the odb::result class template, for example:

-

Y.1.1 To-One Relationships

+
+  typedef odb::query<person> query;
+  typedef odb::result<person> result;
 
-  

An example of a unidirectional to-one relationship is the - employee-employer relationship (an employee has one employer). - The following persistent C++ classes model this relationship:

+ result johns (db.query<person> (query::first == "John")); +
+ +

It is best to view an instance of odb::result + as a handle to a stream, such as a file stream. While we can + make a copy of a result or assign one result to another, the + two instances will refer to the same result stream. Advancing + the current position in one instance will also advance it in + another. The result instance is only usable within the transaction + it was created in. Trying to manipulate the result after the + transaction has terminated leads to undefined behavior.

+ +

The odb::result class template conforms to the + standard C++ sequence requirements and has the following + interface:

-#pragma db object
-class employer
+namespace odb
 {
-  ...
+  template <typename T>
+  class result
+  {
+  public:
+    typedef odb::result_iterator<T> iterator;
 
-  #pragma db id
-  std::string name_;
-};
+  public:
+    result ();
 
-#pragma db object
-class employee
-{
-  ...
+    result (const result&);
 
-  #pragma db id
-  unsigned long id_;
+    result&
+    operator= (const result&);
 
-  #pragma db not_null
-  shared_ptr<employer> employer_;
-};
-  
+ void + swap (result&) -

The corresponding database tables look like this:

+ public: + iterator + begin (); -
-CREATE TABLE employer (
-  name VARCHAR (255) NOT NULL PRIMARY KEY);
+    iterator
+    end ();
 
-CREATE TABLE employee (
-  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
-  employer VARCHAR (255) NOT NULL REFERENCES employer (name));
+  public:
+    void
+    cache ();
+
+    bool
+    empty () const;
+
+    std::size_t
+    size () const;
+  };
+}
   
-

Y.1.2 To-Many Relationships

+

The default constructor creates an empty result set. The + cache() function caches the returned objects' + state in the application's memory. We have already mentioned + result caching when we talked about query execution. As you + may remember the database::query() function + caches the result unless instructed not to by the caller. + The cache() function allows us to + cache the result at a later stage if it wasn't already + cached during query execution.

-

An example of a unidirectional to-many relationship is the - employee-project relationship (an employee can be involved - in multiple projects). The following persistent C++ classes - model this relationship:

+

If the result is cached, the database state of all the returned + objects is stored in the application's memory. Note that + the actual objects are still only instantiated on demand + during result iteration. It is the raw database state that + is cached in memory. In contrast, for uncached results + the object's state is sent by the database system one object + at a time as the iteration progresses.

-
-#pragma db object
-class project
-{
-  ...
+  

Uncached results can improve the performance of both the application + and the database system in situations where we have a large + number of objects in the result or if we will only examine + a small portion of the returned objects. However, uncached + results have a number of limitations. There can only be one + uncached result in a transaction. Creating another result + (cached or uncached) by calling database::query() + will invalidate the existing uncached result. Furthermore, + calling any other database functions, such as update() + or erase() will also invalidate the uncached result.

- #pragma db id - std::string name_; -}; +

The empty() function returns true if + there are no objects in the result and false otherwise. + The size() function can only be called for cached results. + It returns the number of objects in the result. If we call this + function on an uncached result, the odb::result_not_cached + exception is thrown.

-#pragma db object -class employee -{ - ... +

To iterate over the objects in a result we use the + begin() and end() functions + together with the odb::result<T>::iterator + type, for example:

- #pragma db id - unsigned long id_; +
+  result r (db.query<person> (query::first == "John"));
 
-  #pragma db not_null unordered
-  std::vector<shared_ptr<project> > projects_;
-};
+  for (result::iterator i (r.begin ()); i != r.end (); ++i)
+  {
+    ...
+  }
   
-

The corresponding database tables look like this:

+

The result iterator is an input iterator which means that the + only two position operations that it supports are to move to the + next object and to determine whether the end of the result stream + has been reached. In fact, the result iterator can only be in two + states: the current position and the end position. If we have + two iterators pointing to the current position and then we + advance one of them, the other will advance as well. This, + for example, means that it doesn't make sense to store an + iterator that points to some object of interest in the result + stream with the intent of dereferencing it after the iteration + is over. Instead, we would need to store the object itself.

-
-CREATE TABLE project (
-  name VARCHAR (255) NOT NULL PRIMARY KEY);
+  

The result iterator has the following dereference functions + that can be used to access the pointed-to object:

-CREATE TABLE employee ( - id BIGINT UNSIGNED NOT NULL PRIMARY KEY); +
+namespace odb
+{
+  template <typename T>
+  class result_iterator
+  {
+  public:
+    T*
+    operator-> () const;
 
-CREATE TABLE employee_projects (
-  object_id BIGINT UNSIGNED NOT NULL,
-  value VARCHAR (255) NOT NULL REFERENCES project (name));
+    T&
+    operator* () const;
+
+    typename object_traits<T>::pointer_type
+    load ();
+
+    void
+    load (T& x);
+  };
+}
   
-

To obtain a more canonical database schema, the names of tables - and columns above can be customized using ODB pragmas - (Chapter 5, "ODB Pragma Language"). For example:

+

When we call the * or -> operator, + the iterator will allocate a new instance of the object class + in the dynamic memory, load its state from the database + state, and return a reference or pointer to the new instance. The + iterator maintains the ownership of the returned object and will + return the same pointer for subsequent calls to either of these + operators until it is advanced to the next object or we call + the first load() function (see below). For example:

-#pragma db object
-class employee
-{
-  ...
+  result r (db.query<person> (query::first == "John"));
 
-  #pragma db not_null unordered \
-             id_column("employee_id") value_column("project_name")
-  std::vector<shared_ptr<project> > projects_;
-};
+  for (result::iterator i (r.begin ()); i != r.end ();)
+  {
+    cout << i->last () << endl; // Create an object.
+    person& p (*i);             // Reference to the same object.
+    cout << p.age () << endl;
+    ++i;                        // Free the object.
+  }
   
-

The resulting employee_projects table would then - look like this:

+

The overloaded result_iterator::load() functions are + similar to database::load(). The first function + returns a dynamically allocated instance of the current + object. As an optimization, if the iterator already owns an object + as a result of an earlier + call to the * or -> operator, then it + relinquishes the ownership of this object and returns it instead. + This allows us to write code like this without worrying about + a double allocation:

-
-CREATE TABLE employee_projects (
-  employee_id BIGINT UNSIGNED NOT NULL,
-  project_name VARCHAR (255) NOT NULL REFERENCES project (name));
-  
+
+  result r (db.query<person> (query::first == "John"));
 
+  for (result::iterator i (r.begin ()); i != r.end (); ++i)
+  {
+    if (i->last == "Doe")
+    {
+      auto_ptr p (i.load ());
+      ...
+    }
+  }
+  
-

Y.2 Bidirectional Relationships

+

Note, however, that because of this optimization, a subsequent + to load() call to the * or -> + operator results in the allocation of a new object.

-

In bidirectional relationships we are interested in navigating - from object to object in both directions. As a result, each - object class in a relationship contains a pointer to the other - object. If smart pointers are used, then a weak pointer should - be used as one of the pointers to avoid ownership cycles. For - example:

+

The second load() function allows + us to load the current object's state into an existing instance. + For example:

-class employee;
-
-#pragma db object
-class position
-{
-  ...
+  result r (db.query<person> (query::first == "John"));
 
-  #pragma db id
-  unsigned long id_;
+  person p;
+  for (result::iterator i (r.begin ()); i != r.end (); ++i)
+  {
+    i.load (p);
+    cout << p.last () << endl;
+    cout << i.age () << endl;
+  }
+  
- weak_ptr<employee> employee_; -}; -#pragma db object -class employee -{ - ... + - #pragma db id - unsigned long id_; +

5 Containers

- #pragma db not_null - shared_ptr<position> position_; -}; -
+

The ODB runtime library provides built-in persistence support for + all commonly used standard C++ containers, namely, + std::vector, std::list, std::set, + std::multiset, std::map, and + std::multimap. Plus, ODB profile libraries, that are + available for commonly used frameworks and libraries (such as Boost + and Qt), provide persistence support for containers found in these + frameworks and libraries. It is also easy to persist custom + container types as discussed later in Section 5.4, + "Using Custom Containers".

-

Note that when we establish a bidirectional relationship, we - have to set both pointers consistently. One way to make sure - that a relationship is always in a consistent state is to - provide a single function that updates both pointers at the - same time. For example:

+

We don't need to do anything special to declare a member of a + container type in a persistent class. For example:

 #pragma db object
-class position: public enable_shared_from_this<position>
+class person
 {
   ...
-
-  void
-  fill (shared_ptr<employee> e)
-  {
-    employee_ = e;
-    e->positions_ = shared_from_this ();
-  }
-
 private:
-  weak_ptr<employee> employee_;
-};
-
-#pragma db object
-class employee
-{
+  std::vector<std::name> nicknames_;
   ...
-
-private:
-  friend class position;
-
-  #pragma db not_null
-  shared_ptr<position> position_;
 };
   
+

A data member in a persistent class that is of a container type + behaves like a value type. That is, when an object is made persistent, + the elements of the container are store in the database. Similarly, + when a persistent object is loaded from the database, the contents + of the container are automatically loaded as well.

-

Above, to model a bidirectional relationship in persistent classes, - we used two pointers, one in each object. While this is a natural - representation in C++, it does not translate to a canonical - relational model. Consider the database schema generated for - the above two classes:

- -
-CREATE TABLE position (
-  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
-  employee BIGINT UNSIGNED REFERENCES employee (id));
-
-CREATE TABLE employee (
-  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
-  position BIGINT UNSIGNED NOT NULL REFERENCES position (id));
-  
+

While an ordinary member is mapped to one or more columns in the + object's table, a member of a container type is mapped to a seperate + table. The exact schema of such a table depends on the kind of + a container. ODB defines the following container kinds: ordered, + set, multiset, map, and multimap. The container kinds and the + contents of the tables to which they are mapped are discussed + in detail in the following sections.

-

While this database schema is valid, it is unconventional. We have - a reference from a row in the position table to a row - in the employee table. We also have a reference - from this same row in the employee table back to - the row in the position table. From the relational - point of view, one of these references is redundant since - in SQL we can easily navigate in both directions using just one - of these references.

+

Containers in ODB can contain simple value types, composite value + types (see @@), and pointers to objects (see @@). Containers of + containers, either directly or indirectly via a composite value + type, are not allowed. A key in map and multimap containers can + be a simple or composite value type but not a pointer to an object. + An index in the ordered container should be a simple integer type.

-

To eliminate redundant database schema references we can use the - inverse pragma (Section 5.3.7, - "inverse") which tells the ODB compiler that - a pointer is the inverse side of a bidirectional relationship. - Either side of a relationship can be made inverse. For example:

+

The value type in the ordered, set, and map containers as well as + the key type in the map containers should be default-constructible. + The default constructor in these types can be made private in which + case the odb::access class should be made a friend of + the value or key type. For example:

-#pragma db object
-class position
+#pragma db value
+class name
 {
+public:
+  name (const std::string&, const std::string&);
+  ...
+private:
+  friend class odb::access;
+  name ();
   ...
-
-  #pragma db inverse(position_)
-  weak_ptr<employee> employee_;
 };
 
 #pragma db object
-class employee
+class person
 {
   ...
+private:
+  #pragma db id auto
+  unsigned long id_;
 
-  #pragma db not_null
-  shared_ptr<position> position_;
+  std::vector<name> aliases_;
+  ...
 };
   
-

The resulting database schema looks like this:

- -
-CREATE TABLE position (
-  id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
-
-CREATE TABLE employee (
-  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
-  position BIGINT UNSIGNED NOT NULL REFERENCES position (id));
-  
-

As you can see, an inverse member does not have a corresponding - column (or table, in case of an inverse container of pointers) - and, from the point of view of database operations, is effectively - read-only. The only way to change a bidirectional relationship - with an inverse side is to set its direct (non-inverse) - pointer. Also note that an ordered container (Section - X.1, "Ordered Containers") of pointers that is an inverse side - of a bidirectional relationship is always treated as unordered - (Section 5.3.8, "unordered") - because the contents of such a container are implicitly built from - the direct side of the relationship which does not contain the - element order (index).

+

5.1 Ordered Containers

-

There are three distinct bidirectional relationships that we - will cover in the following sections: one-to-one, one-to-many, - and many-to-many. We will only talk about bidirectional - relationships with inverse sides since they result in canonical - database schemas. For sample code that shows how to work with - these relationships, refer to the inverse example - in the odb-examples package.

+

In ODB an ordered container is any container that maintains (explicitly + or implicitly) an order of its elements in the form of an integer index. + Standard C++ containers that are ordered include std::vector + and std::list. While elements in std::set + are also kept in a specific order, this order is not based on an + integer index but rather on the relationship between elements. As + a result, std::set is not considered an ordered + container for the purpose of persistence.

-

Y.2.1 One-to-One Relationships

+

The database table for an ordered container consists of at least + three columns. The first column contains the object id of a + persistent class instance of which the container is a member. + The second column contains the element index within a container. + And the last column contains the element value. If the object + id or element value are composite, then instead of a single + column they can occupy multiple columns.

-

An example of a bidirectional one-to-one relationship is the - presented above employee-position relationship (an employee - fills one position and a position is filled by one employee). - The following persistent C++ classes model this relationship:

+

Consider the following persistent object as an example:

-class employee;
-
 #pragma db object
-class position
+class person
 {
   ...
-
-  #pragma db id
+private:
+  #pragma db id auto
   unsigned long id_;
 
-  #pragma db inverse(position_)
-  weak_ptr<employee> employee_;
+  std::vector<std::string> nicknames_;
+  ...
 };
+  
+ +

The resulting database table (called person_nicknames) will + contain the object id column of type unsigned long + (called object_id), the index column of an integer type + (called index), and the value column of type + std::string (called value).

+ +

A number of ODB pragmas allow us to customize the table name, + column names, and native database types for the container both on + the per-container and per-member basis. For more information on + these pragmas, refer to Chapter 9, "ODB Pragma + Language". The following example shows some of the possible + customizations:

+
 #pragma db object
-class employee
+class person
 {
   ...
-
-  #pragma db id
+private:
+  #pragma db id auto
   unsigned long id_;
 
-  #pragma db not_null
-  shared_ptr<position> position_;
+  #pragma db table("nicknames")                        \
+             id_column ("person_id")                   \
+             index_type ("SMALLINT UNSIGNED NOT NULL") \
+             index_column ("nickname_number")          \
+             value_type ("VARCHAR(255) NOT NULL")      \
+             value_column ("nickname")
+  std::vector<std::string> nicknames_;
+  ...
 };
   
-

The corresponding database tables look like this:

+

While the C++ container used in the persistent class may be ordered, + sometimes we may wish to store such a container in the database without + the order information. In the example above, for instance, the order + of person's nicknames is probably not important. To instruct the ODB + compiler to ignore the order in ordered containers we can use the + unordered pragma (see Chapter 9, "ODB + Pragma Language" for details). For example:

-
-CREATE TABLE position (
-  id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
+  
+#pragma db object
+class person
+{
+  ...
+private:
+  #pragma db id auto
+  unsigned long id_;
 
-CREATE TABLE employee (
-  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
-  position BIGINT UNSIGNED NOT NULL REFERENCES position (id));
+  #pragma db unordered
+  std::vector<std::string> nicknames_;
+  ...
+};
   
-

If instead the other side of this relationship is made inverse, - then the database tables will change as follows:

+

The table for the ordered container that is marked unordered will + miss the index column and the order in which elements are retrieved + from the database may not be the same as the order in which they + were stored.

-
-CREATE TABLE position (
-  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
-  employee BIGINT UNSIGNED REFERENCES employee (id));
+  

5.2 Set and Multiset Containers

-CREATE TABLE employee ( - id BIGINT UNSIGNED NOT NULL PRIMARY KEY); -
+

In ODB set and multiset containers (referred to as just set + containers) are associative containers that contain elements + based on some relationship between them. A set container may + or may not guarantee a particular order of the elements that + it stores. Standard C++ containers that are considered set + containers for the purpose of persistence include + std::set and std::multiset.

-

Y.2.2 One-to-Many Relationships

+

The database table for a set container consists of at least + two columns. The first column contains the object id of a + persistent class instance of which the container is a member. + And the second column contains the element value. If the object + id or element value are composite, then instead of a single + column they can occupy multiple columns.

-

An example of a bidirectional one-to-many relationship is the - employer-employee relationship (an employer has multiple - employees and an employee is employed by one employer). - The following persistent C++ classes model this relationship:

+

Consider the following persistent object as an example:

-class employee;
-
 #pragma db object
-class employer
+class person
 {
   ...
+private:
+  #pragma db id auto
+  unsigned long id_;
 
-  #pragma db id
-  std::string name_;
-
-  #pragma db not_null inverse(employer_)
-  std::vector<weak_ptr<employee> > employees_
+  std::set<std::string> emails_;
+  ...
 };
+  
+ +

The resulting database table (called person_emails) will + contain the object id column of type unsigned long + (called object_id) and the value column of type + std::string (called value).

+ +

A number of ODB pragmas allow us to customize the table name, + column names, and native database types for the container both on + the per-container and per-member basis. For more information on + these pragmas, refer to Chapter 9, "ODB Pragma + Language". The following example shows some of the possible + customizations:

+
 #pragma db object
-class employee
+class person
 {
   ...
-
-  #pragma db id
+private:
+  #pragma db id auto
   unsigned long id_;
 
-  #pragma db not_null
-  shared_ptr<employer> employer_;
+  #pragma db table("emails")                      \
+             id_column ("person_id")              \
+             value_type ("VARCHAR(255) NOT NULL") \
+             value_column ("email")
+  std::set<std::string> emails_;
+  ...
 };
   
-

The corresponding database tables differ significantly depending - on which side of the relationship is made inverse. If the one - side (employer) is inverse as in the code - above, then the resulting database schema looks like this:

- -
-CREATE TABLE employer (
-  name VARCHAR (255) NOT NULL PRIMARY KEY);
-
-CREATE TABLE employee (
-  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
-  employer VARCHAR (255) NOT NULL REFERENCES employer (name));
-  
- -

If instead the many side (employee) of this - relationship is made inverse, then the database tables will change - as follows:

- -
-CREATE TABLE employer (
-  name VARCHAR (255) NOT NULL PRIMARY KEY);
-
-CREATE TABLE employer_employees (
-  object_id VARCHAR (255) NOT NULL,
-  value BIGINT UNSIGNED NOT NULL REFERENCES employee (id));
+  

5.3 Map and Multimap Containers

-CREATE TABLE employee ( - id BIGINT UNSIGNED NOT NULL PRIMARY KEY); -
+

In ODB map and multimap containers (referred to as just set + containers) are associative containers that contain key-value + elemenst based on some relationship between keys. A map container + may or may not guarantee a particular order of the elements that + it stores. Standard C++ containers that are considered map + containers for the purpose of persistence include + std::map and std::multimap.

-

Y.2.3 Many-to-Many Relationships

+

The database table for a map container consists of at least + three columns. The first column contains the object id of a + persistent class instance of which the container is a member. + The second column contains the element key. And the last column + contains the element value. If the object id, element key, or + element value are composite, then instead of a single column + they can occupy multiple columns.

-

An example of a bidirectional many-to-many relationship is the - employee-project relationship (an employee can work on multiple - projects and a project can have multiple participating employees). - The following persistent C++ classes model this relationship:

+

Consider the following persistent object as an example:

-class employee;
-
 #pragma db object
-class project
+class person
 {
   ...
+private:
+  #pragma db id auto
+  unsigned long id_;
 
-  #pragma db id
-  std::string name_;
-
-  #pragma db not_null inverse(projects_)
-  std::vector<weak_ptr<employee> > employees_;
+  std::map<unsigned short, float> age_weight_map_;
+  ...
 };
+  
+ +

The resulting database table (called person_age_weight_map) + will contain the object id column of type unsigned long + (called object_id), the key column of type + unsigned short (called key), and the value + column of type std::string (called value).

+

A number of ODB pragmas allow us to customize the table name, + column names, and native database types for the container both on + the per-container and per-member basis. For more information on + these pragmas, refer to Chapter 9, "ODB Pragma + Language". The following example shows some of the possible + customizations:

+ +
 #pragma db object
-class employee
+class person
 {
   ...
-
-  #pragma db id
+private:
+  #pragma db id auto
   unsigned long id_;
 
-  #pragma db not_null unordered
-  std::vector<shared_ptr<project> > projects_;
+  #pragma db table("weight_map")                \
+             id_column ("person_id")            \
+             key_type ("INT UNSIGNED NOT NULL") \
+             key_column ("age")                 \
+             value_type ("DOUBLE NOT NULL")     \
+             value_column ("weight")
+  std::map<unsigned short, float> age_weight_map_;
+  ...
 };
   
-

The corresponding database tables look like this:

+

5.4 Using Custom Containers

-
-CREATE TABLE project (
-  name VARCHAR (255) NOT NULL PRIMARY KEY);
+  

While the ODB runtime and profile libraries provide support for + a wide range of containers, it is also easy to persist custom + container types.

-CREATE TABLE employee ( - id BIGINT UNSIGNED NOT NULL PRIMARY KEY); +

To achieve this you will need to implement the + container_traits class template specialization for + your container. First determine the container kind (ordered, set, + multiset, map, or multimap) for your container type. Then use a + specialization for one of the standard C++ containers found in + the common ODB runtime library (libodb) as a base + for your own implementation.

-CREATE TABLE employee_projects ( - object_id BIGINT UNSIGNED NOT NULL, - value VARCHAR (255) NOT NULL REFERENCES project (name)); +

Once the container traits specialization is ready for your container, + you will need to include it into the ODB compilation process using + the --odb-epilogue option and into the generated header + file with the --hxx-prologue option. As an example, + suppose we have a hash table container for which we have the traits + specialization implemented in the hashtable-traits.hxx + file. Then, we can create an ODB compiler options file for this + container and save it to hashtable.options:

+ +
+# Options file for the hash table container.
+#
+--odb-epilogue '#include "hashtable-traits.hxx"'
+--hxx-prologue '#include "hashtable-traits.hxx"'
   
-

If instead the other side of this relationship is made inverse, - then the database tables will change as follows:

+

Now, whenever we compile a header file that uses the hashtable + container, we can pass the following option to make sure it + is recognized by the ODB compiler as a container and the traits + file is included in the generated code:

+ +
+--options-file hashtable.options
+  
+ + + -
-CREATE TABLE project (
-  name VARCHAR (255) NOT NULL PRIMARY KEY);
 
-CREATE TABLE project_employees (
-  object_id VARCHAR (255) NOT NULL,
-  value BIGINT UNSIGNED NOT NULL REFERENCES employee (id));
+  

6 Relationships

-CREATE TABLE employee ( - id BIGINT UNSIGNED NOT NULL PRIMARY KEY); -
+

Relationships between persistent objects are expressed with pointers or + containers of pointers. The ODB runtime library provides built-in + support for the TR1 shared_ptr/weak_ptr, + std::auto_ptr, and raw pointers. Plus, ODB profile + libraries, that available for commonly used frameworks and libraries + (such as Boost and Qt), provide support for smart pointers found + in these frameworks and libraries. It is also easy to add support + for a custom smart pointer as discussed later in + Section 6.4, "Using Custom Smart Pointers". Any supported + smart pointer can be used in a data member as long as it can be + explicitly constructed from the canonical object pointer (@@ ref). + For example, we can use weak_ptr if the object pointer + is shared_ptr.

-

Y.3 Lazy Pointers

+

When an object containing a pointer to another object is loaded, + the pointed-to object is loaded as well. In some situations this + eager loading of the relationships is undesirable since it + can lead to a large number of otherwise unused objects being + instantiated from the database. To support finer control + over relationships loading, the ODB runtime and profile + libraries provide the so-called lazy versions of + the supported pointers. An object pointed-to by a lazy pointer + is not loaded automatically when the containing object is loaded. + Instead, we have to explicitly request the instantiation of the + pointed-to object. Lazy pointers are discussed in + detail in Section 6.3, "Lazy Pointers".

-

Consider again the bidirectional, one-to-many employer-employee - relationship that was presented earlier in this chapter:

+

As a simple example, consider the following employee-employer + relationship. Code examples presented in this chapter + will use the shared_ptr and weak_ptr + smart pointers from the TR1 (std::tr1) namespace.

-class employee;
-
 #pragma db object
 class employer
 {
@@ -3535,9 +3498,6 @@ class employer
 
   #pragma db id
   std::string name_;
-
-  #pragma db not_null inverse(employer_)
-  std::vector<weak_ptr<employee> > employees_;
 };
 
 #pragma db object
@@ -3548,1357 +3508,1449 @@ class employee
   #pragma db id
   unsigned long id_;
 
-  #pragma db not_null
+  std::string first_name_;
+  std::string last_name_;
+
   shared_ptr<employer> employer_;
 };
   
-

Consider also the following transaction which obtains the employer - name given the employee id:

+

By default, an object pointer can be NULL. To + specify that a pointer always point to a valid object we can + use the not_null pragma, for example:

-unsigned long id = ...
-string name;
-
-session s;
-transaction t (db.begin ());
-
-shared_ptr<employee> e (db.load<employee> (id));
-name = e->employer_->name_;
+#pragma db object
+class employee
+{
+  ...
 
-t.commit ();
+  #pragma db not_null
+  shared_ptr<employer> employer_;
+};
   
-

While this transaction looks very simple, it actually does a lot more - than what meets the eye and is necessary. Consider what happens when - we load the employee object: the employer_ - pointer is also automatically loaded which means the employer - object corresponding to this employee is also loaded. But the - employer object in turn contains the list of pointers - to all the employees, which are also loaded. A a result, when object - relationships are involved, a simple transaction like the above can - load many more objects than is necessary.

+

In this case, if we perform a database operation on the + employee object and the employer_ + pointer is NULL, then the odb::null_pointer + exception will be thrown.

-

To overcome this problem ODB offers finer grained control over - the relationship loading in the form of lazy pointers. A lazy - pointer does not automatically load the pointed-to object - when the containing object is loaded. Instead, we have to - explicitly load the pointed-to object if and when we need to - access it.

+

We don't need to do anything special to establish or navigate a + relationship between two persistent objects, as shown in the + following code fragment:

-

The ODB runtime library provides lazy counterparts for all the - supported pointers, namely: odb::lazy_shared_ptr and - odb::lazy_weak_ptr for TR1 shared_ptr and - weak_ptr, odb::lazy_auto_ptr for - std::auto_ptr, and odb::lazy_ptr for raw - pointers. The ODB profile libraries provide lazy pointer - implementations for smart pointers from popular frameworks and - libraries.

+
+// Create an employer and a few employees.
+//
+unsigned long john_id, jane_id;
+{
+  shared_ptr<employer> er (new employer ("Example Inc"));
+  shared_ptr<employee> john (new employee ("John", "Doe"));
+  shared_ptr<employee> jane (new employee ("Jane", "Doe"));
 
-  

While we will discuss the interface of lazy pointers in more detail - shortly, the most commonly used extra function provided by these - pointers is load(). This function loads the - pointed-to object if it hasn't already been loaded. After - the call to this function, the lazy pointer can be used - in the the same way as its eager counterpart. The load() - function also returns the eager pointer, in case you need to pass - it around. For a lazy weak pointer, the - load() function also locks the pointer.

+ john->employer_ = er; + jane->employer_ = er; -

The following example shows how we can change our employer-employee - relationship to use lazy pointers. Here we choose to use lazy pointers - for both sides of the relationship.

+ transaction t (db.begin ()); -
-class employee;
+  db.persist (er);
+  john_id = db.persist (john);
+  jane_id = db.persist (jane);
 
-#pragma db object
-class employer
+  t.commit ();
+}
+
+// Load a few employee objects and print their employer.
+//
 {
-  ...
+  session s;
+  transaction t (db.begin ());
 
-  #pragma db not_null inverse(employer_)
-  std::vector<lazy_weak_ptr<employee> > employees_;
-};
+  shared_ptr<employee> john (db.load<employee> (john_id));
+  shared_ptr<employee> jane (db.load<employee> (jane_id));
 
-#pragma db object
-class employee
-{
-  ...
+  cout << john->employer_->name_ << endl;
+  cout << jane->employer_->name_ << endl;
 
-  #pragma db not_null
-  lazy_shared_ptr<employer> employer_;
-};
+  t.commit ();
+}
   
-

And the transaction is changed like this:

+

The only notable line in the above code is the creation of a + session before the second transaction starts. As discussed in + Chapter @@ ref, a session acts as a cache of persistent objects. + By creating a session before loading the employee + objects we make sure that their employer_ pointers + point to the same employer object. Without a + session, each employee would have ended up pointing + to its own, private instance of the Example Inc employer.

+ +

As a general guideline, you should use a session when loading + objects that have pointers to other persistent objects. A + session makes sure that for a given object id, a single instance + is shared among all other objects that relate to it.

+ +

We can also use the data members from the pointed-to + objects in database queries (Chapter 4, "Querying the + Database"). For each pointer in a persistent class, the query + class defines a nested scope containing members corresponding + to the data members in the pointed-to object. For example, the + query class for the employee object contains + the employer scope (derived from the employer_ + pointer) which in turn contains the name member + (derived from the employer::name_ data member of the + pointed-to object). As a result, we can use the + query::employer::name expression while querying + the database for the employee objects. For example, + the following transaction finds all the employees of Example Inc + that have the Doe last name:

-unsigned long id = ...
-string name;
+typedef odb::query<employee> query;
+typedef odb::result<employee> result;
 
 session s;
 transaction t (db.begin ());
 
-shared_ptr<employee> e (db.load<employee> (id));
-e->employer_.load ();
-name = e->employer_->name_;
+result r (db->query<employee> (
+  query::employer::name == "Example Inc" && query::last == "Doe"));
+
+for (result::iterator i (r.begin ()); i != r.end (); ++i)
+  cout << i->first_ << " " << i->last_ << endl;
 
 t.commit ();
   
-

As a general guideline we recommend that you make at least one side - of a bidirectional relationship lazy, especially for relationships - with a many side.

+

An important concept to keep in mind when working with object + relationships is the independence of persistent objects. In particular, + when an object containing a pointer to another object is made persistent + or is updated, the pointed-to object is not automatically persisted + or updated. Rather, only a reference to the object (in the form of the + object id) is stored for the pointed-to object in the database. + The pointed-to object itself is a separate entity and should + be made persistent or updated independently.

-

A lazy pointer implementation mimics the interface of its eager - counterpart which can be used once the pointer is loaded. It also - adds a number of additional functions that are specific to the - lazy loading functionality. Overall, the interface of a lazy - pointer follows this general outline:

+

When persisting or updating an object containing a pointer to another + object, the pointed-to object must have a valid object id. This, + however, may not always be easy to achieve in complex relationships that + involve objects with automatically assigned identifiers. In such + cases it may be necessary to first persist an object with a pointer + set to NULL and then, once the pointed-to object is + made persistent and its identifier assigned, set the pointer + to the correct value and update the object in the database.

-
-template <class T>
-class lazy_ptr
-{
-public:
-  //
-  // The eager pointer interface.
-  //
+  

Persistent object relationships can be divided into two groups: + unidirectional and bidirectional. Each group in turn contains + several configurations that vary depending on the cardinality + of the sides of the relationship. All possible unidirectional + and bidirectional configurations are discussed in the following + sections.

- // Initialization/assignment from an eager pointer. - // -public: - template <class Y> lazy_ptr (const eager_ptr<Y>&); - template <class Y> lazy_ptr& operator= (const eager_ptr<Y>&); +

6.1 Unidirectional Relationships

- // Lazy loading interface. - // -public: - bool loaded () const; - eager_ptr<T> load () const; +

In unidirectional relationships we are only interested in navigating + from object to object in one direction. Because there is no interest + in navigating in the opposite direction, the cardinality of the other + end of the relationship is unimportant. As a result, there are only + two possible unidirectional relationships: to-one and to-many. Each + of these relationships is described in the following sections. For + sample code that shows how to work with these relationships, refer + to the relationship example in the odb-examples + package.

- // Unload the pointer. For transient objects this function is - // equivalent to reset(). - // - void unload () const; +

6.1.1 To-One Relationships

- // Initialization with a persistent loaded object. - // - template <class Y> lazy_ptr (database&, Y*); - template <class Y> lazy_ptr (database&, const eager_ptr<Y>&); +

An example of a unidirectional to-one relationship is the + employee-employer relationship (an employee has one employer). + The following persistent C++ classes model this relationship:

- template <class Y> void reset (database&, Y*); - template <class Y> void reset (database&, const eager_ptr<Y>&); +
+#pragma db object
+class employer
+{
+  ...
 
-  // Initialization with a persistent unloaded object.
-  //
-  template <class ID> lazy_ptr (database&, const ID&);
+  #pragma db id
+  std::string name_;
+};
 
-  template <class ID> void reset (database&, const ID&);
+#pragma db object
+class employee
+{
+  ...
 
-  // Query object id and database of a persistent object.
-  //
-  template <class O /* = T */>
-  object_traits<O>::id_type object_id () const;
+  #pragma db id
+  unsigned long id_;
 
-  odb::database& database () const;
+  #pragma db not_null
+  shared_ptr<employer> employer_;
 };
   
-

In a lazy weak pointer interface, the load() function - returns the strong (shared) eager pointer. The following - transaction demonstrates the use of a lazy weak pointer based on - the employer and employee classes - presented earlier.

+

The corresponding database tables look like this:

-
-typedef std::vector<lazy_weak_ptr<employee> > employees;
+  
+CREATE TABLE employer (
+  name VARCHAR (255) NOT NULL PRIMARY KEY);
 
-session s;
-transaction t (db.begin ());
+CREATE TABLE employee (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+  employer VARCHAR (255) NOT NULL REFERENCES employer (name));
+  
-shared_ptr<employer> er (db.load<employer> ("Example Inc")); -employees& es (er->employees ()); +

6.1.2 To-Many Relationships

-for (employees::iterator i (es.begin ()); i != es.end (); ++i) +

An example of a unidirectional to-many relationship is the + employee-project relationship (an employee can be involved + in multiple projects). The following persistent C++ classes + model this relationship:

+ +
+#pragma db object
+class project
 {
-  // We are only interested in employees with object id less than
-  // 100.
-  //
-  lazy_weak_ptr<employee>& lwp (*i);
+  ...
 
-  if (lwp.object_id<employee> () < 100)
-  {
-    shared_ptr<employee> e (lwp.load ()); // Load and lock.
-    cout << e->first_ << " " << e->last_ << endl;
-  }
-}
+  #pragma db id
+  std::string name_;
+};
 
-t.commit ();
+#pragma db object
+class employee
+{
+  ...
+
+  #pragma db id
+  unsigned long id_;
+
+  #pragma db not_null unordered
+  std::vector<shared_ptr<project> > projects_;
+};
   
-

Notice that inside the for-loop we use a reference to the lazy - weak pointer instead of making a copy. This is not merely to - avoid a copy. When a lazy pointer is loaded, all other lazy - pointers that point to the same object do not automatically - become loaded (though an attempt to load such copies will - result in them pointing to the same object, provided the - same session is still in effect). By using a reference - in the above transaction we make sure that we load the - pointer that is contained in the employer - object. This way, if we later need to re-examine this - employee object, the pointer will already - be loaded.

+

The corresponding database tables look like this:

-

As another example, suppose we want to add an employee - to Example Inc. The straightforward implementation of this - transaction is presented below:

+
+CREATE TABLE project (
+  name VARCHAR (255) NOT NULL PRIMARY KEY);
+
+CREATE TABLE employee (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
+
+CREATE TABLE employee_projects (
+  object_id BIGINT UNSIGNED NOT NULL,
+  value VARCHAR (255) NOT NULL REFERENCES project (name));
+  
+ +

To obtain a more canonical database schema, the names of tables + and columns above can be customized using ODB pragmas + (Chapter 9, "ODB Pragma Language"). For example:

-session s;
-transaction t (db.begin ());
+#pragma db object
+class employee
+{
+  ...
 
-shared_ptr<employer> er (db.load<employer> ("Example Inc"));
-shared_ptr<employee> e (new employee ("John", "Doe"));
+  #pragma db not_null unordered \
+             id_column("employee_id") value_column("project_name")
+  std::vector<shared_ptr<project> > projects_;
+};
+  
-e->employer_ = er; -er->employees ().push_back (e); +

The resulting employee_projects table would then + look like this:

-db.persist (e); -t.commit (); +
+CREATE TABLE employee_projects (
+  employee_id BIGINT UNSIGNED NOT NULL,
+  project_name VARCHAR (255) NOT NULL REFERENCES project (name));
   
-

Notice here that we didn't have to update the employer object - in the database since the employees_ list of - pointers is an inverse side of a bidirectional relationship - and is effectively read-only, from the persistence point of - view.

-

A faster implementation of this transaction, that avoids loading - the employer object, relies on the ability to initialize an - unloaded lazy pointer with the database where the object - is stored as well as its identifier:

+

6.2 Bidirectional Relationships

+ +

In bidirectional relationships we are interested in navigating + from object to object in both directions. As a result, each + object class in a relationship contains a pointer to the other + object. If smart pointers are used, then a weak pointer should + be used as one of the pointers to avoid ownership cycles. For + example:

-lazy_shared_ptr<employer> er (db, std::string ("Example Inc"));
-shared_ptr<employee> e (new employee ("John", "Doe"));
+class employee;
 
-e->employer_ = er;
+#pragma db object
+class position
+{
+  ...
 
-session s;
-transaction t (db.begin ());
+  #pragma db id
+  unsigned long id_;
 
-db.persist (e);
+  weak_ptr<employee> employee_;
+};
 
-t.commit ();
-  
+#pragma db object +class employee +{ + ... -

Y.4 Using Custom Smart Pointers

+ #pragma db id + unsigned long id_; -

While the ODB runtime and profile libraries provide support for - the majority of widely-used pointers, it is also easy to add - support for a custom smart pointer.

+ #pragma db not_null + shared_ptr<position> position_; +}; +
-

To achieve this you will need to implement the - pointer_traits class template specialization for - your pointer. The first step is to determine the pointer kind - since the interface of the pointer_traits specialization - varies depending on the pointer kind. The supported pointer kinds - are: raw (raw pointer or equivalent, that is, unmanaged), - unique (smart pointer that doesn't support sharing), - shared (smart pointer that supports sharing), and - weak (weak counterpart of the shared pointer). Any of - these pointers can be lazy, which also affects the - interface of the pointer_traits specialization.

+

Note that when we establish a bidirectional relationship, we + have to set both pointers consistently. One way to make sure + that a relationship is always in a consistent state is to + provide a single function that updates both pointers at the + same time. For example:

-

Once you have determined the pointer kind for your smart pointer, - use a specialization for one of the standard pointers found in - the common ODB runtime library (libodb) as a base - for your own implementation.

+
+#pragma db object
+class position: public enable_shared_from_this<position>
+{
+  ...
 
-  

Once the pointer traits specialization is ready, you will need to - include it into the ODB compilation process using the - --odb-epilogue option and into the generated header - file with the --hxx-prologue option. As an example, - suppose we have the smart_ptr smart pointer for which - we have the traits specialization implemented in the - smart-ptr-traits.hxx file. Then, we can create an ODB - compiler options file for this pointer and save it to - smart-ptr.options:

+ void + fill (shared_ptr<employee> e) + { + employee_ = e; + e->positions_ = shared_from_this (); + } -
-# Options file for smart_ptr.
-#
---odb-epilogue '#include "smart-ptr-traits.hxx"'
---hxx-prologue '#include "smart-ptr-traits.hxx"'
-  
+private: + weak_ptr<employee> employee_; +}; -

Now, whenever we compile a header file that uses smart_ptr, - we can pass the following option to make sure it is recognized by the - ODB compiler as a smart pointer and the traits file is included in the - generated code:

+#pragma db object +class employee +{ + ... -
---options-file smart-ptr.options
-  
+private: + friend class position; -

It is also possible to implement a lazy counterpart for your - smart pointer. The ODB runtime library provides a class template - that encapsulates the object id management and loading - functionality that is needed to implement a lazy pointer. All - you need to do is wrap it with an interface that mimics - your smart pointer. Using one of the existing lazy pointer - implementations (either from the ODB runtime library or one - of the profile libraries) as a base for your implementation - is the easiest way to get started.

+ #pragma db not_null + shared_ptr<position> position_; +}; +
- +

Above, to model a bidirectional relationship in persistent classes, + we used two pointers, one in each object. While this is a natural + representation in C++, it does not translate to a canonical + relational model. Consider the database schema generated for + the above two classes:

+
+CREATE TABLE position (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+  employee BIGINT UNSIGNED REFERENCES employee (id));
 
-  

Z Composite Value Types

+CREATE TABLE employee ( + id BIGINT UNSIGNED NOT NULL PRIMARY KEY, + position BIGINT UNSIGNED NOT NULL REFERENCES position (id)); +
-

Composite value type is a class or struct - type that is mapped to more than one database column. To declare - a composite value type we use the db value pragma, - for example:

+

While this database schema is valid, it is unconventional. We have + a reference from a row in the position table to a row + in the employee table. We also have a reference + from this same row in the employee table back to + the row in the position table. From the relational + point of view, one of these references is redundant since + in SQL we can easily navigate in both directions using just one + of these references.

+ +

To eliminate redundant database schema references we can use the + inverse pragma (Section 9.3.7, + "inverse") which tells the ODB compiler that + a pointer is the inverse side of a bidirectional relationship. + Either side of a relationship can be made inverse. For example:

-#pragma db value
-class basic_name
+#pragma db object
+class position
 {
   ...
 
-  std::string first_;
-  std::string last_;
+  #pragma db inverse(position_)
+  weak_ptr<employee> employee_;
 };
-  
- -

A composite value type does not have to define a default constructor, - unless it is used as an element of a container, in which case the - default constructor can be made private. If a composite value type - has private or protected non-transient data members or if its - default constructor is not public and the value type is used as - an element in a container, then the odb::access class - should be declared a friend of this value type. For example:

-
-#pragma db value
-class basic_name
+#pragma db object
+class employee
 {
-public:
-  basic_name (const std::string& first, const std::string& last);
-
   ...
 
-private:
-  friend class odb::access;
-
-  basic_name () {} // Needed for storing basic_name in containers.
-
-  std::string first_;
-  std::string last_;
+  #pragma db not_null
+  shared_ptr<position> position_;
 };
   
-

The complete versions of the above code fragment as well as other code - samples presented in this chapter can found in the composite - example in the odb-examples package.

+

The resulting database schema looks like this:

-

The members of a composite value can be other value types (either - simple or composite), containers (@@ ref), and object pointers (@@ ref). - Similarly, a composite value type can be used in object members, - as an element in a container, and as a base for another composite - value type. In particular, composite value types can be used as - element types in set containers (@@ ref) and as key types in map - containers (@@ ref). A composite value type that is used as an - element of a container cannot contain other containers since - containers of containers are not allowed. The following example - illustrates some of the possible use cases:

+
+CREATE TABLE position (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
 
-
-#pragma db value
-class basic_name
-{
-  ...
+CREATE TABLE employee (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+  position BIGINT UNSIGNED NOT NULL REFERENCES position (id));
+  
- std::string first_; - std::string last_; -}; +

As you can see, an inverse member does not have a corresponding + column (or table, in case of an inverse container of pointers) + and, from the point of view of database operations, is effectively + read-only. The only way to change a bidirectional relationship + with an inverse side is to set its direct (non-inverse) + pointer. Also note that an ordered container (Section + 5.1, "Ordered Containers") of pointers that is an inverse side + of a bidirectional relationship is always treated as unordered + (Section 9.3.8, "unordered") + because the contents of such a container are implicitly built from + the direct side of the relationship which does not contain the + element order (index).

-typedef std::vector<basic_name> basic_names; +

There are three distinct bidirectional relationships that we + will cover in the following sections: one-to-one, one-to-many, + and many-to-many. We will only talk about bidirectional + relationships with inverse sides since they result in canonical + database schemas. For sample code that shows how to work with + these relationships, refer to the inverse example + in the odb-examples package.

-#pragma db value -class name_extras -{ - ... +

6.2.1 One-to-One Relationships

- std::string nickname_; - basic_names aliases_; -}; +

An example of a bidirectional one-to-one relationship is the + presented above employee-position relationship (an employee + fills one position and a position is filled by one employee). + The following persistent C++ classes model this relationship:

-#pragma db value -class name: public basic_name +
+class employee;
+
+#pragma db object
+class position
 {
   ...
 
-  std::string title_;
-  name_extras extras_;
+  #pragma db id
+  unsigned long id_;
+
+  #pragma db inverse(position_)
+  weak_ptr<employee> employee_;
 };
 
 #pragma db object
-class person
+class employee
 {
   ...
 
-  #pragma db id auto
+  #pragma db id
   unsigned long id_;
 
-  name name_;
+  #pragma db not_null
+  shared_ptr<position> position_;
 };
-
+
-

We can also use the data members from the composite value types - in database queries (Chapter 4, "Querying the - Database"). For each composite value in a persistent class, - the query class defines a nested scope containing members corresponding - to the data members in the value type. For example, the - query class for the person object presented above - contains the name scope (derived from the name_ - data member) which in turn contains the extras member - (derived from the name::extras_ data member of the - composite value type). The process continues reqursively for nested - composite value types and, as a result, we can use the - query::name::extras::nickname expression while querying - the database for the person objects. For example:

+

The corresponding database tables look like this:

-
-typedef odb::query<person> query;
-typedef odb::result<person> result;
+  
+CREATE TABLE position (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
 
-transaction t (db->begin ());
+CREATE TABLE employee (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+  position BIGINT UNSIGNED NOT NULL REFERENCES position (id));
+  
-result r (db->query<person> ( - query::name::extras::nickname == "Squeaky")); +

If instead the other side of this relationship is made inverse, + then the database tables will change as follows:

-... +
+CREATE TABLE position (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+  employee BIGINT UNSIGNED REFERENCES employee (id));
 
-t.commit ();
+CREATE TABLE employee (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
   
-

Z.1 Composite Value Column and Table Names

+

6.2.2 One-to-Many Relationships

-

Customizing a column name for a data member of a simple value - type is straightforward: we simply specify the desired name with - the db column pragma (@@ ref). For composite value - types things are slightly more complicated since it is mapped to - multiple columns. Consider the following example:

+

An example of a bidirectional one-to-many relationship is the + employer-employee relationship (an employer has multiple + employees and an employee is employed by one employer). + The following persistent C++ classes model this relationship:

-
-#pragma db value
-class name
+  
+class employee;
+
+#pragma db object
+class employer
 {
   ...
 
-  std::string first_;
-  std::string last_;
+  #pragma db id
+  std::string name_;
+
+  #pragma db not_null inverse(employer_)
+  std::vector<weak_ptr<employee> > employees_
 };
 
 #pragma db object
-class person
+class employee
 {
   ...
 
-  #pragma db id auto
+  #pragma db id
   unsigned long id_;
 
-  name name_;
+  #pragma db not_null
+  shared_ptr<employer> employer_;
 };
-
+
-

The column names for the first_ and last_ - members are constructed by using the sanitized name of the - person::name_ member as a prefix and the names of the - members in the value type (first_ and last_) - as suffixes. As a result, the database schema for the above classes - will look like this:

+

The corresponding database tables differ significantly depending + on which side of the relationship is made inverse. If the one + side (employer) is inverse as in the code + above, then the resulting database schema looks like this:

-CREATE TABLE person (
+CREATE TABLE employer (
+  name VARCHAR (255) NOT NULL PRIMARY KEY);
+
+CREATE TABLE employee (
   id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
-  name_first TEXT NOT NULL,
-  name_last TEXT NOT NULL);
+  employer VARCHAR (255) NOT NULL REFERENCES employer (name));
   
-

We can customize both the prefix and the suffix using the - db column pragma as shown in the following - example:

+

If instead the many side (employee) of this + relationship is made inverse, then the database tables will change + as follows:

+ +
+CREATE TABLE employer (
+  name VARCHAR (255) NOT NULL PRIMARY KEY);
+
+CREATE TABLE employer_employees (
+  object_id VARCHAR (255) NOT NULL,
+  value BIGINT UNSIGNED NOT NULL REFERENCES employee (id));
+
+CREATE TABLE employee (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
+  
+ +

6.2.3 Many-to-Many Relationships

+ +

An example of a bidirectional many-to-many relationship is the + employee-project relationship (an employee can work on multiple + projects and a project can have multiple participating employees). + The following persistent C++ classes model this relationship:

-#pragma db value
-class name
+class employee;
+
+#pragma db object
+class project
 {
   ...
 
-  #pragma db column("first_name")
-  std::string first_;
+  #pragma db id
+  std::string name_;
 
-  #pragma db column("last_name")
-  std::string last_;
+  #pragma db not_null inverse(projects_)
+  std::vector<weak_ptr<employee> > employees_;
 };
 
 #pragma db object
-class person
+class employee
 {
   ...
 
-  #pragma db column("person_")
-  name name_;
+  #pragma db id
+  unsigned long id_;
+
+  #pragma db not_null unordered
+  std::vector<shared_ptr<project> > projects_;
 };
   
-

The database schema changes as follows:

+

The corresponding database tables look like this:

-CREATE TABLE person (
-  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
-  person_first_name TEXT NOT NULL,
-  person_last_name TEXT NOT NULL);
-  
- -

We can also make the column prefix empty, for example:

+CREATE TABLE project ( + name VARCHAR (255) NOT NULL PRIMARY KEY); -
-#pragma db object
-class person
-{
-  ...
+CREATE TABLE employee (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
 
-  #pragma db column("")
-  name name_;
-};
+CREATE TABLE employee_projects (
+  object_id BIGINT UNSIGNED NOT NULL,
+  value VARCHAR (255) NOT NULL REFERENCES project (name));
   
-

This will result in the following schema:

+

If instead the other side of this relationship is made inverse, + then the database tables will change as follows:

-CREATE TABLE person (
-  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
-  first_name TEXT NOT NULL,
-  last_name TEXT NOT NULL);
+CREATE TABLE project (
+  name VARCHAR (255) NOT NULL PRIMARY KEY);
+
+CREATE TABLE project_employees (
+  object_id VARCHAR (255) NOT NULL,
+  value BIGINT UNSIGNED NOT NULL REFERENCES employee (id));
+
+CREATE TABLE employee (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
   
-

The same principal applies when a composite value type is used - as an element of a container, except that instead of - db column either the db value_column - (@@ ref) or db key_column (@@ ref) pragmas are used to - specify the column prefix.

+

6.3 Lazy Pointers

-

When a composite value type contains a container, an extra table - is used to store the elements (@@ ref Chapter X, "Containers"). - The names of such tables are constructed in a way similar to the - column names, except that by default both the object name and the - member name are used as a prefix. For example:

+

Consider again the bidirectional, one-to-many employer-employee + relationship that was presented earlier in this chapter:

-#pragma db value
-class name
+class employee;
+
+#pragma db object
+class employer
 {
   ...
 
-  std::string first_;
-  std::string last_;
-  std::vector<std::string> nicknames_;
+  #pragma db id
+  std::string name_;
+
+  #pragma db not_null inverse(employer_)
+  std::vector<weak_ptr<employee> > employees_;
 };
 
 #pragma db object
-class person
+class employee
 {
   ...
 
-  name name_;
+  #pragma db id
+  unsigned long id_;
+
+  #pragma db not_null
+  shared_ptr<employer> employer_;
 };
   
-

The corresponding database schema will look like this:

+

Consider also the following transaction which obtains the employer + name given the employee id:

-
-CREATE TABLE `person_name_nicknames` (
-  `object_id` BIGINT UNSIGNED NOT NULL,
-  `index` BIGINT UNSIGNED NOT NULL,
-  `value` TEXT NOT NULL)
+  
+unsigned long id = ...
+string name;
 
-CREATE TABLE person (
-  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
-  name_first TEXT NOT NULL,
-  name_last TEXT NOT NULL);
+session s;
+transaction t (db.begin ());
+
+shared_ptr<employee> e (db.load<employee> (id));
+name = e->employer_->name_;
+
+t.commit ();
   
-

To customize the container table name we can use the - db table (@@ ref) pragma, for example:

+

While this transaction looks very simple, it actually does a lot more + than what meets the eye and is necessary. Consider what happens when + we load the employee object: the employer_ + pointer is also automatically loaded which means the employer + object corresponding to this employee is also loaded. But the + employer object in turn contains the list of pointers + to all the employees, which are also loaded. A a result, when object + relationships are involved, a simple transaction like the above can + load many more objects than is necessary.

+ +

To overcome this problem ODB offers finer grained control over + the relationship loading in the form of lazy pointers. A lazy + pointer does not automatically load the pointed-to object + when the containing object is loaded. Instead, we have to + explicitly load the pointed-to object if and when we need to + access it.

+ +

The ODB runtime library provides lazy counterparts for all the + supported pointers, namely: odb::lazy_shared_ptr and + odb::lazy_weak_ptr for TR1 shared_ptr and + weak_ptr, odb::lazy_auto_ptr for + std::auto_ptr, and odb::lazy_ptr for raw + pointers. The ODB profile libraries provide lazy pointer + implementations for smart pointers from popular frameworks and + libraries.

+ +

While we will discuss the interface of lazy pointers in more detail + shortly, the most commonly used extra function provided by these + pointers is load(). This function loads the + pointed-to object if it hasn't already been loaded. After + the call to this function, the lazy pointer can be used + in the the same way as its eager counterpart. The load() + function also returns the eager pointer, in case you need to pass + it around. For a lazy weak pointer, the + load() function also locks the pointer.

+ +

The following example shows how we can change our employer-employee + relationship to use lazy pointers. Here we choose to use lazy pointers + for both sides of the relationship.

-#pragma db value
-class name
+class employee;
+
+#pragma db object
+class employer
 {
   ...
 
-  #pragma db table("nickname")
-  std::vector<std::string> nicknames_;
+  #pragma db not_null inverse(employer_)
+  std::vector<lazy_weak_ptr<employee> > employees_;
 };
 
 #pragma db object
-class person
+class employee
 {
   ...
 
-  #pragma db table("person_")
-  name name_;
+  #pragma db not_null
+  lazy_shared_ptr<employer> employer_;
 };
   
-

This will result in the following schema changes:

+

And the transaction is changed like this:

-
-CREATE TABLE `person_nickname` (
-  `object_id` BIGINT UNSIGNED NOT NULL,
-  `index` BIGINT UNSIGNED NOT NULL,
-  `value` TEXT NOT NULL)
+  
+unsigned long id = ...
+string name;
+
+session s;
+transaction t (db.begin ());
+
+shared_ptr<employee> e (db.load<employee> (id));
+e->employer_.load ();
+name = e->employer_->name_;
+
+t.commit ();
   
-

Similar to columns, we can make the table prefix empty.

+

As a general guideline we recommend that you make at least one side + of a bidirectional relationship lazy, especially for relationships + with a many side.

- +

A lazy pointer implementation mimics the interface of its eager + counterpart which can be used once the pointer is loaded. It also + adds a number of additional functions that are specific to the + lazy loading functionality. Overall, the interface of a lazy + pointer follows this general outline:

+
+template <class T>
+class lazy_ptr
+{
+public:
+  //
+  // The eager pointer interface.
+  //
 
-  

Q Session

+ // Initialization/assignment from an eager pointer. + // +public: + template <class Y> lazy_ptr (const eager_ptr<Y>&); + template <class Y> lazy_ptr& operator= (const eager_ptr<Y>&); -

A session is an application's unit of work that may encompass several - database transactions (@@ ref Transaction). In this version of ODB a - session is just an object cache. In the future versions it will provide - additional functionality, such as automatic object state change flushing - and optimistic concurrency.

+ // Lazy loading interface. + // +public: + bool loaded () const; + eager_ptr<T> load () const; + + // Unload the pointer. For transient objects this function is + // equivalent to reset(). + // + void unload () const; + + // Initialization with a persistent loaded object. + // + template <class Y> lazy_ptr (database&, Y*); + template <class Y> lazy_ptr (database&, const eager_ptr<Y>&); + + template <class Y> void reset (database&, Y*); + template <class Y> void reset (database&, const eager_ptr<Y>&); + + // Initialization with a persistent unloaded object. + // + template <class ID> lazy_ptr (database&, const ID&); + + template <class ID> void reset (database&, const ID&); + + // Query object id and database of a persistent object. + // + template <class O /* = T */> + object_traits<O>::id_type object_id () const; + + odb::database& database () const; +}; +
-

Each thread of execution in an application can have only one active - session at a time. A session is started by creating an instance of - the odb::session class and is automatically terminated - when this instance is destroyed. You will need to include the - <odb/session.hxx> header file to make this class - available in your application. For example:

+

In a lazy weak pointer interface, the load() function + returns the strong (shared) eager pointer. The following + transaction demonstrates the use of a lazy weak pointer based on + the employer and employee classes + presented earlier.

-#include <odb/database.hxx>
-#include <odb/session.hxx>
-#include <odb/transaction.hxx>
+typedef std::vector<lazy_weak_ptr<employee> > employees;
 
-using namespace odb;
+session s;
+transaction t (db.begin ());
 
-{
-  session s;
+shared_ptr<employer> er (db.load<employer> ("Example Inc"));
+employees& es (er->employees ());
 
-  // First transaction.
+for (employees::iterator i (es.begin ()); i != es.end (); ++i)
+{
+  // We are only interested in employees with object id less than
+  // 100.
   //
-  {
-    transaction t (db.begin ());
-    ...
-    t.commit ();
-  }
+  lazy_weak_ptr<employee>& lwp (*i);
 
-  // Second transaction.
-  //
+  if (lwp.object_id<employee> () < 100)
   {
-    transaction t (db.begin ());
-    ...
-    t.commit ();
+    shared_ptr<employee> e (lwp.load ()); // Load and lock.
+    cout << e->first_ << " " << e->last_ << endl;
   }
-
-  // Session 's' is terminated here.
 }
+
+t.commit ();
   
-

The odb::session class has the following interface:

+

Notice that inside the for-loop we use a reference to the lazy + weak pointer instead of making a copy. This is not merely to + avoid a copy. When a lazy pointer is loaded, all other lazy + pointers that point to the same object do not automatically + become loaded (though an attempt to load such copies will + result in them pointing to the same object, provided the + same session is still in effect). By using a reference + in the above transaction we make sure that we load the + pointer that is contained in the employer + object. This way, if we later need to re-examine this + employee object, the pointer will already + be loaded.

-
-namespace odb
-{
-  class session
-  {
-  public:
-    session ();
-    ~session ();
+  

As another example, suppose we want to add an employee + to Example Inc. The straightforward implementation of this + transaction is presented below:

- // Copying or assignment of sessions is not supported. - // - private: - session (const session&); - session& operator= (const session&); +
+session s;
+transaction t (db.begin ());
 
-    // Current session interface.
-    //
-  public:
-    static session&
-    current ();
+shared_ptr<employer> er (db.load<employer> ("Example Inc"));
+shared_ptr<employee> e (new employee ("John", "Doe"));
 
-    static bool
-    has_current ();
+e->employer_ = er;
+er->employees ().push_back (e);
 
-    static void
-    current (session&);
+db.persist (e);
+t.commit ();
+  
- static void - reset_current (); +

Notice here that we didn't have to update the employer object + in the database since the employees_ list of + pointers is an inverse side of a bidirectional relationship + and is effectively read-only, from the persistence point of + view.

- // Object cache interface. - // - public: - typedef odb::database database_type; +

A faster implementation of this transaction, that avoids loading + the employer object, relies on the ability to initialize an + unloaded lazy pointer with the database where the object + is stored as well as its identifier:

- template <typename T> - void - insert (database_type&, - const object_traits<T>::id_type&, - const object_traits<T>::pointer_type&); +
+lazy_shared_ptr<employer> er (db, std::string ("Example Inc"));
+shared_ptr<employee> e (new employee ("John", "Doe"));
 
-    template <typename T>
-    object_traits<T>::pointer_type
-    find (database_type&, const object_traits<T>::id_type&) const;
+e->employer_ = er;
 
-    template <typename T>
-    void
-    erase (database_type&, const object_traits<T>::id_type&);
-  };
-}
-  
+session s; +transaction t (db.begin ()); -

The session constructor creates a new session and sets it as a - current session for this thread. If we try to create a session - while there is already a current session in effect, this constructor - throws the odb::already_in_session exception. The - destructor clears the current session for this thread if this - session is the current one.

+db.persist (e); -

The static current() accessor returns the currently active - session for this thread. If there is no active session, this function - throws the odb::not_in_session exception. We can check - whether there is a session in effect in this thread using the - has_current() static function.

+t.commit (); +
-

The static current() modifier allows us to set the - current session for this thread. The reset_current - static function clears the current session. These two functions - allow for more advanced use cases, such as multiplexing - between two or more sessions in the same thread.

+

6.4 Using Custom Smart Pointers

-

We normally don't use the object cache interface directly. However, - it could be useful in some cases, for example, to find out whether - an object has already been loaded.

+

While the ODB runtime and profile libraries provide support for + the majority of widely-used pointers, it is also easy to add + support for a custom smart pointer.

-

Q.1 Object Cache

+

To achieve this you will need to implement the + pointer_traits class template specialization for + your pointer. The first step is to determine the pointer kind + since the interface of the pointer_traits specialization + varies depending on the pointer kind. The supported pointer kinds + are: raw (raw pointer or equivalent, that is, unmanaged), + unique (smart pointer that doesn't support sharing), + shared (smart pointer that supports sharing), and + weak (weak counterpart of the shared pointer). Any of + these pointers can be lazy, which also affects the + interface of the pointer_traits specialization.

-

A session is an object cache. Every time an object is made persistent - by calling the database::persist() function (@@ ref), loaded - by calling the database::load() or database::find() - function (@@ ref), or loaded by iterating over a query result (@@ ref), - the pointer to the persistent object, in the form of the canonical object - pouinter (@@ ref), is stored in the session. For as long as the - session is in effect, any subsequent calls to load the same object will - return the cached instance. When an object's state is deleted from the - database with the database::erase() function (@@ ref), the - cached object pointer is removed from the session. For example:

+

Once you have determined the pointer kind for your smart pointer, + use a specialization for one of the standard pointers found in + the common ODB runtime library (libodb) as a base + for your own implementation.

-
-shared_ptr<person> p (new person ("John", "Doe"));
+  

Once the pointer traits specialization is ready, you will need to + include it into the ODB compilation process using the + --odb-epilogue option and into the generated header + file with the --hxx-prologue option. As an example, + suppose we have the smart_ptr smart pointer for which + we have the traits specialization implemented in the + smart-ptr-traits.hxx file. Then, we can create an ODB + compiler options file for this pointer and save it to + smart-ptr.options:

-session s; -transaction t (db.begin ()); +
+# Options file for smart_ptr.
+#
+--odb-epilogue '#include "smart-ptr-traits.hxx"'
+--hxx-prologue '#include "smart-ptr-traits.hxx"'
+  
-unsigned long id (db.persist (p)); // p is cached in s. -shared_ptr<person> p1 (db.load<person> (id)); // p1 same as p. +

Now, whenever we compile a header file that uses smart_ptr, + we can pass the following option to make sure it is recognized by the + ODB compiler as a smart pointer and the traits file is included in the + generated code:

-t.commit (); +
+--options-file smart-ptr.options
   
+

It is also possible to implement a lazy counterpart for your + smart pointer. The ODB runtime library provides a class template + that encapsulates the object id management and loading + functionality that is needed to implement a lazy pointer. All + you need to do is wrap it with an interface that mimics + your smart pointer. Using one of the existing lazy pointer + implementations (either from the ODB runtime library or one + of the profile libraries) as a base for your implementation + is the easiest way to get started.

-

The per-object caching policies depend on the object pointer kind - (@@ ref). Objects with a unique pointer, such as std::auto_ptr, - as an object pointer are never cached since it is not possible to have - two such pointers pointing to the same object. When an object is - persisted via a pointer or loaded as a dynamically allocated instance, - objects with both raw and shared pointers as object pointers are - cached. If an object is persisted as a reference or loaded into - a pre-allocated instance, the object is only cached if its object - pointer is a raw pointer.

-

Finally, the session caches both constant and non-constant objects, - depending on whether a constant reference or constant pointer was - passed to the database::persist() function (in contrast, - when loaded, objects are always created and cached as non-constant). - If we try to load an object as non-constant that was previously - persisted and cached as constant, the odb::const_object - exception is thrown. The following transaction illustartes the - situation where this would happen:

+ -
-shared_ptr<const person> p (new person ("John", "Doe"));
 
-session s;
-transaction t (db.begin ());
+  

7 Composite Value Types

-unsigned long id (db.persist (p)); -shared_ptr<const person> p1 (db.load<const person> (id)); // Ok. -shared_ptr<person> p2 (db.load<person> (id)); // Exception. +

Composite value type is a class or struct + type that is mapped to more than one database column. To declare + a composite value type we use the db value pragma, + for example:

-t.commit (); -
+
+#pragma db value
+class basic_name
+{
+  ...
 
+  std::string first_;
+  std::string last_;
+};
+  
- +

A composite value type does not have to define a default constructor, + unless it is used as an element of a container, in which case the + default constructor can be made private. If a composite value type + has private or protected non-transient data members or if its + default constructor is not public and the value type is used as + an element in a container, then the odb::access class + should be declared a friend of this value type. For example:

+
+#pragma db value
+class basic_name
+{
+public:
+  basic_name (const std::string& first, const std::string& last);
 
-  

4 Querying the Database

+ ... -

If we don't know the identifiers of the objects that we are looking - for, we can use queries to search the database for objects matching - certain criteria. The ODB query facility is optional and we need to - explicitly request the generation of the necessary database support - code with the --generate-query ODB compiler option.

+private: + friend class odb::access; -

ODB provides a flexible query API that offers two distinct levels of - abstraction from the database system query language such as SQL. - At the high level we are presented with an easy to use yet powerful - object-oriented query language, called ODB Query Language. This - query language is modeled after and is integrated into C++ allowing - us to write expressive and safe queries that look and feel like - ordinary C++. We have already seen examples of these queries in the - introductory chapters. Below is another, more interesting, example:

+ basic_name () {} // Needed for storing basic_name in containers. -
-  typedef odb::query<person> query;
-  typedef odb::result<person> result;
+  std::string first_;
+  std::string last_;
+};
+  
- unsigned short age; - query q (query::first == "John" && query::age < query::_ref (age)); +

The complete versions of the above code fragment as well as other code + samples presented in this chapter can found in the composite + example in the odb-examples package.

- for (age = 10; age < 100; age += 10) - { - result r (db.query<person> (q)); - ... - } -
+

The members of a composite value can be other value types (either + simple or composite), containers (@@ ref), and object pointers (@@ ref). + Similarly, a composite value type can be used in object members, + as an element in a container, and as a base for another composite + value type. In particular, composite value types can be used as + element types in set containers (@@ ref) and as key types in map + containers (@@ ref). A composite value type that is used as an + element of a container cannot contain other containers since + containers of containers are not allowed. The following example + illustrates some of the possible use cases:

-

At the low level, queries can be written as predicates using - the database system-native query language such as the - WHERE predicate from the SQL SELECT - statement. This language will be referred to as native query - language. At this level ODB still takes care of converting - query parameters from C++ to the database system format. Below - is the re-implementation of the above example using SQL as - the native query language:

+
+#pragma db value
+class basic_name
+{
+  ...
 
-  
-  query q ("first = 'John' AND age = " + query::_ref (age));
-  
+ std::string first_; + std::string last_; +}; -

Note that at this level we lose the static typing of - query expressions. For example, if we wrote something like this:

+typedef std::vector<basic_name> basic_names; -
-  query q (query::first == 123 && query::agee < query::_ref (age));
-  
+#pragma db value +class name_extras +{ + ... -

We would get two errors during the C++ compilation. The first would - indicate that we cannot compare query::first to an - integer and the second would pick the misspelling in - query::agee. On the other hand, if we wrote something - like this:

+ std::string nickname_; + basic_names aliases_; +}; -
-  query q ("first = 123 AND agee = " + query::_ref (age));
-  
+#pragma db value +class name: public basic_name +{ + ... -

It would compile fine and would trigger an error only when executed - by the database system.

+ std::string title_; + name_extras extras_; +}; -

We can also combine the two query languages in a single query, for - example:

+#pragma db object +class person +{ + ... -
-  query q ("first = 'John'" + (query::age < query::_ref (age)));
-  
+ #pragma db id auto + unsigned long id_; -

4.1 ODB Query Language

+ name name_; +}; +
-

An ODB query is an expression that tells the database system whether - any given object matches the desired criteria. As such, a query expression - always evaluates as true or false. At - the higher level, an expression consists of other expressions - combined with logical operators such as && (AND), - || (OR), and ! (NOT). For example:

+

We can also use the data members from the composite value types + in database queries (Chapter 4, "Querying the + Database"). For each composite value in a persistent class, + the query class defines a nested scope containing members corresponding + to the data members in the value type. For example, the + query class for the person object presented above + contains the name scope (derived from the name_ + data member) which in turn contains the extras member + (derived from the name::extras_ data member of the + composite value type). The process continues reqursively for nested + composite value types and, as a result, we can use the + query::name::extras::nickname expression while querying + the database for the person objects. For example:

-  typedef odb::query<person> query;
-
-  query q (query::first == "John" || query::age == 31);
-  
+typedef odb::query<person> query; +typedef odb::result<person> result; -

At the core of every query expression lie simple expressions which - involve one or more object members, values, or parameters. To - refer to an object member we use an expression such as - query::first above. The names of members in the - query class are derived from the names of data members - in the object class by removing the common member name decorations, - such as leading and trailing underscores, the m_ prefix, - etc.

+transaction t (db->begin ()); -

In a simple expression an object member can be compared to a value, - parameter, or another member using a number of predefined operators - and functions. The following table gives an overview of the available - expressions:

+result r (db->query<person> ( + query::name::extras::nickname == "Squeaky")); - - - - - - - +... - - - - - +t.commit (); + - - - - - +

7.1 Composite Value Column and Table Names

- - - - - +

Customizing a column name for a data member of a simple value + type is straightforward: we simply specify the desired name with + the db column pragma (@@ ref). For composite value + types things are slightly more complicated since it is mapped to + multiple columns. Consider the following example:

- - - - - +
+#pragma db value
+class name
+{
+  ...
 
-    
- - - - + std::string first_; + std::string last_; +}; - - - - - +#pragma db object +class person +{ + ... - - - - - + #pragma db id auto + unsigned long id_; - - - - - + name name_; +}; + - - - - - +

The column names for the first_ and last_ + members are constructed by using the sanitized name of the + person::name_ member as a prefix and the names of the + members in the value type (first_ and last_) + as suffixes. As a result, the database schema for the above classes + will look like this:

- - - - - -
OperatorDescriptionExample
==equalquery::age == 31
!=unequalquery::age != 31
<less thanquery::age < 31
>greater thanquery::age > 31
<=less than or equalquery::age <= 31
>=greater than or equalquery::age >= 31
in()one of the valuesquery::age.in (30, 32, 34)
in_range()one of the values in rangequery::age.in_range (begin, end)
is_null()value is NULLquery::age.is_null ()
is_not_null()value is not NULLquery::age.is_not_null ()
+
+CREATE TABLE person (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+  name_first TEXT NOT NULL,
+  name_last TEXT NOT NULL);
+  
-

The in() function accepts a maximum of five arguments. - Use the in_range() function if you need to compare - to more than five values. This function accepts a pair of - standard C++ iterators and compares to all the values from - the begin position inclusive and until and - excluding the end position. The following - code fragment shows how we can use these functions:

+

We can customize both the prefix and the suffix using the + db column pragma as shown in the following + example:

-  std::vector<string> names;
+#pragma db value
+class name
+{
+  ...
 
-  names.push_back ("John");
-  names.push_back ("Jack");
-  names.push_back ("Jane");
+  #pragma db column("first_name")
+  std::string first_;
 
-  query q1 (query::first.in ("John", "Jack", "Jane"));
-  query q2 (query::first.in_range (names.begin (), names.end ()));
-  
+ #pragma db column("last_name") + std::string last_; +}; +#pragma db object +class person +{ + ... + #pragma db column("person_") + name name_; +}; +
-

The operator precedence in the query expressions are the same - as for equivalent C++ operators. We can use parentheses to - make sure the expression is evaluated in the desired order. - For example:

+

The database schema changes as follows:

-
-  query q ((query::first == "John" || query::first == "Jane") &&
-           query::age < 31);
+  
+CREATE TABLE person (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+  person_first_name TEXT NOT NULL,
+  person_last_name TEXT NOT NULL);
   
+

We can also make the column prefix empty, for example:

+
+#pragma db object
+class person
+{
+  ...
 
-  

4.2 Parameter Binding

+ #pragma db column("") + name name_; +}; +
-

An instance of the odb::query class encapsulates two - parts of information about the query: the query expression and - the query parameters. Parameters can be bound to C++ variables - either by value or by reference.

+

This will result in the following schema:

-

If a parameter is bound by value, then the value for this parameter - is copied from the C++ variable to the query instance at the query - construction time. On the other hand, if a parameter is bound by - reference, then the query instance stores a reference to the - bound variable. The actual value of the parameter is only extracted - at the query execution time. Consider, for example, the following - two queries:

+
+CREATE TABLE person (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+  first_name TEXT NOT NULL,
+  last_name TEXT NOT NULL);
+  
+ +

The same principal applies when a composite value type is used + as an element of a container, except that instead of + db column either the db value_column + (@@ ref) or db key_column (@@ ref) pragmas are used to + specify the column prefix.

+ +

When a composite value type contains a container, an extra table + is used to store the elements (@@ ref Chapter X, "Containers"). + The names of such tables are constructed in a way similar to the + column names, except that by default both the object name and the + member name are used as a prefix. For example:

-  string name ("John");
+#pragma db value
+class name
+{
+  ...
 
-  query q1 (query::first == query::_val (name));
-  query q2 (query::first == query::_ref (name));
+  std::string first_;
+  std::string last_;
+  std::vector<std::string> nicknames_;
+};
 
-  name = "Jane";
+#pragma db object
+class person
+{
+  ...
 
-  db.query<person> (q1); // Find John.
-  db.query<person> (q2); // Find Jane.
+  name name_;
+};
   
-

The odb::query class provides two special functions, - _val() and _ref(), that allow us to - bind the parameter either by value or by reference, respectively. - In the ODB query language, if the binding is not specified - explicitly, the value semantic is used by default. In the - native query language, binding must always be specified - explicitly. For example:

+

The corresponding database schema will look like this:

-
-  query q1 (query::age < age);                // By value.
-  query q2 (query::age < query::_val (age));  // By value.
-  query q3 (query::age < query::_ref (age));  // By reference.
+  
+CREATE TABLE `person_name_nicknames` (
+  `object_id` BIGINT UNSIGNED NOT NULL,
+  `index` BIGINT UNSIGNED NOT NULL,
+  `value` TEXT NOT NULL)
 
-  query q4 ("age < " + age);                  // Error.
-  query q5 ("age < " + query::_val (age));    // By value.
-  query q6 ("age < " + query::_ref (age));    // By reference.
+CREATE TABLE person (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+  name_first TEXT NOT NULL,
+  name_last TEXT NOT NULL);
   
-

A query that only has by-value parameters does not depend on any - other variables and is self-sufficient once constructed. A query - that has one or more by-reference parameters depends on the - bound variables until the query is executed. If one such variable - goes out of scope and we execute the query, the behavior is - undefined.

+

To customize the container table name we can use the + db table (@@ ref) pragma, for example:

-

4.3 Executing a Query

+
+#pragma db value
+class name
+{
+  ...
 
-  

Once we have the query instance ready and by-reference parameters - initialized, we can execute the query using the - database::query() function template. It has two - overloaded versions:

+ #pragma db table("nickname") + std::vector<std::string> nicknames_; +}; -
-  template <typename T>
-  result<T>
-  query (bool cache = true);
+#pragma db object
+class person
+{
+  ...
 
-  template <typename T>
-  result<T>
-  query (const odb::query<T>&, bool cache = true);
+  #pragma db table("person_")
+  name name_;
+};
   
-

The first query() function is used to return all the - persistent objects of a given type stored in the database. - The second function uses the passed query instance to only return - objects matching the query criteria. The cache argument - determines whether the objects' states should be cached in the - application's memory or if they should be returned by the database - system one by one as the iteration over the result progresses. The - result caching is discussed in detail in the next section.

+

This will result in the following schema changes:

-

When calling the query() function, we have to - explicitly specify the object type we are querying. For example:

+
+CREATE TABLE `person_nickname` (
+  `object_id` BIGINT UNSIGNED NOT NULL,
+  `index` BIGINT UNSIGNED NOT NULL,
+  `value` TEXT NOT NULL)
+  
-
-  typedef odb::query<person> query;
-  typedef odb::result<person> result;
+  

Similar to columns, we can make the table prefix empty.

- result all (db.query<person> ()); - result johns (db.query<person> (query::first == "John")); -
-

Note that it is not required to explicitly create a named - query variable before executing it. For example, the following - two queries are equivalent:

+ -
-  query q (query::first == "John");
 
-  result r1 (db.query<person> (q));
-  result r1 (db.query<person> (query::first == "John"));
-  
+

8 Session

-

Normally we would create a named query instance if we are - planning to run the same query multiple times and would use the - in-line version for those that are executed only once.

+

A session is an application's unit of work that may encompass several + database transactions (@@ ref Transaction). In this version of ODB a + session is just an object cache. In the future versions it will provide + additional functionality, such as automatic object state change flushing + and optimistic concurrency.

-

It is also possible to create queries from other queries by - combining them using logical operators. For example:

+

Each thread of execution in an application can have only one active + session at a time. A session is started by creating an instance of + the odb::session class and is automatically terminated + when this instance is destroyed. You will need to include the + <odb/session.hxx> header file to make this class + available in your application. For example:

-result
-find_minors (database& db, const query& name_query)
-{
-  return db.query<person> (name_query && query::age < 18);
-}
-
-result r (find_underage (db, query::first == "John"));
-  
+#include <odb/database.hxx> +#include <odb/session.hxx> +#include <odb/transaction.hxx> -

4.4 Query Result

+using namespace odb; -

The result of executing a query is zero, one, or more objects - matching the query criteria. The result is returned as an instance - of the odb::result class template, for example:

+{ + session s; -
-  typedef odb::query<person> query;
-  typedef odb::result<person> result;
+  // First transaction.
+  //
+  {
+    transaction t (db.begin ());
+    ...
+    t.commit ();
+  }
 
-  result johns (db.query<person> (query::first == "John"));
-  
+ // Second transaction. + // + { + transaction t (db.begin ()); + ... + t.commit (); + } -

It is best to view an instance of odb::result - as a handle to a stream, such as a file stream. While we can - make a copy of a result or assign one result to another, the - two instances will refer to the same result stream. Advancing - the current position in one instance will also advance it in - another. The result instance is only usable within the transaction - it was created in. Trying to manipulate the result after the - transaction has terminated leads to undefined behavior.

+ // Session 's' is terminated here. +} +
-

The odb::result class template conforms to the - standard C++ sequence requirements and has the following - interface:

+

The odb::session class has the following interface:

 namespace odb
 {
-  template <typename T>
-  class result
+  class session
   {
   public:
-    typedef odb::result_iterator<T> iterator;
+    session ();
+    ~session ();
+
+    // Copying or assignment of sessions is not supported.
+    //
+  private:
+    session (const session&);
+    session& operator= (const session&);
 
+    // Current session interface.
+    //
   public:
-    result ();
+    static session&
+    current ();
 
-    result (const result&);
+    static bool
+    has_current ();
 
-    result&
-    operator= (const result&);
+    static void
+    current (session&);
 
-    void
-    swap (result&)
+    static void
+    reset_current ();
 
+    // Object cache interface.
+    //
   public:
-    iterator
-    begin ();
-
-    iterator
-    end ();
+    typedef odb::database database_type;
 
-  public:
+    template <typename T>
     void
-    cache ();
+    insert (database_type&,
+            const object_traits<T>::id_type&,
+            const object_traits<T>::pointer_type&);
 
-    bool
-    empty () const;
+    template <typename T>
+    object_traits<T>::pointer_type
+    find (database_type&, const object_traits<T>::id_type&) const;
 
-    std::size_t
-    size () const;
+    template <typename T>
+    void
+    erase (database_type&, const object_traits<T>::id_type&);
   };
 }
   
-

The default constructor creates an empty result set. The - cache() function caches the returned objects' - state in the application's memory. We have already mentioned - result caching when we talked about query execution. As you - may remember the database::query() function - caches the result unless instructed not to by the caller. - The cache() function allows us to - cache the result at a later stage if it wasn't already - cached during query execution.

- -

If the result is cached, the database state of all the returned - objects is stored in the application's memory. Note that - the actual objects are still only instantiated on demand - during result iteration. It is the raw database state that - is cached in memory. In contrast, for uncached results - the object's state is sent by the database system one object - at a time as the iteration progresses.

- -

Uncached results can improve the performance of both the application - and the database system in situations where we have a large - number of objects in the result or if we will only examine - a small portion of the returned objects. However, uncached - results have a number of limitations. There can only be one - uncached result in a transaction. Creating another result - (cached or uncached) by calling database::query() - will invalidate the existing uncached result. Furthermore, - calling any other database functions, such as update() - or erase() will also invalidate the uncached result.

- -

The empty() function returns true if - there are no objects in the result and false otherwise. - The size() function can only be called for cached results. - It returns the number of objects in the result. If we call this - function on an uncached result, the odb::result_not_cached - exception is thrown.

+

The session constructor creates a new session and sets it as a + current session for this thread. If we try to create a session + while there is already a current session in effect, this constructor + throws the odb::already_in_session exception. The + destructor clears the current session for this thread if this + session is the current one.

-

To iterate over the objects in a result we use the - begin() and end() functions - together with the odb::result<T>::iterator - type, for example:

+

The static current() accessor returns the currently active + session for this thread. If there is no active session, this function + throws the odb::not_in_session exception. We can check + whether there is a session in effect in this thread using the + has_current() static function.

-
-  result r (db.query<person> (query::first == "John"));
+  

The static current() modifier allows us to set the + current session for this thread. The reset_current + static function clears the current session. These two functions + allow for more advanced use cases, such as multiplexing + between two or more sessions in the same thread.

- for (result::iterator i (r.begin ()); i != r.end (); ++i) - { - ... - } -
+

We normally don't use the object cache interface directly. However, + it could be useful in some cases, for example, to find out whether + an object has already been loaded.

-

The result iterator is an input iterator which means that the - only two position operations that it supports are to move to the - next object and to determine whether the end of the result stream - has been reached. In fact, the result iterator can only be in two - states: the current position and the end position. If we have - two iterators pointing to the current position and then we - advance one of them, the other will advance as well. This, - for example, means that it doesn't make sense to store an - iterator that points to some object of interest in the result - stream with the intent of dereferencing it after the iteration - is over. Instead, we would need to store the object itself.

+

8.1 Object Cache

-

The result iterator has the following dereference functions - that can be used to access the pointed-to object:

+

A session is an object cache. Every time an object is made persistent + by calling the database::persist() function (@@ ref), loaded + by calling the database::load() or database::find() + function (@@ ref), or loaded by iterating over a query result (@@ ref), + the pointer to the persistent object, in the form of the canonical object + pouinter (@@ ref), is stored in the session. For as long as the + session is in effect, any subsequent calls to load the same object will + return the cached instance. When an object's state is deleted from the + database with the database::erase() function (@@ ref), the + cached object pointer is removed from the session. For example:

-namespace odb
-{
-  template <typename T>
-  class result_iterator
-  {
-  public:
-    T*
-    operator-> () const;
+shared_ptr<person> p (new person ("John", "Doe"));
 
-    T&
-    operator* () const;
+session s;
+transaction t (db.begin ());
 
-    typename object_traits<T>::pointer_type
-    load ();
+unsigned long id (db.persist (p));            // p is cached in s.
+shared_ptr<person> p1 (db.load<person> (id)); // p1 same as p.
 
-    void
-    load (T& x);
-  };
-}
+t.commit ();
   
-

When we call the * or -> operator, - the iterator will allocate a new instance of the object class - in the dynamic memory, load its state from the database - state, and return a reference or pointer to the new instance. The - iterator maintains the ownership of the returned object and will - return the same pointer for subsequent calls to either of these - operators until it is advanced to the next object or we call - the first load() function (see below). For example:

- -
-  result r (db.query<person> (query::first == "John"));
 
-  for (result::iterator i (r.begin ()); i != r.end ();)
-  {
-    cout << i->last () << endl; // Create an object.
-    person& p (*i);             // Reference to the same object.
-    cout << p.age () << endl;
-    ++i;                        // Free the object.
-  }
-  
+

The per-object caching policies depend on the object pointer kind + (@@ ref). Objects with a unique pointer, such as std::auto_ptr, + as an object pointer are never cached since it is not possible to have + two such pointers pointing to the same object. When an object is + persisted via a pointer or loaded as a dynamically allocated instance, + objects with both raw and shared pointers as object pointers are + cached. If an object is persisted as a reference or loaded into + a pre-allocated instance, the object is only cached if its object + pointer is a raw pointer.

-

The overloaded result_iterator::load() functions are - similar to database::load(). The first function - returns a dynamically allocated instance of the current - object. As an optimization, if the iterator already owns an object - as a result of an earlier - call to the * or -> operator, then it - relinquishes the ownership of this object and returns it instead. - This allows us to write code like this without worrying about - a double allocation:

+

Finally, the session caches both constant and non-constant objects, + depending on whether a constant reference or constant pointer was + passed to the database::persist() function (in contrast, + when loaded, objects are always created and cached as non-constant). + If we try to load an object as non-constant that was previously + persisted and cached as constant, the odb::const_object + exception is thrown. The following transaction illustartes the + situation where this would happen:

-  result r (db.query<person> (query::first == "John"));
-
-  for (result::iterator i (r.begin ()); i != r.end (); ++i)
-  {
-    if (i->last == "Doe")
-    {
-      auto_ptr p (i.load ());
-      ...
-    }
-  }
-  
- -

Note, however, that because of this optimization, a subsequent - to load() call to the * or -> - operator results in the allocation of a new object.

+shared_ptr<const person> p (new person ("John", "Doe")); -

The second load() function allows - us to load the current object's state into an existing instance. - For example:

+session s; +transaction t (db.begin ()); -
-  result r (db.query<person> (query::first == "John"));
+unsigned long id (db.persist (p));
+shared_ptr<const person> p1 (db.load<const person> (id)); // Ok.
+shared_ptr<person> p2 (db.load<person> (id));             // Exception.
 
-  person p;
-  for (result::iterator i (r.begin ()); i != r.end (); ++i)
-  {
-    i.load (p);
-    cout << p.last () << endl;
-    cout << i.age () << endl;
-  }
+t.commit ();
   
-

5 ODB Pragma Language

+

5 ODB Pragma Language

As we have already seen in previous chapters, ODB uses a pragma-based language to capture database-specific information about C++ types. @@ -5034,10 +5086,10 @@ private: the C++ compiler to build our application. Some C++ compilers issue warnings about pragmas that they do not recognize. There are several ways to deal with this problem which are covered - at the end of this chapter in Section 5.4, + at the end of this chapter in Section 9.4, "C++ Compiler Warnings".

-

5.1 Object Type Pragmas

+

9.1 Object Type Pragmas

A pragma with the object qualifier declares a C++ class as a persistent object type. The qualifier can be optionally followed, @@ -5054,19 +5106,19 @@ private:

table the table name for the persistent class5.1.19.1.1
pointer the pointer type for the persistent class5.1.29.1.2
-

5.1.1 table

+

9.1.1 table

The table specifier specifies the table name that should be used to store objects of the class in a relational database. For @@ -5083,7 +5135,7 @@ class person

If the table name is not specified, the class name is used as the table name.

-

5.1.2 pointer

+

9.1.2 pointer

The pointer specifier specifies the object pointer type for the persistent class. The object pointer type is used to return, @@ -5133,7 +5185,7 @@ class person

For additional information on object pointers, refer to Section @@, "".

-

5.2 Value Type Pragmas

+

9.2 Value Type Pragmas

A pragma with the value qualifier describes a value type. It can be optionally followed, in any order, by one or more @@ -5150,67 +5202,67 @@ class person type the database type for the value type - 5.2.1 + 9.2.1 not_null object pointer cannot be NULL - 5.2.2 + 9.2.2 unordered ordered container should be stored unordered - 5.2.3 + 9.2.3 index_type the database type for the container's index type - 5.2.4 + 9.2.4 key_type the database type for the container's key type - 5.2.5 + 9.2.5 value_type the database type for the container's value type - 5.2.6 + 9.2.6 id_column the column name for the container's table object id - 5.2.7 + 9.2.7 index_column the column name for the container's table index - 5.2.8 + 9.2.8 key_column the column name for the container's table key - 5.2.9 + 9.2.9 value_column the column name for the container's table value - 5.2.10 + 9.2.10

Many of the value type specifiers have corresponding member type - specifiers with the same names (see Section 5.3, + specifiers with the same names (see Section 9.3, "Data Member Pragmas"). The behavior of such specifiers for members is similar to that for value types. The only difference is the scope. A particular value type specifier applies to all @@ -5219,7 +5271,7 @@ class person to a single member. In other words, member specifiers take precedence over values specified with value specifiers.

-

5.2.1 type

+

9.2.1 type

The type specifier specifies the native database type that should be used for data members of this type. For example:

@@ -5241,7 +5293,7 @@ private: types, such as bool, int, and std::string and the database types for each supported database system. For more information on the default mapping, - refer to Chapter 6, "Database Systems".

+ refer to Chapter 10, "Database Systems".

In the above example we changed the mapping for the bool type which is now mapped to the INT database type. In @@ -5267,7 +5319,7 @@ private: mapping example in the odb-examples package shows how to do this for all the supported database systems.

-

5.2.2 not_null

+

9.2.2 not_null

The not_null specifier specifies that an object pointer or a container of object pointers type cannot have or contain the @@ -5295,7 +5347,7 @@ typedef std::vector<shared_ptr<account> > accounts; #pragma db value(accounts) not_null -

5.2.3 unordered

+

9.2.3 unordered

The unordered specifier specifies that the ordered container should be stored in the database unordered. The database @@ -5308,13 +5360,13 @@ typedef std::vector<std::string> names; #pragma db value(names) unordered -

5.2.4 index_type

+

9.2.4 index_type

The index_type specifier specifies the native database type that should be used for the ordered container's index column. The semantics of index_type are similar to that of the type specifier (see - Section 5.2.1, "type"). The native + Section 9.2.1, "type"). The native database type is expected to be an integer type. For example:

@@ -5322,13 +5374,13 @@ typedef std::vector<std::string> names;
 #pragma db value(names) index_type("SMALLINT UNSIGNED NOT NULL")
   
-

5.2.5 key_type

+

9.2.5 key_type

The key_type specifier specifies the native database type that should be used for the map container's key column. The semantics of key_type are similar to that of the type specifier (see - Section 5.2.1, "type"). For + Section 9.2.1, "type"). For example:

@@ -5336,13 +5388,13 @@ typedef std::map<unsigned short, float> age_weight_map;
 #pragma db value(age_weight_map) key_type("INT UNSIGNED NOT NULL")
   
-

5.2.6 value_type

+

9.2.6 value_type

The value_type specifier specifies the native database type that should be used for the container's value column. The semantics of value_type are similar to that of the type specifier (see - Section 5.2.1, "type"). For + Section 9.2.1, "type"). For example:

@@ -5350,7 +5402,7 @@ typedef std::vector<std::string> names;
 #pragma db value(names) value_type("VARCHAR(255) NOT NULL")
   
-

5.2.7 id_column

+

9.2.7 id_column

The id_column specifier specifies the column name that should be used to store the object id in the @@ -5364,7 +5416,7 @@ typedef std::vector<std::string> names;

If the column name is not specified, then object_id is used by default.

-

5.2.8 index_column

+

9.2.8 index_column

The index_column specifier specifies the column name that should be used to store the element index in the @@ -5378,7 +5430,7 @@ typedef std::vector<std::string> names;

If the column name is not specified, then index is used by default.

-

5.2.9 key_column

+

9.2.9 key_column

The key_column specifier specifies the column name that should be used to store the key in the map @@ -5392,7 +5444,7 @@ typedef std::map<unsigned short, float> age_weight_map;

If the column name is not specified, then key is used by default.

-

5.2.10 value_column

+

9.2.10 value_column

The value_column specifier specifies the column name that should be used to store the element value in the @@ -5409,7 +5461,7 @@ typedef std::map<unsigned short, float> age_weight_map; -

5.3 Data Member Pragmas

+

9.3 Data Member Pragmas

A pragma with the member qualifier or a positioned pragma without a qualifier describes a data member. It can @@ -5427,104 +5479,104 @@ typedef std::map<unsigned short, float> age_weight_map; id the member is an object id - 5.3.1 + 9.3.1 auto id is assigned by the database - 5.3.2 + 9.3.2 type the database type for the member - 5.3.3 + 9.3.3 column the column name for the member - 5.3.4 + 9.3.4 transient the member is not stored in the database - 5.3.5 + 9.3.5 not_null object pointer cannot be NULL - 5.3.6 + 9.3.6 inverse the member is an inverse side of a bidirectional relationship - 5.3.7 + 9.3.7 unordered ordered container should be stored unordered - 5.3.8 + 9.3.8 table the table name for the container - 5.3.9 + 9.3.9 index_type the database type for the container's index type - 5.3.10 + 9.3.10 key_type the database type for the container's key type - 5.3.11 + 9.3.11 value_type the database type for the container's value type - 5.3.12 + 9.3.12 id_column the column name for the container's table object id - 5.3.13 + 9.3.13 index_column the column name for the container's table index - 5.3.14 + 9.3.14 key_column the column name for the container's table key - 5.3.15 + 9.3.15 value_column the column name for the container's table value - 5.3.16 + 9.3.16

Many of the member specifiers have corresponding value type - specifiers with the same names (see Section 5.2, + specifiers with the same names (see Section 9.2, "Value Type Pragmas"). The behavior of such specifiers for members is similar to that for value types. The only difference is the scope. A particular value type specifier applies to all @@ -5533,7 +5585,7 @@ typedef std::map<unsigned short, float> age_weight_map; to a single member. In other words, member specifiers take precedence over values specified with value specifiers.

-

5.3.1 id

+

9.3.1 id

The id specifier specifies that the data member contains the object id. Every persistent class must have a member designated @@ -5554,7 +5606,7 @@ private:

In a relational database, an identifier member is mapped to a primary key.

-

5.3.2 auto

+

9.3.2 auto

The auto specifier specifies that the object's identifier is automatically assigned by the database. Only a member that was @@ -5582,7 +5634,7 @@ private:

For additional information on the automatic identifier assignment, refer to Section 3.5, "Making Objects Persistent".

-

5.3.3 type

+

9.3.3 type

The type specifier specifies the native database type that should be used for the data member. For example:

@@ -5599,7 +5651,7 @@ private: }; -

5.3.4 column

+

9.3.4 column

The column specifier specifies the column name that should be used to store the member in a relational database. @@ -5618,14 +5670,14 @@ private:

For a member of a composite value type, the column specifier - specifies the column name prefix. Refer to Section Z.1, + specifies the column name prefix. Refer to Section 7.1, "Composite Value Column and Table Names" for details.

If the column name is not specified, it is derived from the member name by removing the common member name decorations, such as leading and trailing underscores, the m_ prefix, etc.

-

5.3.5 transient

+

9.3.5 transient

The transient specifier instructs the ODB compiler not to store the data member in the database. For example:

@@ -5648,7 +5700,7 @@ private: references that are only meaningful in the application's memory, as well as utility members such as mutexes, etc.

-

5.3.6 not_null

+

9.3.6 not_null

The not_null specifier specifies that the member of an object pointer or a container of object pointers type cannot @@ -5677,7 +5729,7 @@ private: }; -

5.3.7 inverse

+

9.3.7 inverse

The inverse specifier specifies that the member of an object pointer or a container of object pointers type is an @@ -5717,9 +5769,9 @@ private: information. Only ordered and set containers can be used for inverse members. If an inverse member is of an ordered container type, it is automatically marked as unordered (see - Section 5.3.8, "unordered").

+ Section 9.3.8, "unordered").

-

5.3.8 unordered

+

9.3.8 unordered

The unordered specifier specifies that the member of an ordered container type should be stored in the database unordered. @@ -5739,7 +5791,7 @@ private: }; -

5.3.9 table

+

9.3.9 table

The table specifier specifies the table name that should be used to store the contents of the container member. For example:

@@ -5767,16 +5819,16 @@ private:

The table specifier can also be used for members of composite value types. In this case it specifies the table name prefix for container members inside the composite value type. Refer - to Section Z.1, "Composite Value Column and Table + to Section 7.1, "Composite Value Column and Table Names" for details.

-

5.3.10 index_type

+

9.3.10 index_type

The index_type specifier specifies the native database type that should be used for the ordered container's index column of the data member. The semantics of index_type are similar to that of the type specifier (see - Section 5.3.3, "type"). The native + Section 9.3.3, "type"). The native database type is expected to be an integer type. For example:

@@ -5791,13 +5843,13 @@ private:
 };
   
-

5.3.11 key_type

+

9.3.11 key_type

The key_type specifier specifies the native database type that should be used for the map container's key column of the data member. The semantics of key_type are similar to that of the type specifier (see - Section 5.3.3, "type"). For + Section 9.3.3, "type"). For example:

@@ -5812,13 +5864,13 @@ private:
 };
   
-

5.3.12 value_type

+

9.3.12 value_type

The value_type specifier specifies the native database type that should be used for the container's value column of the data member. The semantics of value_type are similar to that of the type specifier (see - Section 5.3.3, "type"). For + Section 9.3.3, "type"). For example:

@@ -5833,14 +5885,14 @@ private:
 };
   
-

5.3.13 id_column

+

9.3.13 id_column

The id_column specifier specifies the column name that should be used to store the object id in the container's table for the member. The semantics of id_column are similar to that of the column specifier (see - Section 5.3.4, "column"). + Section 9.3.4, "column"). For example:

@@ -5858,14 +5910,14 @@ private:
   

If the column name is not specified, then object_id is used by default.

-

5.3.14 index_column

+

9.3.14 index_column

The index_column specifier specifies the column name that should be used to store the element index in the ordered container's table for the member. The semantics of index_column are similar to that of the column specifier (see - Section 5.3.4, "column"). + Section 9.3.4, "column"). For example:

@@ -5883,14 +5935,14 @@ private:
   

If the column name is not specified, then index is used by default.

-

5.3.15 key_column

+

9.3.15 key_column

The key_column specifier specifies the column name that should be used to store the key in the map container's table for the member. The semantics of key_column are similar to that of the column specifier (see - Section 5.3.4, "column"). + Section 9.3.4, "column"). For example:

@@ -5908,14 +5960,14 @@ private:
   

If the column name is not specified, then key is used by default.

-

5.3.16 value_column

+

9.3.16 value_column

The value_column specifier specifies the column name that should be used to store the element value in the container's table for the member. The semantics of value_column are similar to that of the column specifier (see - Section 5.3.4, "column"). + Section 9.3.4, "column"). For example:

@@ -5933,7 +5985,7 @@ private:
   

If the column name is not specified, then value is used by default.

-

5.4 C++ Compiler Warnings

+

9.4 C++ Compiler Warnings

When the C++ header file defining our persistent classes and containing ODB pragmas is processed by a C++ compiler, it @@ -5988,7 +6040,7 @@ private:

The disadvantage of this approach is that it can quickly become overly verbose when positioned pragmas are used.

-

5.4.1 GNU C++

+

9.4.1 GNU C++

GNU g++ does not issue warnings about unknown pragmas unless requested with the -Wall command line option. @@ -6000,7 +6052,7 @@ private: g++ -Wall -Wno-unknown-pragmas ...

-

5.4.2 Visual C++

+

9.4.2 Visual C++

Microsoft Visual C++ issues an unknown pragma warning (C4068) at warning level 1 or higher. This means that unless we have disabled @@ -6035,7 +6087,7 @@ private: #pragma warning (pop)

-

5.4.3 Sun C++

+

9.4.3 Sun C++

The Sun C++ compiler does not issue warnings about unknown pragmas unless the +w or +w2 option is specified. @@ -6047,7 +6099,7 @@ private: CC +w -erroff=unknownpragma ...

-

5.4.4 IBM XL C++

+

9.4.4 IBM XL C++

IBM XL C++ issues an unknown pragma warning (1540-1401) by default. To disable this warning we can add the -qsuppress=1540-1401 @@ -6057,7 +6109,7 @@ CC +w -erroff=unknownpragma ... xlC -qsuppress=1540-1401 ...

-

5.4.5 HP aC++

+

9.4.5 HP aC++

HP aC++ (aCC) issues an unknown pragma warning (2161) by default. To disable this warning we can add the +W2161 @@ -6071,7 +6123,7 @@ aCC +W2161 ... -

6 Database Systems

+

10 Database Systems

This chapter covers topics specific to the database system implementations and their support in ODB. In particular, it @@ -6080,7 +6132,7 @@ aCC +W2161 ... and native database types.

-

6.1 MySQL Database

+

10.1 MySQL Database

To generate support code for the MySQL database you will need to pass the "--database mysql" @@ -6089,12 +6141,12 @@ aCC +W2161 ... library (libodb-mysql). All MySQL-specific ODB classes are defined in the odb::mysql namespace.

-

6.1.1 MySQL Type Mapping

+

10.1.1 MySQL Type Mapping

The following table summarizes the default mapping between basic C++ value types and MySQL database types. This mapping can be customized on the per-type and per-member basis using the ODB - Pragmas Language (see Chapter 5, "ODB Pragma + Pragmas Language (see Chapter 9, "ODB Pragma Language").

@@ -6187,7 +6239,7 @@ aCC +W2161 ... to VARCHAR(255) NOT NULL MySQL type. Otherwise, it is mapped to TEXT NOT NULL.

-

6.1.2 MySQL Database Class

+

10.1.2 MySQL Database Class

The MySQL database class has the following interface:

@@ -6326,7 +6378,7 @@ namespace odb

This constructor throws the odb::mysql::cli_exception exception if the MySQL option values are missing or invalid. - See section Section 6.1.4, "MySQL Exceptions" + See section Section 10.1.4, "MySQL Exceptions" for more information on this exception.

The static print_usage() function prints the list of options @@ -6350,7 +6402,7 @@ namespace odb handle, refer to the MySQL ODB runtime source code for the interface of the connection class.

-

6.1.3 Connection Factory

+

10.1.3 Connection Factory

The connection_factory abstract class has the following interface:

@@ -6464,7 +6516,7 @@ main (int argc, char* argv[]) } -

6.1.4 MySQL Exceptions

+

10.1.4 MySQL Exceptions

The MySQL ODB runtime library defines the following MySQL-specific exceptions:

-- cgit v1.1