From dce27fe8994e819a463e83212779d3d43bd2ba2e Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 13 Sep 2012 10:19:21 +0200 Subject: Document how to handle circular relationships --- doc/manual.xhtml | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 166 insertions(+), 12 deletions(-) (limited to 'doc') diff --git a/doc/manual.xhtml b/doc/manual.xhtml index 3939952..bf5c733 100644 --- a/doc/manual.xhtml +++ b/doc/manual.xhtml @@ -379,8 +379,9 @@ for consistency. - 6.3Lazy Pointers - 6.4Using Custom Smart Pointers + 6.3Circular Relationships + 6.4Lazy Pointers + 6.5Using Custom Smart Pointers @@ -2537,7 +2538,7 @@ namespace accounting provide support for smart pointers found in these frameworks and libraries (Part III, "Profiles"). It is also easy to add support for our own smart pointers, as described in - Section 6.4, "Using Custom Smart Pointers".

+ Section 6.5, "Using Custom Smart Pointers".

3.4 Database

@@ -4972,11 +4973,11 @@ private: for shared_ptr/weak_ptr (TR1 or C++11), std::unique_ptr (C++11), std::auto_ptr, and raw pointers. Plus, ODB profile - libraries, that available for commonly used frameworks and libraries + libraries, that are available for commonly used frameworks and libraries (such as Boost and Qt), provide support for smart pointers found in these frameworks and libraries (Part III, "Profiles"). It is also easy to add support for a custom smart pointer as discussed later - in Section 6.4, "Using Custom Smart Pointers". Any + in Section 6.5, "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 (Section 3.3, "Object and View Pointers"). For @@ -4994,7 +4995,7 @@ private: 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".

+ detail in Section 6.4, "Lazy Pointers".

As a simple example, consider the following employee-employer relationship. Code examples presented in this chapter @@ -5416,7 +5417,7 @@ t.commit (); loaded employee object preventing its immediate deletion. Another way to resolve this problem is to avoid immediate loading of the pointed-to objects using lazy weak - pointers. Lazy pointers are discussed in Section 6.3, + pointers. Lazy pointers are discussed in Section 6.4, "Lazy Pointers" later in this chapter.

Above, to model a bidirectional relationship in persistent classes, @@ -5689,7 +5690,160 @@ CREATE TABLE employee ( id BIGINT UNSIGNED NOT NULL PRIMARY KEY); -

6.3 Lazy Pointers

+

6.3 Circular Relationships

+ +

A relationship between two persistent classes is circular if each + of them references the other. Bidirectional relationships are + always circular. A unidirectional relationship combined with + inheritance (Chapter 8, "Inheritance") can also + be circular. For example, the employee class could + derive from person which, in turn, could contain a + pointer to employee.

+ +

We don't need to do anything extra if persistent classes with + circular dependencies are defined in the same header + file. Specifically, ODB will make sure that the database tables + and foreign key constraints are created in the correct order. As a + result, unless you have good reasons not to, it is recommended that + you keep persistent classes with circular dependencies in the same + header file.

+ +

If you have to keep such classes in separate header files, then + there are two extra steps that you may need to take in order to + use these classes with ODB. Consider again the example from + Section 6.2.1, "One-to-One Relationships" + but this time with the classes defined in separate headers:

+ +
+// position.hxx
+//
+class employee;
+
+#pragma db object
+class position
+{
+  ...
+
+  #pragma db id
+  unsigned long id_;
+
+  #pragma db inverse(position_)
+  weak_ptr<employee> employee_;
+};
+  
+ +
+// employee.hxx
+//
+#include "position.hxx"
+
+#pragma db object
+class employee
+{
+  ...
+
+  #pragma db id
+  unsigned long id_;
+
+  #pragma db not_null
+  shared_ptr<position> position_;
+};
+  
+ +

Note that the position.hxx header contains only the forward + declaration for employee. While this is sufficient to + define a valid, from the C++ point of view, position class, + the ODB compiler needs to "see" the definitions of the pointed-to + persistent classes. There are several ways we can fulfil this + requirement. The easiest is to simply include employee.hxx + at the end of position.hxx:

+ +
+// position.hxx
+//
+class employee;
+
+#pragma db object
+class position
+{
+  ...
+};
+
+#include "employee.hxx"
+  
+ +

We can also limit this inclusion only to the time when + position.hxx is compiled with the ODB compiler:

+ +
+// position.hxx
+//
+
+...
+
+#ifdef ODB_COMPILER
+#  include "employee.hxx"
+#endif
+  
+ +

Finally, if we don't want to modify position.hxx, + then we can add employee.hxx to the ODB compilation + process with the --odb-epilogue option. For example:

+ +
+odb ... --odb-epilogue "#include \"employee.hxx\"" position.hxx
+  
+ +

Note also that in this example we didn't have to do anything extra + for employee.hxx because it already includes + position.hxx. However, if instead it relied only + on the forward declaration of the position class, + then we would have to handle it in the same way as + position.hxx.

+ +

The other difficulty with separately defined classes involving + circular relationships has to do with the correct order of foreign + key constraint creation in the generated database schema. In + the above example, if we generate the database schema as + standalone SQL files, then we will end up with two such files: + position.sql and employee.sql. + If we try to execute employee.sql first, then + we will get an error indicating that the table corresponding to + the position class and referenced by the foreign + key constraint corresponding to the position_ + pointer does not yet exist.

+ +

Note that there is no such problem if the database schema + is embedded in the generated C++ code instead of being produced + as standalone SQL files. In this case, the ODB compiler is + able to ensure the correct creation order even if the classes + are defined in separate header files.

+ +

In certain cases, for example, a bidirectional relationship + with an inverse side, this problem can be resolved by executing + the database schema creation files in the correct order. In our + example, this would be position.sql first + and employee.sql second. However, this approach + doesn't scale beyond simple object models.

+ +

A more robust solution to this problem is to generate the database + schema for all the persistent classes into a single SQL file. This + way, the ODB compiler can again ensure the correct creation order + of tables and foreign keys. To instruct the ODB compiler to produce + a combined schema file for several headers we can use the + --generate-schema-only and --at-once + options. For example:

+ +
+odb ... --generate-schema-only --at-once --output-name schema \
+position.hxx employee.hxx
+  
+ +

The result of the above command is a single schema.sql + file that contains the database creation code for both + position and employee classes.

+ +

6.4 Lazy Pointers

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

@@ -5981,7 +6135,7 @@ db.persist (e); t.commit (); -

6.4 Using Custom Smart Pointers

+

6.5 Using Custom Smart Pointers

While the ODB runtime and profile libraries provide support for the majority of widely-used pointers, it is also easy to add @@ -8654,7 +8808,7 @@ t.commit ();

The per-object caching policies depend on the object pointer kind - (Section 6.4, "Using Custom Smart Pointers"). + (Section 6.5, "Using Custom Smart Pointers"). Objects with a unique pointer, such as std::auto_ptr or std::unique_ptr, as an object pointer are never cached since it is not possible to have two such pointers pointing @@ -17205,7 +17359,7 @@ class person odb::boost::lazy_weak_ptr. You will need to include the <odb/boost/lazy-ptr.hxx> header file to make the lazy variants available in your application. For the description of the lazy - pointer interface and semantics refer to Section 6.3, + pointer interface and semantics refer to Section 6.4, "Lazy Pointers". The following example shows how we can use these smart pointers to establish a relationship between persistent objects.

@@ -18302,7 +18456,7 @@ class person and QLazyWeakPointer. You will need to include the <odb/qt/lazy-ptr.hxx> header file to make the lazy variants available in your application. For the description of the lazy - pointer interface and semantics refer to Section 6.3, + pointer interface and semantics refer to Section 6.4, "Lazy Pointers". The following example shows how we can use these smart pointers to establish a relationship between persistent objects.

-- cgit v1.1