From 09d7377f81aeb8fde4aa1698e946457f03380d45 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 6 May 2013 12:05:39 +0200 Subject: Add support for object sections Sections are an optimization mechanism that allows the partitioning of data members of a persistent class into groups that can be separately loaded and/or updated. --- NEWS | 6 + doc/manual.xhtml | 2401 ++++++++++++++++++++++++------------- odb/common.cxx | 48 +- odb/common.hxx | 38 +- odb/context.cxx | 247 +++- odb/context.hxx | 305 ++++- odb/context.ixx | 27 + odb/features.hxx | 1 + odb/header.cxx | 44 +- odb/inline.cxx | 26 + odb/pragma.cxx | 157 ++- odb/processor.cxx | 372 +++++- odb/relational/common.hxx | 13 +- odb/relational/context.cxx | 2 +- odb/relational/context.hxx | 8 +- odb/relational/context.ixx | 4 +- odb/relational/header.cxx | 84 +- odb/relational/header.hxx | 198 +++ odb/relational/inline.hxx | 89 +- odb/relational/mssql/header.cxx | 26 + odb/relational/mssql/source.cxx | 51 +- odb/relational/mysql/context.cxx | 32 +- odb/relational/mysql/context.hxx | 2 +- odb/relational/oracle/source.cxx | 12 + odb/relational/pgsql/context.cxx | 46 +- odb/relational/pgsql/context.hxx | 2 +- odb/relational/pgsql/header.cxx | 48 +- odb/relational/pgsql/source.cxx | 163 ++- odb/relational/source.cxx | 1093 ++++++++++++++--- odb/relational/source.hxx | 1540 ++++++++++++++++++++---- odb/relational/sqlite/context.cxx | 32 +- odb/relational/sqlite/context.hxx | 2 +- odb/validator.cxx | 156 ++- 33 files changed, 5934 insertions(+), 1341 deletions(-) create mode 100644 odb/context.ixx diff --git a/NEWS b/NEWS index 720834c..c7742fa 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,11 @@ Version 2.3.0 + * Support for object sections. Sections are an optimization mechanism that + allows the partitioning of data members of a persistent class into groups + that can be separately loaded and/or updated. For more information, refer + to Chapter 9, "Sections" in the ODB manual as well as the 'section' + example in the odb-examples package. + * Support for pattern matching (SQL LIKE operator) in the C++-integrated queries. For more information, refer to Section 4.1, "ODB Query Language" in the ODB manual. diff --git a/doc/manual.xhtml b/doc/manual.xhtml index 2746a0c..f754e40 100644 --- a/doc/manual.xhtml +++ b/doc/manual.xhtml @@ -429,154 +429,168 @@ for consistency. - 9Views + 9Sections - - - - - - + + + +
9.1Object Views
9.2Table Views
9.3Mixed Views
9.4View Query Conditions
9.5Native Views
9.6Other View Features and Limitations
9.1Sections and Inheritance
9.2Sections and Optimistic Concurrency
9.3Sections and Lazy Pointers
9.4Sections and Change-Tracking Containers
- 10Session + 10Views - - + + + + + +
10.1Object Cache
10.2Custom Sessions
10.1Object Views
10.2Table Views
10.3Mixed Views
10.4View Query Conditions
10.5Native Views
10.6Other View Features and Limitations
- 11Optimistic Concurrency + 11Session + + + +
11.1Object Cache
11.2Custom Sessions
+ - 12ODB Pragma Language + 12Optimistic Concurrency + + + + 14ODB Pragma Language - - - - - - + - + - @@ -585,9 +599,9 @@ for consistency. - @@ -597,13 +611,13 @@ for consistency. - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +
12.1Object Type Pragmas + 14.1Object Type Pragmas - - - - - - - - - - - - + + + + + + + + + + + + +
12.1.1table
12.1.2pointer
12.1.3abstract
12.1.4readonly
12.1.5optimistic
12.1.6no_id
12.1.7callback
12.1.8schema
12.1.9polymorphic
12.1.10session
12.1.11definition
12.1.12transient
14.1.1table
14.1.2pointer
14.1.3abstract
14.1.4readonly
14.1.5optimistic
14.1.6no_id
14.1.7callback
14.1.8schema
14.1.9polymorphic
14.1.10session
14.1.11definition
14.1.12transient
14.1.13sectionable
12.2View Type Pragmas + 14.2View Type Pragmas - - - - - - - + + + + + + +
12.2.1object
12.2.2table
12.2.3query
12.2.4pointer
12.2.5callback
12.2.6definition
12.2.7transient
14.2.1object
14.2.2table
14.2.3query
14.2.4pointer
14.2.5callback
14.2.6definition
14.2.7transient
12.3Value Type Pragmas + 14.3Value Type Pragmas - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + +
12.3.1type
12.3.2id_type
12.3.3null/not_null
12.3.4default
12.3.5options
12.3.6readonly
12.3.7definition
12.3.8transient
12.3.9unordered
12.3.10index_type
12.3.11key_type
12.3.12value_type
12.3.13value_null/value_not_null
12.3.14id_options
12.3.15index_options
12.3.16key_options
12.3.17value_options
12.3.18id_column
12.3.19index_column
12.3.20key_column
12.3.21value_column
14.3.1type
14.3.2id_type
14.3.3null/not_null
14.3.4default
14.3.5options
14.3.6readonly
14.3.7definition
14.3.8transient
14.3.9unordered
14.3.10index_type
14.3.11key_type
14.3.12value_type
14.3.13value_null/value_not_null
14.3.14id_options
14.3.15index_options
14.3.16key_options
14.3.17value_options
14.3.18id_column
14.3.19index_column
14.3.20key_column
14.3.21value_column
12.4Data Member Pragmas + 14.4Data Member Pragmas - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
12.4.1id
12.4.2auto
12.4.3type
12.4.4id_type
12.4.5get/set/access
12.4.6null/not_null
12.4.7default
12.4.8options
12.4.9column (object, composite value)
12.4.10column (view)
12.4.11transient
12.4.12readonly
12.4.13virtual
12.4.14inverse
12.4.15version
12.4.16index
12.4.17unique
12.4.18unordered
12.4.19table
12.4.20index_type
12.4.21key_type
12.4.22value_type
12.4.23value_null/value_not_null
12.4.24id_options
12.4.25index_options
12.4.26key_options
12.4.27value_options
12.4.28id_column
12.4.29index_column
12.4.30key_column
12.4.31value_column
14.4.1id
14.4.2auto
14.4.3type
14.4.4id_type
14.4.5get/set/access
14.4.6null/not_null
14.4.7default
14.4.8options
14.4.9column (object, composite value)
14.4.10column (view)
14.4.11transient
14.4.12readonly
14.4.13virtual
14.4.14inverse
14.4.15version
14.4.16index
14.4.17unique
14.4.18unordered
14.4.19table
14.4.20load/update
14.4.21section
14.4.22index_type
14.4.23key_type
14.4.24value_type
14.4.25value_null/value_not_null
14.4.26id_options
14.4.27index_options
14.4.28key_options
14.4.29value_options
14.4.30id_column
14.4.31index_column
14.4.32key_column
14.4.33value_column
12.5Namespace Pragmas + 14.5Namespace Pragmas - - - - + + + +
12.5.1pointer
12.5.2table
12.5.3schema
12.5.4session
14.5.1pointer
14.5.2table
14.5.3schema
14.5.4session
12.6Index Definition Pragmas14.6Index Definition Pragmas
12.7Database Type Mapping Pragmas14.7Database Type Mapping Pragmas
12.8C++ Compiler Warnings + 14.8C++ Compiler Warnings - - - - - - + + + + + +
12.8.1GNU C++
12.8.2Visual C++
12.8.3Sun C++
12.8.4IBM XL C++
12.8.5HP aC++
12.8.6Clang
14.8.1GNU C++
14.8.2Visual C++
14.8.3Sun C++
14.8.4IBM XL C++
14.8.5HP aC++
14.8.6Clang
13Advanced Techniques and Mechanisms + 15Advanced Techniques and Mechanisms - +
13.1Transaction Callbacks
15.1Transaction Callbacks
14Multi-Database Support + 16Multi-Database Support - + - @@ -612,156 +626,156 @@ for consistency. - - - - - @@ -771,35 +785,35 @@ for consistency. - + - - + - + - + - + - + - + - +
14.1Static Multi-Database Support
16.1Static Multi-Database Support
14.2Dynamic Multi-Database Support + 16.2Dynamic Multi-Database Support - +
14.2.214.2.2 Dynamic Loading of Database Support Code
16.2.216.2.2 Dynamic Loading of Database Support Code
15MySQL Database + 17MySQL Database - - - - + + + - - +
15.1MySQL Type Mapping + 17.1MySQL Type Mapping - - + +
15.1.1String Type Mapping
15.1.2Binary Type Mapping
17.1.1String Type Mapping
17.1.2Binary Type Mapping
15.2MySQL Database Class
15.3MySQL Connection and Connection Factory
15.4MySQL Exceptions
17.2MySQL Database Class
17.3MySQL Connection and Connection Factory
17.4MySQL Exceptions
15.5MySQL Limitations + 17.5MySQL Limitations - +
15.5.1Foreign Key Constraints
17.5.1Foreign Key Constraints
15.6MySQL Index Definition
17.6MySQL Index Definition
16SQLite Database + 18SQLite Database - - - - + + + - - +
16.1SQLite Type Mapping + 18.1SQLite Type Mapping - - + +
16.1.1String Type Mapping
16.1.2Binary Type Mapping
18.1.1String Type Mapping
18.1.2Binary Type Mapping
16.2SQLite Database Class
16.3SQLite Connection and Connection Factory
16.4SQLite Exceptions
18.2SQLite Database Class
18.3SQLite Connection and Connection Factory
18.4SQLite Exceptions
16.5SQLite Limitations + 18.5SQLite Limitations - - - - - - + + + + + +
16.5.1Query Result Caching
16.5.2Automatic Assignment of Object Ids
16.5.3Foreign Key Constraints
16.5.4Constraint Violations
16.5.5Sharing of Queries
16.5.6Forced Rollback
18.5.1Query Result Caching
18.5.2Automatic Assignment of Object Ids
18.5.3Foreign Key Constraints
18.5.4Constraint Violations
18.5.5Sharing of Queries
18.5.6Forced Rollback
16.6SQLite Index Definition
18.6SQLite Index Definition
17PostgreSQL Database + 19PostgreSQL Database - - - - + + + - - +
17.1PostgreSQL Type Mapping + 19.1PostgreSQL Type Mapping - - + +
17.1.1String Type Mapping
17.1.2Binary Type and UUID Mapping
19.1.1String Type Mapping
19.1.2Binary Type and UUID Mapping
17.2PostgreSQL Database Class
17.3PostgreSQL Connection and Connection Factory
17.4PostgreSQL Exceptions
19.2PostgreSQL Database Class
19.3PostgreSQL Connection and Connection Factory
19.4PostgreSQL Exceptions
17.5PostgreSQL Limitations + 19.5PostgreSQL Limitations - - - - - - + + + + + +
17.5.1Query Result Caching
17.5.2Foreign Key Constraints
17.5.3Unique Constraint Violations
17.5.4Date-Time Format
17.5.5Timezones
17.5.6NUMERIC Type Support
19.5.1Query Result Caching
19.5.2Foreign Key Constraints
19.5.3Unique Constraint Violations
19.5.4Date-Time Format
19.5.5Timezones
19.5.6NUMERIC Type Support
17.6PostgreSQL Index Definition
19.6PostgreSQL Index Definition
18Oracle Database + 20Oracle Database - - - - + + + - - +
18.1Oracle Type Mapping + 20.1Oracle Type Mapping - - + +
18.1.1String Type Mapping
18.1.2Binary Type Mapping
20.1.1String Type Mapping
20.1.2Binary Type Mapping
18.2Oracle Database Class
18.3Oracle Connection and Connection Factory
18.4Oracle Exceptions
20.2Oracle Database Class
20.3Oracle Connection and Connection Factory
20.4Oracle Exceptions
18.5Oracle Limitations + 20.5Oracle Limitations - - - - - - - - + + + + + + + +
18.5.1Identifier Truncation
18.5.2Query Result Caching
18.5.3Foreign Key Constraints
18.5.4Unique Constraint Violations
18.5.5Large FLOAT and NUMBER Types
18.5.6Timezones
18.5.7LONG Types
18.5.8LOB Types and By-Value Accessors/Modifiers
20.5.1Identifier Truncation
20.5.2Query Result Caching
20.5.3Foreign Key Constraints
20.5.4Unique Constraint Violations
20.5.5Large FLOAT and NUMBER Types
20.5.6Timezones
20.5.7LONG Types
20.5.8LOB Types and By-Value Accessors/Modifiers
18.6Oracle Index Definition
20.6Oracle Index Definition
19Microsoft SQL Server Database + 21Microsoft SQL Server Database - - - - + + + - - +
19.1SQL Server Type Mapping + 21.1SQL Server Type Mapping - - - - + + + +
19.1.1String Type Mapping
19.1.2Binary Type and UNIQUEIDENTIFIER Mapping
19.1.3ROWVERSION Mapping
19.1.4Long String and Binary Types
21.1.1String Type Mapping
21.1.2Binary Type and UNIQUEIDENTIFIER Mapping
21.1.3ROWVERSION Mapping
21.1.4Long String and Binary Types
19.2SQL Server Database Class
19.3SQL Server Connection and Connection Factory
19.4SQL Server Exceptions
21.2SQL Server Database Class
21.3SQL Server Connection and Connection Factory
21.4SQL Server Exceptions
19.5SQL Server Limitations + 21.5SQL Server Limitations - - - - - - - + + + + + + +
19.5.1Query Result Caching
19.5.2Foreign Key Constraints
19.5.3Unique Constraint Violations
19.5.4Multi-threaded Windows Applications
19.5.5Affected Row Count and DDL Statements
19.5.6Long Data and Auto Object Ids, ROWVERSION
19.5.7Long Data and By-Value Accessors/Modifiers
21.5.1Query Result Caching
21.5.2Foreign Key Constraints
21.5.3Unique Constraint Violations
21.5.4Multi-threaded Windows Applications
21.5.5Affected Row Count and DDL Statements
21.5.6Long Data and Auto Object Ids, ROWVERSION
21.5.7Long Data and By-Value Accessors/Modifiers
19.6SQL Server Index Definition
21.6SQL Server Index Definition
20Profiles Introduction22Profiles Introduction
21Boost Profile + 23Boost Profile - - - - + + + + - - @@ -808,35 +822,35 @@ for consistency. - - + - + - + - + - + - + - + - + - + - + - + - + + + + + + +
21.1Smart Pointers Library
21.2Unordered Containers Library
21.3Multi-Index Container Library
21.4Optional Library
23.1Smart Pointers Library
23.2Unordered Containers Library
23.3Multi-Index Container Library
23.4Optional Library
21.5Date Time Library + 23.5Date Time Library - - - - - + + + + +
21.5.1MySQL Database Type Mapping
21.5.2SQLite Database Type Mapping
21.5.3PostgreSQL Database Type Mapping
21.5.4Oracle Database Type Mapping
21.5.5SQL Server Database Type Mapping
23.5.1MySQL Database Type Mapping
23.5.2SQLite Database Type Mapping
23.5.3PostgreSQL Database Type Mapping
23.5.4Oracle Database Type Mapping
23.5.5SQL Server Database Type Mapping
21.6Uuid Library + 23.6Uuid Library - - - - - + + + + +
21.6.1MySQL Database Type Mapping
21.6.2SQLite Database Type Mapping
21.6.3PostgreSQL Database Type Mapping
21.6.4Oracle Database Type Mapping
21.6.5SQL Server Database Type Mapping
23.6.1MySQL Database Type Mapping
23.6.2SQLite Database Type Mapping
23.6.3PostgreSQL Database Type Mapping
23.6.4Oracle Database Type Mapping
23.6.5SQL Server Database Type Mapping
22Qt Profile + 24Qt Profile - - + - - @@ -957,10 +971,10 @@ for consistency. - - - - + + + +
22.1Basic Types Library + 24.1Basic Types Library - - - - - + + + + +
22.1.1MySQL Database Type Mapping
22.1.2SQLite Database Type Mapping
22.1.3PostgreSQL Database Type Mapping
22.1.4Oracle Database Type Mapping
22.1.5SQL Server Database Type Mapping
24.1.1MySQL Database Type Mapping
24.1.2SQLite Database Type Mapping
24.1.3PostgreSQL Database Type Mapping
24.1.4Oracle Database Type Mapping
24.1.5SQL Server Database Type Mapping
22.2Smart Pointers Library
24.2Smart Pointers Library
22.3Containers Library + 24.3Containers Library - +
22.3.1Change-Tracking QList
24.3.1Change-Tracking QList
22.4Date Time Library + 24.4Date Time Library - - - - - + + + + +
22.4.1MySQL Database Type Mapping
22.4.2SQLite Database Type Mapping
22.4.3PostgreSQL Database Type Mapping
22.4.4Oracle Database Type Mapping
22.4.5SQL Server Database Type Mapping
24.4.1MySQL Database Type Mapping
24.4.2SQLite Database Type Mapping
24.4.3PostgreSQL Database Type Mapping
24.4.4Oracle Database Type Mapping
24.4.5SQL Server Database Type Mapping
6Relationships
7Value Types
8Inheritance
9Views
10Session
11Optimistic Concurrency
12ODB Pragma Language
10Views
11Session
12Optimistic Concurrency
14ODB Pragma Language
@@ -1333,7 +1347,7 @@ private: placed after the class definition. They could have also been moved into a separate header leaving the original class completely unchanged (for more information on such a non-intrusive conversion - refer to Chapter 12, "ODB Pragma Language").

+ refer to Chapter 14, "ODB Pragma Language").

 class person
@@ -1606,7 +1620,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
-     (Section 15.2, "MySQL Database Class").

+ (Section 17.2, "MySQL Database Class").

Next, we create three person objects. Right now they are transient objects, which means that if we terminate the application @@ -2014,7 +2028,7 @@ Hello, Joe! tables using object relationships or custom join conditions.

While you can find a much more detailed description of views in - Chapter 9, "Views", here is how we can define + Chapter 10, "Views", here is how we can define the person_stat view that returns the basic statistics about the person objects:

@@ -2138,7 +2152,7 @@ odb::mysql::database db2 ("john", "secret", "test_db2"); tight integration with specific database systems to being able to write database-agnostic code and loading individual database systems support dynamically. While all these aspects are covered in detail - in Chapter 14, "Multi-Database Support", in this + in Chapter 16, "Multi-Database Support", in this section we will get a taste of this functionality by extending our "Hello World" example to be able to store its data either in MySQL or PostgreSQL (other database systems supported by ODB can be added @@ -2156,7 +2170,7 @@ odb --multi-database dynamic -d common -d mysql -d pgsql \

The --multi-database ODB compiler option turns on multi-database support. For now it is not important what the dynamic value that we passed to this option means, but - if you are curious, see Chapter 14. The result of this + if you are curious, see Chapter 16. The result of this command are three sets of generated files: person-odb.?xx (common interface; corresponds to the common database), person-odb-mysql.?xx (MySQL support code), and @@ -2384,7 +2398,7 @@ psql --user=odb_test --dbname=odb_test -f person-pgsql.sql computations to the relational database instead of performing them in the application's process. To support such requirements ODB distinguishes a third kind of C++ types, called views - (Chapter 9, "Views"). An ODB view is a C++ + (Chapter 10, "Views"). An ODB view is a C++ class that embodies a light-weight, read-only projection of one or more persistent objects or database tables or the result of a native SQL query execution.

@@ -2497,13 +2511,13 @@ class person 7.2.1, "Composite Object Ids") value type. This type should be default-constructible. It is also possible to declare a persistent class without an object id, however, such a class will have limited - functionality (Section 12.1.6, + functionality (Section 14.1.6, "no_id").

The above two pragmas are the minimum required to declare a persistent class with an object id. Other pragmas can be used to fine-tune the database-related properties of a class and its - members (Chapter 12, "ODB Pragma Language").

+ members (Chapter 14, "ODB Pragma Language").

Normally, a persistent class should define the default constructor. The generated database support code uses this constructor when @@ -2534,7 +2548,7 @@ private: Section 4.4, "Query Result").

The ODB compiler also needs access to the non-transient - (Section 12.4.11, "transient") + (Section 14.4.11, "transient") data members of a persistent class. The ODB compiler can access such data members directly if they are public. It can also do so if they are private or protected and the odb::access @@ -2607,9 +2621,14 @@ private: and modifier expressions using the db get and db set pragmas. For more information on custom accessor and modifier expressions refer to - Section 12.4.5, + Section 14.4.5, "get/set/access".

+

Data members of a persistent class can also be split into + separately-loaded and/or separately-updated sections. + For more information on this functionality, refer to + Chapter 9, "Sections".

+

You may be wondering whether we also have to declare value types as persistent. We don't need to do anything special for simple value types such as int or std::string since the @@ -2619,7 +2638,7 @@ private: mapping to the database type and, possibly, the code to convert between the two. For more information on how to achieve this refer to the db type pragma description - in Section 12.3.1, "type".

+ in Section 14.3.1, "type".

Similar to object classes, composite value types have to be explicitly declared as persistent using the db value @@ -2652,7 +2671,7 @@ class name return pointers to these instances. As we will see in later chapters, pointers are also used to establish relationships between objects (Chapter 6, "Relationships") as well as to cache - persistent objects in a session (Chapter 10, + persistent objects in a session (Chapter 11, "Session"). While in most cases you won't need to deal with pointers to views, it is possible to a obtain a dynamically allocated instance of a view using the result_iterator::load() @@ -2751,9 +2770,9 @@ namespace accounting }

-

Refer to Section 12.1.2, "pointer - (object)", Section 12.2.4, "pointer - (view)", and Section 12.5.1, "pointer +

Refer to Section 14.1.2, "pointer + (object)", Section 14.2.4, "pointer + (view)", and Section 14.5.1, "pointer (namespace)" for more information on these mechanisms.

Built-in support that is provided by the ODB runtime library allows us @@ -2899,7 +2918,7 @@ namespace odb our application. To map persistent classes to custom database schemas, ODB provides a wide range of mapping customization pragmas, such as db table, db column, - and db type (Chapter 12, "ODB Pragma + and db type (Chapter 14, "ODB Pragma Language"). For sample code that shows how to perform such mapping for various C++ constructs, refer to the schema/custom example in the odb-examples package.

@@ -3106,7 +3125,7 @@ t.commit ();

For more information on the transaction callback support, refer - to Section 13.1, "Transaction Callbacks".

+ to Section 15.1, "Transaction Callbacks".

Note that in the above discussion of atomicity, consistency, isolation, and durability, all of those guarantees only apply @@ -3185,7 +3204,7 @@ update_age (database& db, person& p) } -

See also Section 13.1, "Transaction Callbacks" +

See also Section 15.1, "Transaction Callbacks" for an alternative approach.

3.6 Connections

@@ -3403,7 +3422,7 @@ for (unsigned short retry_count (0); ; retry_count++)

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 12.4.2, + application-assigned object ids (Section 14.4.2, "auto").

The second and third persist() functions are similar to the @@ -3518,7 +3537,7 @@ t.commit ();

The first special property of reload() compared to the load() function is that it does not interact with the session's object cache - (Section 10.1, "Object Cache"). That is, if + (Section 11.1, "Object Cache"). That is, if the object being reloaded is already in the cache, then it will remain there after reload() returns. Similarly, if the object is not in the cache, then reload() won't @@ -3529,7 +3548,7 @@ t.commit (); concurrency model. In this case, if the states of the object in the application memory and in the database are the same, then no reloading will occur. For more information on optimistic - concurrency, refer to Chapter 11, "Optimistic + concurrency, refer to Chapter 12, "Optimistic Concurrency".

If we don't know for sure whether an object with a given id @@ -3649,14 +3668,14 @@ t.commit (); there is no such object in the database. Instead, this condition is treated as a change of object state and object_changed is thrown instead. For a more detailed discussion of optimistic - concurrency, refer to Chapter 11, "Optimistic + concurrency, refer to Chapter 12, "Optimistic Concurrency".

In ODB, persistent classes, composite value types, as well as individual - data members can be declared read-only (see Section - 12.1.4, "readonly (object)", Section - 12.3.6, "readonly (composite value)", and - Section 12.4.12, "readonly + data members can be declared read-only (see Section + 14.1.4, "readonly (object)", Section + 14.3.6, "readonly (composite value)", and + Section 14.4.12, "readonly (data member)").

If an individual data member is declared read-only, then @@ -3737,7 +3756,7 @@ t.commit (); object in the database. Instead, this condition is treated as a change of object state and object_changed is thrown instead. For a more detailed discussion of optimistic concurrency, - refer to Chapter 11, "Optimistic Concurrency".

+ refer to Chapter 12, "Optimistic Concurrency".

The erase_query() function allows us to delete the state of multiple objects matching certain criteria. It uses @@ -4195,6 +4214,20 @@ namespace odb virtual const char* what () const throw (); }; + + // Section exceptions. + // + struct section_not_loaded: exception + { + virtual const char* + what () const throw (); + }; + + struct section_not_in_object: exception + { + virtual const char* + what () const throw (); + }; } @@ -4212,7 +4245,7 @@ namespace odb

The next two exceptions (already_in_session, and not_in_session) are thrown by the odb::session - class and are discussed in Chapter 10, "Session".

+ class and are discussed in Chapter 11, "Session".

The session_required exception is thrown when ODB detects that correctly loading a bidirectional object relationship requires a @@ -4249,7 +4282,7 @@ namespace odb by the update() database function and certain erase() database functions when operating on objects with the optimistic concurrency model. See - Chapter 11, "Optimistic Concurrency" for details.

+ Chapter 12, "Optimistic Concurrency" for details.

The result_not_cached exception is thrown by the query result class. Refer to Section 4.4, @@ -4263,7 +4296,7 @@ namespace odb

The abstract_class exception is thrown by the database functions when we attempt to persist, update, load, or erase an instance of a polymorphic abstract class. For more information - on abstract classes, refer to Section 12.1.3, + on abstract classes, refer to Section 14.1.3, "abstract".

The no_type_info exception is thrown by the database @@ -4289,6 +4322,13 @@ namespace odb name is not found. Refer to Section 3.4, "Database" for details.

+

The section_not_loaded exception is thrown if we + attempt to update an object section that hasn't been loaded. + The section_not_in_object exception is thrown if + the section instance being loaded or updated does not belong + to the corresponding object. See Chapter 9, + "Sections" for more information on these exceptions.

+

The odb::exception class is defined in the <odb/exception.hxx> header file. All the concrete ODB exceptions are defined in @@ -4746,7 +4786,10 @@ namespace odb (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.

+ or erase() will also invalidate the uncached result. + It also follows that uncached results cannot be used on objects + with containers (Chapter 5, "Containers") since + loading a container would invalidate the uncached result.

The empty() function returns true if there are no objects in the result and false otherwise. @@ -5470,7 +5513,7 @@ private: column, they can occupy multiple columns. For an ordered container table the ODB compiler also defines two indexes: one for the object id column(s) and the other for the index - column. Refer to Section 12.6, "Index Definition + column. Refer to Section 14.6, "Index Definition Pragmas" for more information on how to customize these indexes.

@@ -5499,7 +5542,7 @@ private:

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

@@ -5525,8 +5568,8 @@ private: 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 - db unordered pragma (Section 12.3.9, - "unordered", Section 12.4.18, + db unordered pragma (Section 14.3.9, + "unordered", Section 14.4.18, "unordered"). For example:

@@ -5565,7 +5608,7 @@ private:
      id or element value are composite, then, instead of a single
      column, they can occupy multiple columns. ODB compiler also
      defines an index on a set container table for the object id
-     column(s). Refer to Section 12.6, "Index Definition
+     column(s). Refer to Section 14.6, "Index Definition
      Pragmas" for more information on how to customize this
      index.

@@ -5593,7 +5636,7 @@ private:

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

@@ -5632,7 +5675,7 @@ private: element value are composite, then instead of a single column they can occupy multiple columns. ODB compiler also defines an index on a map container table for the object id - column(s). Refer to Section 12.6, "Index Definition + column(s). Refer to Section 14.6, "Index Definition Pragmas" for more information on how to customize this index.

@@ -5661,7 +5704,7 @@ private:

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

@@ -5812,6 +5855,10 @@ for (;;) }
+

For the interaction of change-tracking containers with change-updated + object sections, refer to Section 9.4, "Sections and + Change-Tracking Containers".

+

5.4.1 Change-Tracking vector

Class template odb::vector, defined in @@ -5828,8 +5875,8 @@ for (;;)

odb::vector incurs 2-bit per element overhead in order to store the change state. It cannot - be stored unordered in the database (Section - 12.4.18 "unordered") but can be used as an inverse + be stored unordered in the database (Section + 14.4.18 "unordered") but can be used as an inverse side of a relationship (6.2 "Bidirectional Relationships"). In this case, no change tracking is performed since no state for such a container is stored in the database.

@@ -6085,11 +6132,11 @@ class employee

By default, an object pointer can be NULL. To specify that a pointer always points to a valid object we can - use the not_null pragma (Section - 12.4.6, "null/not_null") for + use the not_null pragma (Section + 14.4.6, "null/not_null") for single object pointers and the value_not_null pragma - (Section - 12.4.23, "value_null/value_not_null") + (Section + 14.4.25, "value_null/value_not_null") for containers of object pointers. For example:

@@ -6156,7 +6203,7 @@ unsigned long john_id, jane_id;
 
   

The only notable line in the above code is the creation of a session before the second transaction starts. As discussed in - Chapter 10, "Session", a session acts as a cache + Chapter 11, "Session", 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 @@ -6336,7 +6383,7 @@ CREATE TABLE employee_projects (

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

+ (Chapter 14, "ODB Pragma Language"). For example:

 #pragma db object
@@ -6503,7 +6550,7 @@ CREATE TABLE employee (
      of these references.

To eliminate redundant database schema references we can use the - inverse pragma (Section 12.4.14, + inverse pragma (Section 14.4.14, "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:

@@ -6547,7 +6594,7 @@ CREATE TABLE employee ( 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 12.4.18, "unordered") + (Section 14.4.18, "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).

@@ -7198,6 +7245,10 @@ db.persist (e); t.commit ();
+

For the interaction of lazy pointers with lazy-loaded object + sections, refer to Section 9.3, "Sections and + Lazy Pointers".

+

6.5 Using Custom Smart Pointers

While the ODB runtime and profile libraries provide support for @@ -7279,7 +7330,7 @@ t.commit (); mapping for each database system refer to Part II, Database Systems. We can also provide a custom mapping for these or our own value types using the db type - pragma (Section 12.3.1, "type").

+ pragma (Section 14.3.1, "type").

7.2 Composite Value Types

@@ -7331,7 +7382,7 @@ private:

The ODB compiler also needs access to the non-transient - (Section 12.4.11, "transient") + (Section 14.4.11, "transient") data members of a composite value type. It uses the same mechanisms as for persistent classes which are discussed in Section 3.2, "Declaring Persistent Objects and @@ -7497,15 +7548,15 @@ class person pointer, or read-only data members. It also must be default-constructible. Furthermore, if the persistent class in which this composite value type is used as object id has session support - enabled (Chapter 10, "Session"), then it must also + enabled (Chapter 11, "Session"), then it must also implement the less-than comparison operator (operator<).

7.2.2 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 (Section - 12.4.9, "column"). For composite value + the db column pragma (Section + 14.4.9, "column"). For composite value types things are slightly more complex since they are mapped to multiple columns. Consider the following example:

@@ -7606,9 +7657,9 @@ CREATE TABLE person (

The same principle 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 - (Section 12.4.31, "value_column") or + (Section 14.4.33, "value_column") or db key_column - (Section 12.4.30, "key_column") + (Section 14.4.32, "key_column") pragmas are used to specify the column prefix.

When a composite value type contains a container, an extra table @@ -7652,8 +7703,8 @@ CREATE TABLE person (

To customize the container table name we can use the - db table pragma (Section - 12.4.19, "table"), for example:

+ db table pragma (Section + 14.4.19, "table"), for example:

 #pragma db value
@@ -7694,7 +7745,7 @@ CREATE TABLE person_nickname (
      of a valid value in a column. While by default ODB maps
      values to columns that do not allow NULL values,
      it is possible to change that with the db null
-     pragma (Section 12.4.6,
+     pragma (Section 14.4.6,
      "null/not_null").

To properly support the NULL semantics, the @@ -7819,7 +7870,7 @@ namespace odb consider using a more efficient implementation of the optional value concept such as the optional class template from Boost - (Section 21.4, "Optional Library").

+ (Section 23.4, "Optional Library").

Another common C++ representation of a value that can be NULL is a pointer. ODB will automatically @@ -8020,7 +8071,7 @@ public: instances of a base class from being stored in the database. To achieve this a persistent class can be declared abstract using the db abstract - pragma (Section 12.1.3, "abstract"). + pragma (Section 14.1.3, "abstract"). Note that a C++-abstract class, or a class that has one or more pure virtual functions and therefore cannot be instantiated, is also database-abstract. However, a @@ -8267,7 +8318,7 @@ class contractor: public person

Similarly, if we enable or disable session support - (Chapter 10, "Session") for the root class, then + (Chapter 11, "Session") for the root class, then the ODB compiler will automatically enable or disable it for all the derived classes.

@@ -8500,9 +8551,9 @@ t.commit (); to ordinary objects. These include containers (Chapter 5, "Containers"), object relationships, including to polymorphic objects (Chapter 6, "Relationships"), views - (Chapter 9, "Views"), session (Chapter - 10, "Session"), and optimistic concurrency (Chapter - 11, "Optimistic Concurrency"). There are, however, a few + (Chapter 10, "Views"), session (Chapter + 11, "Session"), and optimistic concurrency (Chapter + 12, "Optimistic Concurrency"). There are, however, a few limitations, mainly due to the underlying use of SQL to access the data.

@@ -8622,11 +8673,713 @@ class permanent_employee: public employee // Polymorphism inheritance. }; + + + + +
+

9 Sections

+ +

ODB sections are an optimization mechanism that allows us to + partition data members of a persistent class into groups that + can be separately loaded and/or updated. This can be useful, + for example, if an object contains expensive to load or update + data members (such as BLOBs or containers) and + that are accessed or modified infrequently. For example:

+ +
+#include <odb/section.hxx>
+
+#pragma db object
+class person
+{
+  ...
+
+  #pragma db load(lazy) update(manual)
+  odb::section keys_;
+
+  #pragma db section(keys_) type("BLOB")
+  char public_key_[1024];
+
+  #pragma db section(keys_) type("BLOB")
+  char private_key_[1024];
+};
+
+transaction t (db.begin ());
+
+auto_ptr<person> p (db.load<person> (...)); // Keys are not loaded.
+
+if (need_keys)
+{
+  db.load (*p, p->keys_); // Load keys.
+  ...
+}
+
+db.update (*p); // Keys are not updated.
+
+if (update_keys)
+{
+  ...
+  db.update (*p, p->keys_); // Update keys.
+}
+
+t.commit ();
+  
+ +

A complete example that shows how to use sections is available in + the section directory in the odb-examples + package.

+ +

Why do we need to group data members into sections? Why can't + each data member be loaded and updated independently if and + when necessary? The reason for this requirement is that loading + or updating a group of data members with a single database + statement is significantly more efficient than loading or updating + each data member with a separate statement. Because ODB + prepares and caches statements used to load and update + persistent objects, generating a custom statement for + a specific set of data members that need to be loaded or + updated together is not a viable approach either. To resolve + this, ODB allows us to group data members that are + often updated and/or loaded together into sections. To + achieve the best performance, we should aim to find a balance + between having too many sections with too few data + members and too few sections with too many data + members. We can use the access and modification patterns + of our application as a base for this decision.

+ +

To add a new section to a persistent class we declare a new + data member of the odb::section type. At this + point we also need to specify the loading and updating behavior + of this section with the db load and + db update pragmas, respectively.

+ +

The loading behavior of a section can be either eager + or lazy. An eager-loaded section is always loaded as + part of the object load. A lazy-loaded section is not loaded + as part of the object load and has to be explicitly loaded with + the database::load() function (discussed below) if + and when necessary.

+ +

The updating behavior of a section can be always, + change, or manual. An always-updated + section is always updated as part of the object update, + provided it has been loaded. A change-updated section + is only updated as part of the object update if it has been loaded + and marked as changed. A manually-updated section is never updated + as part of the object update and has to be explicitly updated with + the database::update() function (discussed below) if + and when necessary.

+ +

If no loading behavior is specified explicitly, then an eager-loaded + section is assumed. Similarly, if no updating behavior is specified, + then an always-updated section is assumed. An eager-loaded, always-updated + section is pointless and therefore illegal. Only persistent classes + with an object id can have sections.

+ +

To specify that a data member belongs to a section we use the + db section pragma with the section's member + name as its single argument. Except for special data members + such as the object id and optimistic concurrency version, any + direct, non-transient member of a persistent class can belong + to a section, including composite values, containers, and + pointers to objects. For example:

+ +
+#pragma db value
+class text
+{
+  std::string data;
+  std::string lang;
+};
+
+#pragma db object
+class person
+{
+  ...
+
+  #pragma db load(lazy)
+  odb::section extras_;
+
+  #pragma db section(extras_)
+  text bio_;
+
+  #pragma db section(extras_)
+  std::vector<std::string> nicknames_;
+
+  #pragma db section(extras_)
+  std::shared_ptr<person> emergency_contact_;
+};
+  
+ +

An empty section is pointless and therefore illegal, except + in abstract or polymorphic classes where data members can be + added to a section by derived classes (see Section + 9.1, "Sections and Inheritance").

+ +

The odb::section class is defined in the + <odb/section.hxx> header file and has the + following interface:

+ +
+namespace odb
+{
+  class section
+  {
+  public:
+    // Load state.
+    //
+    bool
+    loaded () const;
+
+    void
+    unload ();
+
+    // Change state.
+    //
+    bool
+    changed () const;
+
+    void
+    change ();
+
+    // User data.
+    //
+    unsigned char
+    user_data () const;
+
+    void
+    user_data (unsigned char);
+  };
+}
+  
+ +

The loaded() accessor can be used to determine + whether a section is already loaded. The unload() + modifier marks a loaded section as not loaded. This, for example, + can be useful if you don't want the section to be reloaded during + the object reload.

+ +

The changed() accessor can be used to query the + section's change state. The change() modifier + marks the section as changed. It is valid to call this modifier + for an unloaded (or transient) section, however, the state will + be reset back to unchanged once the section (or object) is loaded. + The change state is only relevant to sections with change-updated + behavior and is ignored for all other sections.

+ +

The size of the section class is one byte with four bits available + to store a custom state via the user_data() accessor + and modifier.

+ +

The odb::database class provides special + versions of the load() and update() + functions that allow us to load and update sections of a + persistent class. Their signatures are as follows:

+ +
+  template <typename T>
+  void
+  load (T& object, section&);
+
+  template <typename T>
+  void
+  update (const T& object, const section&);
+  
+ +

Before calling the section load() function, the + object itself must already be loaded. If the section is already + loaded, then the call to load() will reload its + data members. It is illegal to explicitly load an eager-loaded + section.

+ +

Before calling the section update() function, the + section (and therefore the object) must be in the loaded state. + If the section is not loaded, the odb::section_not_loaded + exception is thrown. The section update() function + does not check but does clear the section's change state. In + other words, section update() will always update + section data members in the database and clear the change flag. + Note also that any section, that is, always-, change-, or + manually-updated, can be explicitly updated with this function.

+ +

Both section load() and update(), just + like the rest of the database operations, must be performed within + a transaction. Notice also that both load() and + update() expect a reference to the section as + their second argument. This reference must refer to the data + member in the object passed as the first argument. If instead + it refers to some other instance of the section + class, for example, a local copy or a temporary, then the + odb::section_not_in_object exception is thrown. + For example:

+ +
+#pragma db object
+class person
+{
+public:
+  ...
+
+  odb::section
+  keys () const {return keys_;}
+
+private:
+  odb::section keys_;
+
+  ...
+};
+
+auto_ptr<person> p (db.load<person> (...));
+
+section s (p->keys ());
+db.load (*p, s);            // Throw section_not_in_object, copy.
+
+db.update (*p, p->keys ()); // Throw section_not_in_object, copy.
+  
+ +

At first glance it may seem more appropriate to make the + section class non-copyable in order to prevent + such errors from happening. However, it is perfectly reasonable + to expect to be able to copy (or assign) sections as part of + the object copying (or assignment). As a result, sections are + left copyable and copy-assignable, however, this functionality + should not be used in accessors or modifiers. Instead, section + accessors and modifiers should always be by-reference. Here is + how we can fix our previous example:

+ +
+#pragma db object
+class person
+{
+public:
+  ...
+
+  const odb::section&
+  keys () const {return keys_;}
+
+  odb::section&
+  keys () {return keys_;}
+
+private:
+  odb::section keys_;
+
+  ...
+};
+
+auto_ptr<person> p (db.load<person> (...));
+
+section& s (p->keys ());
+db.load (*p, s);            // Ok, reference.
+
+db.update (*p, p->keys ()); // Ok, reference.
+  
+ +

Several other database operations affect sections. The state of + a section in a transient object is undefined. That is, before + the call to object persist() or load() + functions, or after the call to object erase() + function, the values returned by the section::loaded() and + section::changed() accessors are undefined.

+ +

After the call to persist(), all sections, including + eager-loaded ones, are marked as loaded and unchanged. If instead we + are loading an object with the load() call or as + a result of a query, then eager-loaded sections are loaded + and marked as loaded and unchanged while lazy-loaded ones are marked + as unloaded. If a lazy-loaded section is later loaded with the + section load() call, then it is marked as loaded and + unchanged.

+ +

When we update an object with the update() call, + manually-updated sections are ignored while always-updated + sections are updated if they are loaded. Change-updated + sections are only updated if they are both loaded and marked + as changed. After the update, such sections are reset to the + unchanged state. When we reload an object with the + reload() call, sections that were loaded are + automatically reloaded and reset to the unchanged state.

+ +

To further illustrate the state transitions of a section, + consider this example:

+ +
+#pragma db object
+class person
+{
+  ...
+
+  #pragma db load(lazy) update(change)
+  odb::section keys_;
+
+  ...
+};
+
+transaction t (db.begin ());
+
+person p ("John", "Doe"); // Section state is undefined (transient).
+
+db.persist (p);           // Section state: loaded, unchanged.
+
+auto_ptr<person> l (
+  db.load<person> (...)); // Section state: unloaded, unchanged.
+
+db.update (*l);           // Section not updated since not loaded.
+db.update (p);            // Section not updated since not changed.
+
+p.keys_.change ();        // Section state: loaded, changed.
+db.update (p);            // Section updated, state: loaded, unchanged.
+
+db.update (*l, l->keys_); // Throw section_not_loaded.
+db.update (p, p.keys_);   // Section updated even though not changed.
+
+db.reload (*l);           // Section not reloaded since not loaded.
+db.reload (p);            // Section reloaded, state: loaded, unchanged.
+
+db.load (*l, l->keys_);   // Section loaded, state: loaded, unchanged.
+db.load (p, p.keys_);     // Section reloaded, state: loaded, unchanged.
+
+db.erase (p);             // Section state is undefined (transient).
+
+t.commit ();
+  
+ +

When using change-updated behavior, it is our responsibility to + mark the section as changed when any of the data members belonging + to this section is modified. A natural place to mark the section + as changed is the modifiers for section data members, for example:

+ +
+#pragma db object
+class person
+{
+  ...
+
+  typedef std::array<char, 1024> key_type;
+
+  const key_type&
+  public_key () const {return public_key_;}
+
+  void
+  public_key (const key_type& k)
+  {
+    public_key_ = k;
+    keys_.change ();
+  }
+
+  const key_type&
+  private_key () const {return private_key_;}
+
+  void
+  private_key (const key_type& k)
+  {
+    private_key_ = k;
+    keys_.change ();
+  }
+
+private:
+  #pragma db load(lazy) update(change)
+  odb::section keys_;
+
+  #pragma db section(keys_) type("BLOB")
+  key_type public_key_;
+
+  #pragma db section(keys_) type("BLOB")
+  key_type private_key_;
+
+  ...
+};
+  
+ +

One interesting aspect of change-updated sections is what happens + when a transaction that performed an object or section update is + later rolled back. In this case, while the change state of a + section has been reset (after update), actual changes were not + committed to the database. Change-updated sections handle this + case by automatically registering a rollback callback and then, + if it is called, restoring the original change state. The + following code illustrates this semantics (continuing with + the previous example):

+ +
+auto_ptr<person> p;
+
+try
+{
+  transaction t (db.begin ());
+  p = db.load<person> (...);
+  db.load (*p, p->keys_);
+
+  p->private_key (new_key); // The section is marked changed.
+  db.update (*p);           // The section is reset to unchanged.
+
+  throw failed ();          // Triggers rollback.
+  t.commit ();
+}
+catch (const failed&)
+{
+  // The section is restored back to changed.
+}
+  
+ + +

9.1 Sections and Inheritance

+ +

With both reuse and polymorphism inheritance (Chapter 8, + "Inheritance") it is possible to add new sections to derived + classes. It is also possible to add data members from derived + classes to sections declared in the base. For example:

+ +
+#pragma db object polymorphic
+class person
+{
+  ...
+
+  virtual void
+  print ();
+
+  #pragma db load(lazy)
+  odb::section print_;
+
+  #pragma db section(print_)
+  std::string bio_;
+};
+
+#pragma db object
+class employee: public person
+{
+  ...
+
+  virtual void
+  print ();
+
+  #pragma db section(print_)
+  std::vector<std::string> employment_history_;
+};
+
+transaction t (db.begin ());
+
+auto_ptr<person> p (db.load<person> (...)); // Person or employee.
+db.load (*p, p->print_); // Load data members needed for print.
+p->print ();
+
+t.commit ();
+  
+ +

When data members of a section are spread over several classes in a + reuse inheritance hierarchy, both section load and update are + performed with a single database statement. In contrast, with + polymorphism inheritance, section load is performed with a + single statement while update requires a separate statement + for each class that adds to the section.

+ +

Note also that in polymorphism inheritance the section-to-object + association is static. Or, in other words, you can load a section + via an object only if its static type actually contains this + section. The following example will help illustrate this + point further:

+ +
+#pragma db object polymorphic
+class person
+{
+  ...
+};
+
+#pragma db object
+class employee: public person
+{
+  ...
+
+  #pragma db load(lazy)
+  odb::section extras_;
+
+  ...
+};
+
+#pragma db object
+class manager: public employee
+{
+  ...
+};
+
+auto_ptr<manager> m (db.load<manager> (...));
+
+person& p (*m);
+employee& e (*m);
+section& s (m->extras_);
+
+db.load (p, s); // Error: extras_ is not in person.
+db.load (e, s); // Ok: extras_ is in employee.
+  
+ +

9.2 Sections and Optimistic Concurrency

+ +

When sections are used in a class with the optimistic concurrency + model (Chapter 12, "Optimistic Concurrency"), + both section update and load operations compare the object version + to that in the database and throw the odb::object_changed + exception if they do not match. In addition, the section update + operation increments the version to indicate that the object state + has changed. For example:

+ +
+#pragma db object optimistic
+class person
+{
+  ...
+
+  #pragma db version
+  unsigned long long version_;
+
+  #pragma db load(lazy)
+  odb::section extras_;
+
+  #pragma db section(extras_)
+  std::string bio_;
+};
+
+auto_ptr<person> p;
+
+{
+  transaction t (db.begin ());
+  p = db.load<person> (...);
+  t.commit ();
+}
+
+{
+  transaction t (db.begin ());
+
+  try
+  {
+    db.load (*p, p->extras_); // Throws if object state has changed.
+  }
+  catch (const object_changed&)
+  {
+    db.reload (*p);
+    db.load (*p, p->extras_); // Cannot fail.
+  }
+
+  t.commit ();
+}
+  
+ +

Note also that if an object update triggers one or more + section updates, then each such update will increment the + object version. As a result, an update of an object that + contains sections may result in a version increment by + more than one.

+ +

When sections are used together with optimistic concurrency and + inheritance, an extra step may be required to enable this + functionality. If you plan to add new sections to derived + classes, then the root class of the hierarchy + (the one that declares the version data member) must be + declared as sectionable with the db sectionable + pragma. For example:

+ +
+#pragma db object polymorphic sectionable
+class person
+{
+  ...
+
+  #pragma db version
+  unsigned long long version_;
+};
+
+#pragma db object
+class employee: public person
+{
+  ...
+
+  #pragma db load(lazy)
+  odb::section extras_;
+
+  #pragma db section(extras_)
+  std::vector<std::string> employment_history_;
+};
+  
+ +

This requirement has to do with the need to generate extra + version increment code in the root class that will be used + by sections added in the derived classes. If you forget to + declare the root class as sectionable and later add a + section to one of the derived classes, the ODB compiler + will issue diagnostics.

+ +

9.3 Sections and Lazy Pointers

+ +

If a lazy pointer (Section 6.4, "Lazy Pointers") + belongs to a lazy-loaded section, then we end up with two levels of + lazy loading. Specifically, when the section is loaded, the lazy + pointer is initialized with the object id but the object itself + is not loaded. For example:

+ +
+#pragma db object
+class employee
+{
+  ...
+
+  #pragma db load(lazy)
+  odb::section extras_;
+
+  #pragma db section(extras_)
+  odb::lazy_shared_ptr<employer> employer_;
+};
+
+transaction t (db.begin ());
+
+auto_ptr<employee> e (db.load<employee> (...)); // employer_ is NULL.
+
+db.load (*e, e->extras_); // employer_ contains valid employer id.
+
+e->employer_.load (); // employer_ points to employer object.
+
+t.commit ();
+  
+ +

9.4 Sections and Change-Tracking Containers

+ +

If a change-tracking container (Section 5.4, + "Change-Tracking Containers") belongs to a change-updated + section, then prior to an object update ODB will check if the + container has been changed and if so, automatically mark the + section as changed. For example:

+ +
+#pragma db object
+class person
+{
+  ...
+
+  #pragma db load(lazy) update(change)
+  odb::section extras_;
+
+  #pragma db section(extras_)
+  odb::vector<std::string> nicknames_;
+};
+
+transaction t (db.begin ());
+
+auto_ptr<person> p (db.load<person> (...));
+db.load (*p, p->extras_);
+
+p->nicknames_.push_back ("JD");
+
+db.update (*p); // Section is automatically updated even
+                // though it was not marked as changed.
+t.commit ();
+  
+ +
-

9 Views

+

10 Views

An ODB view is a C++ class or struct type that embodies a light-weight, read-only projection of one or more @@ -8764,11 +9517,11 @@ t.commit (); the result of a custom SQL query. The following sections discuss each of these kinds of view in more detail.

-

9.1 Object Views

+

10.1 Object Views

To associate one or more objects with a view we use the - db object pragma (Section - 12.2.1, "object"). We have already seen + db object pragma (Section + 14.2.1, "object"). We have already seen a simple, single-object view in the introduction to this chapter. To associate the second and subsequent objects we repeat the db object pragma for each additional object, @@ -8799,7 +9552,7 @@ struct employee_employer below. The optional join-condition part provides the criteria which should be used to associate this object with any of the previously associated objects or, as we will see in - Section 9.3, "Mixed Views", tables. Note that + Section 10.3, "Mixed Views", tables. Note that while the first associated object can have an alias, it cannot have a join condition.

@@ -8949,7 +9702,7 @@ struct employee_birth_code or the match is ambiguous, the ODB compiler will issue an error. To associate two differently-named members or to resolve an ambiguity, we can explicitly specify the member association using the - db column pragma (Section 12.4.9, + db column pragma (Section 14.4.9, "column"). For example:

@@ -8965,8 +9718,8 @@ struct employee_employer
   

If an object data member specifies the SQL type with - the db type pragma (Section - 12.4.3, "type"), then this type is also used for + the db type pragma (Section + 14.4.3, "type"), then this type is also used for the associated view data members.

Note also that similar to join conditions, if we assign an alias to @@ -9148,7 +9901,7 @@ t.commit (); query expression, and you need to qualify the column with the table, then you will need to use the table alias instead.

-

9.2 Table Views

+

10.2 Table Views

A table view is similar to an object view except that it is based on one or more database tables instead of persistent @@ -9156,7 +9909,7 @@ t.commit (); ad-hoc tables that are not mapped to persistent classes.

To associate one or more tables with a view we use the - db table pragma (Section 12.2.2, + db table pragma (Section 14.2.2, "table"). To associate the second and subsequent tables we repeat the db table pragma for each additional table. For example, the following view is based on the @@ -9211,7 +9964,7 @@ struct employee_max_vacation

For more information on database schemas and the format of the - qualified names, refer to Section 12.1.8, + qualified names, refer to Section 14.1.8, "schema".

Note also that in the above examples we specified the SQL type @@ -9237,7 +9990,7 @@ struct employee_max_vacation column names, and query expressions. The optional join-condition part provides the criteria which should be used to associate this table with any of the previously associated tables or, as we will see in - Section 9.3, "Mixed Views", objects. Note that + Section 10.3, "Mixed Views", objects. Note that while the first associated table can have an alias, it cannot have a join condition.

@@ -9305,7 +10058,7 @@ t.commit (); -

9.3 Mixed Views

+

10.3 Mixed Views

A mixed view has both associated objects and tables. As a first example of a mixed view, let us improve employee_vacation @@ -9368,12 +10121,12 @@ struct employee_prev_employer }; -

9.4 View Query Conditions

+

10.4 View Query Conditions

Object, table, and mixed views can also specify an optional query condition that should be used whenever the database is queried for this view. To specify a query condition we use the - db query pragma (Section 12.2.3, + db query pragma (Section 14.2.3, "query").

As an example, consider a view that returns some information about @@ -9479,7 +10232,7 @@ struct employer_age }; -

9.5 Native Views

+

10.5 Native Views

The last kind of view supported by ODB is a native view. Native views are a low-level mechanism for capturing results of native @@ -9488,7 +10241,7 @@ struct employer_age the native SQL query, which must at a minimum include the select-list and, if applicable, the from-list. For example, here is how we can re-implement the employee_vacation table - view from Section 9.2 above as a native view:

+ view from Section 10.2 above as a native view:

 #pragma db view query("SELECT employee_id, vacation_days " \
@@ -9606,7 +10359,7 @@ result n (db.query<sequence_value> (
   
-

9.6 Other View Features and Limitations

+

10.6 Other View Features and Limitations

Views cannot be derived from other views. However, you can derive a view from a transient C++ class. View data members cannot be @@ -9674,14 +10427,14 @@ struct employee_name


-

10 Session

+

11 Session

A session is an application's unit of work that may encompass several database transactions. In this version of ODB a session is just an object cache. In future versions it may provide additional functionality, such as delayed database operations and automatic object state change tracking. As discussed later in - Section 10.2, "Custom Sessions", it is also + Section 11.2, "Custom Sessions", it is also possible to provide a custom session implementation that provides these or other features.

@@ -9868,7 +10621,7 @@ namespace odb iterate over the objects store in the cache. Refer to the ODB runtime header files for more details on this direct access.

-

10.1 Object Cache

+

11.1 Object Cache

A session is an object cache. Every time a session-enabled object is made persistent by calling the database::persist() function @@ -9950,7 +10703,7 @@ unsigned long id2 (save (db, p2)); // p2 is cached in s as non-const. } -

10.2 Custom Sessions

+

11.2 Custom Sessions

ODB can use a custom session implementation instead of the default odb::session. There could be multiple @@ -10070,7 +10823,7 @@ public:


-

11 Optimistic Concurrency

+

12 Optimistic Concurrency

The ODB transaction model (Section 3.5, "Transactions") guarantees consistency as long as we perform all the @@ -10154,9 +10907,9 @@ p.age (age); methods, such as timestamps, may be supported in the future.

To declare a persistent class with the optimistic concurrency model we - use the optimistic pragma (Section 12.1.5, + use the optimistic pragma (Section 14.1.5, "optimistic"). We also use the version - pragma (Section 12.4.15, "version") + pragma (Section 14.4.15, "version") to specify which data member will store the object version. For example:

@@ -10365,7 +11118,7 @@ for (bool done (false); !done; )
-

12 ODB Pragma Language

+

14 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. @@ -10534,10 +11287,10 @@ class person the C++ compiler to build the 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 12.8, + at the end of this chapter in Section 14.8, "C++ Compiler Warnings".

-

12.1 Object Type Pragmas

+

14.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, @@ -10554,78 +11307,84 @@ class person

table table name for a persistent class12.1.114.1.1
pointer pointer type for a persistent class12.1.214.1.2
abstract persistent class is abstract12.1.314.1.3
readonly persistent class is read-only12.1.414.1.4
optimistic persistent class with the optimistic concurrency model12.1.514.1.5
no_id persistent class has no object id12.1.614.1.6
callback database operations callback12.1.714.1.7
schema database schema for a persistent class12.1.814.1.8
polymorphic persistent class is polymorphic12.1.914.1.9
session enable/disable session support for a persistent class12.1.1014.1.10
definition definition location for a persistent class12.1.1114.1.11
transient all non-virtual data members in a persistent class are transient12.1.1214.1.12
sectionablesupport addition of new sections in derived classes14.1.13
-

12.1.1 table

+

14.1.1 table

The table specifier specifies the table name that should be used to store objects of the persistent class in a relational @@ -10652,10 +11411,10 @@ class person

For more information on database schemas and the format of the - qualified names, refer to Section 12.1.8, + qualified names, refer to Section 14.1.8, "schema".

-

12.1.2 pointer

+

14.1.2 pointer

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

If a pointer type is not explicitly specified, the default pointer, - specified at the namespace level (Section 12.5.1, + specified at the namespace level (Section 14.5.1, "pointer") or with the --default-pointer ODB compiler option, is used. If neither of these two mechanisms is used to specify the pointer, then the raw pointer is used by default.

@@ -10706,7 +11465,7 @@ class person

For a more detailed discussion of object pointers, refer to Section 3.3, "Object and View Pointers".

-

12.1.3 abstract

+

14.1.3 abstract

The abstract specifier specifies that the persistent class is abstract. An instance of an abstract class cannot be stored in @@ -10738,7 +11497,7 @@ class contractor: public person discussion of persistent class inheritance, refer to Chapter 8, "Inheritance".

-

12.1.4 readonly

+

14.1.4 readonly

The readonly specifier specifies that the persistent class is read-only. The database state of read-only objects cannot be @@ -10763,17 +11522,17 @@ class person read-only while the rest is treated as read-write.

Note that it is also possible to declare individual data members - (Section 12.4.12, "readonly") - as well as composite value types (Section 12.3.6, + (Section 14.4.12, "readonly") + as well as composite value types (Section 14.3.6, "readonly") as read-only.

-

12.1.5 optimistic

+

14.1.5 optimistic

The optimistic specifier specifies that the persistent class has the optimistic concurrency model. A class with the optimistic concurrency model must also specify the data member that is used to store the object version using the version pragma - (Section 12.4.15, "version"). + (Section 14.4.15, "version"). For example:

@@ -10794,9 +11553,9 @@ class person
      same class.

For a more detailed discussion of optimistic concurrency, refer to - Chapter 11, "Optimistic Concurrency".

+ Chapter 12, "Optimistic Concurrency".

-

12.1.6 no_id

+

14.1.6 no_id

The no_id specifier specifies that the persistent class has no object id. For example:

@@ -10828,13 +11587,13 @@ class person

Furthermore, persistent classes without object ids cannot have container data members nor can they be used in object relationships. Such objects are not entered into the session object cache - (Section 10.1, "Object Cache") either.

+ (Section 11.1, "Object Cache") either.

To declare a persistent class with an object id, use the data member - id specifier (Section 12.4.1, + id specifier (Section 14.4.1, "id").

-

12.1.7 callback

+

14.1.7 callback

The callback specifier specifies the persist class member function that should be called before and after a @@ -10958,7 +11717,7 @@ private: };

-

12.1.8 schema

+

14.1.8 schema

The schema specifier specifies a database schema that should be used for the persistent class.

@@ -11147,8 +11906,8 @@ class employee

The standard syntax for qualified names used in the schema and table specifiers as well - as the view column specifier (Section - 12.4.10, "column (view)") has the + as the view column specifier (Section + 14.4.10, "column (view)") has the "name.name..." form where, as discussed above, the leading name component can be empty to denote a fully qualified name. This form, however, @@ -11176,17 +11935,17 @@ class employee }; -

Table prefixes (Section 12.5.2, "table") +

Table prefixes (Section 14.5.2, "table") can be used as an alternative to database schemas if the target database system does not support schemas.

-

12.1.9 polymorphic

+

14.1.9 polymorphic

The polymorphic specifier specifies that the persistent class is polymorphic. For more information on polymorphism support, refer to Chapter 8, "Inheritance".

-

12.1.10 session

+

14.1.10 session

The session specifier specifies whether to enable session support for the persistent class. For example:

@@ -11214,11 +11973,11 @@ class employer

Session support is disabled by default unless the --generate-session ODB compiler option is specified or session support is enabled at the namespace level - (Section 12.5.4, "session"). - For more information on sessions, refer to Chapter - 10, "Session".

+ (Section 14.5.4, "session"). + For more information on sessions, refer to Chapter + 11, "Session".

-

12.1.11 definition

+

14.1.11 definition

The definition specifier specifies an alternative definition location for the persistent class. By @@ -11230,18 +11989,27 @@ class employer containing this pragma.

For more information on this functionality, refer to - Section 12.3.7, "definition".

+ Section 14.3.7, "definition".

-

12.1.12 transient

+

14.1.12 transient

The transient specifier instructs the ODB compiler to treat all non-virtual data members in the persistent class as transient - (Section 12.4.1, "transient"). + (Section 14.4.1, "transient"). This specifier is primarily useful when declaring virtual data - members, as discussed in Section 12.4.13, + members, as discussed in Section 14.4.13, "virtual".

-

12.2 View Type Pragmas

+

14.1.13 sectionable

+ +

The sectionable specifier instructs the ODB compiler + to generate support for the addition of new object sections in + derived classes in a hierarchy with the optimistic concurrency + model. For more information on this functionality, refer to + Section 9.2, "Sections and Optimistic + Concurrency".

+ +

14.2 View Type Pragmas

A pragma with the view qualifier declares a C++ class as a view type. The qualifier can be optionally followed, @@ -11259,84 +12027,84 @@ class employer

object object associated with a view12.2.114.2.1
table table associated with a view12.2.214.2.2
query view query condition12.2.314.2.3
pointer pointer type for a view12.2.414.2.4
callback database operations callback12.2.514.2.5
definition definition location for a view12.2.614.2.6
transient all non-virtual data members in a view are transient12.2.714.2.7
-

For more information on view types refer to Chapter 9, +

For more information on view types refer to Chapter 10, "Views".

-

12.2.1 object

+

14.2.1 object

The object specifier specifies a persistent class that should be associated with the view. For more information - on object associations refer to Section 9.1, "Object + on object associations refer to Section 10.1, "Object Views".

-

12.2.2 table

+

14.2.2 table

The table specifier specifies a database table that should be associated with the view. For more information - on table associations refer to Section 9.2, "Table + on table associations refer to Section 10.2, "Table Views".

-

12.2.3 query

+

14.2.3 query

The query specifier specifies a query condition for an object or table view or a native SQL query for a native view. An empty query specifier indicates that a native SQL query is provided at runtime. For more information - on query conditions refer to Section 9.4, "View + on query conditions refer to Section 10.4, "View Query Conditions". For more information on native SQL queries, - refer to Section 9.5, "Native Views".

+ refer to Section 10.5, "Native Views".

-

12.2.4 pointer

+

14.2.4 pointer

The pointer specifier specifies the view pointer type for the view class. Similar to objects, the view pointer type is used to return dynamically allocated instances of a view class. The semantics of the pointer specifier for a view are the same as those of the pointer specifier for an object - (Section 12.1.2, "pointer").

+ (Section 14.1.2, "pointer").

-

12.2.5 callback

+

14.2.5 callback

The callback specifier specifies the view class member function that should be called before and after an @@ -11344,12 +12112,12 @@ class employer result iteration. The semantics of the callback specifier for a view are similar to those of the callback specifier for an object - (Section 12.1.7, "callback") + (Section 14.1.7, "callback") except that the only events that can trigger a callback call in the case of a view are pre_load and post_load.

-

12.2.6 definition

+

14.2.6 definition

The definition specifier specifies an alternative definition location for the view class. By @@ -11361,18 +12129,18 @@ class employer containing this pragma.

For more information on this functionality, refer to - Section 12.3.7, "definition".

+ Section 14.3.7, "definition".

-

12.2.7 transient

+

14.2.7 transient

The transient specifier instructs the ODB compiler to treat all non-virtual data members in the view class as transient - (Section 12.4.1, "transient"). + (Section 14.4.1, "transient"). This specifier is primarily useful when declaring virtual data - members, as discussed in Section 12.4.13, + members, as discussed in Section 14.4.13, "virtual".

-

12.3 Value Type Pragmas

+

14.3 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 @@ -11389,133 +12157,133 @@ class employer

type database type for a value type12.3.114.3.1
id_type database type for a value type when used as an object id12.3.214.3.2
null/not_null type can/cannot be NULL12.3.314.3.3
default default value for a value type12.3.414.3.4
options database options for a value type12.3.514.3.5
readonly composite value type is read-only12.3.614.3.6
definition definition location for a composite value type12.3.714.3.7
transient all non-virtual data members in a composite value are transient12.3.814.3.8
unordered ordered container should be stored unordered12.3.914.3.9
index_type database type for a container's index type12.3.1014.3.10
key_type database type for a container's key type12.3.1114.3.11
value_type database type for a container's value type12.3.1214.3.12
value_null/value_not_null container's value can/cannot be NULL12.3.1314.3.13
id_options database options for a container's id column12.3.1414.3.14
index_options database options for a container's index column12.3.1514.3.15
key_options database options for a container's key column12.3.1614.3.16
value_options database options for a container's value column12.3.1714.3.17
id_column column name for a container's object id12.3.1814.3.18
index_column column name for a container's index12.3.1914.3.19
key_column column name for a container's key12.3.2014.3.20
value_column column name for a container's value12.3.2114.3.21

Many of the value type specifiers have corresponding member type - specifiers with the same names (Section 12.4, + specifiers with the same names (Section 14.4, "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 the @@ -11525,7 +12293,7 @@ class employer take precedence over and override parameters specified with value specifiers.

-

12.3.1 type

+

14.3.1 type

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

@@ -11547,8 +12315,8 @@ class person std::string and the database types for each supported database system. For more information on the default mapping, refer to Part II, "Database Systems". The - null and not_null (Section - 12.3.3, "null/not_null") specifiers + null and not_null (Section + 14.3.3, "null/not_null") specifiers can be used to control the NULL semantics of a type.

In the above example we changed the mapping for the bool @@ -11575,13 +12343,13 @@ class person mapping example in the odb-examples package shows how to do this for all the supported database systems.

-

12.3.2 id_type

+

14.3.2 id_type

The id_type specifier specifies the native database type that should be used for data members of this type that are designated as - object identifiers (Section 12.4.1, + object identifiers (Section 14.4.1, "id"). In combination with the type - specifier (Section 12.3.1, "type") + specifier (Section 14.3.1, "type") id_type allows you to map a C++ type differently depending on whether it is used in an ordinary member or an object id. For example:

@@ -11616,7 +12384,7 @@ class person }; -

12.3.3 null/not_null

+

14.3.3 null/not_null

The null and not_null specifiers specify that a value type or object pointer can or cannot be NULL, @@ -11645,7 +12413,7 @@ typedef shared_ptr<person> person_ptr;

The NULL semantics can also be specified on the - per-member basis (Section 12.4.6, + per-member basis (Section 14.4.6, "null/not_null"). If both a type and a member have null/not_null specifiers, then the member specifier takes precedence. If a member specifier @@ -11679,7 +12447,7 @@ typedef shared_ptr<person> person_ptr; discussion of the NULL semantics for object pointers, refer to Chapter 6, "Relationships".

-

12.3.4 default

+

14.3.4 default

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

@@ -11698,10 +12466,10 @@ class person

The semantics of the default specifier for a value type are similar to those of the default specifier for a - data member (Section 12.4.7, + data member (Section 14.4.7, "default").

-

12.3.5 options

+

14.3.5 options

The options specifier specifies additional column definition options that should be used for data members of this @@ -11721,10 +12489,10 @@ class person

The semantics of the options specifier for a value type are similar to those of the options specifier for a - data member (Section 12.4.8, + data member (Section 14.4.8, "options").

-

12.3.6 readonly

+

14.3.6 readonly

The readonly specifier specifies that the composite value type is read-only. Changes to data members of a read-only @@ -11750,11 +12518,11 @@ class person_name read-only while the rest is treated as read-write.

Note that it is also possible to declare individual data members - (Section 12.4.12, "readonly") - as well as whole objects (Section 12.1.4, + (Section 14.4.12, "readonly") + as well as whole objects (Section 14.1.4, "readonly") as read-only.

-

12.3.7 definition

+

14.3.7 definition

The definition specifier specifies an alternative definition location for the composite value type. By @@ -11819,16 +12587,16 @@ class object }; -

12.3.8 transient

+

14.3.8 transient

The transient specifier instructs the ODB compiler to treat all non-virtual data members in the composite value type - as transient (Section 12.4.1, + as transient (Section 14.4.1, "transient"). This specifier is primarily useful when declaring virtual data members, as discussed in - Section 12.4.13, "virtual".

+ Section 14.4.13, "virtual".

-

12.3.9 unordered

+

14.3.9 unordered

The unordered specifier specifies that the ordered container should be stored unordered in the database. The database @@ -11845,13 +12613,13 @@ typedef std::vector<std::string> names; storage in the database, refer to Section 5.1, "Ordered Containers".

-

12.3.10 index_type

+

14.3.10 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 those of the type specifier - (Section 12.3.1, "type"). The native + (Section 14.3.1, "type"). The native database type is expected to be an integer type. For example:

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

12.3.11 key_type

+

14.3.11 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 those of the type specifier - (Section 12.3.1, "type"). For + (Section 14.3.1, "type"). For example:

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

12.3.12 value_type

+

14.3.12 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 those of the type specifier - (Section 12.3.1, "type"). For + (Section 14.3.1, "type"). For example:

@@ -11888,18 +12656,18 @@ typedef std::vector<std::string> names;
   

The value_null and value_not_null - (Section 12.3.13, + (Section 14.3.13, "value_null/value_not_null") specifiers can be used to control the NULL semantics of a value column.

-

12.3.13 value_null/value_not_null

+

14.3.13 value_null/value_not_null

The value_null and value_not_null specifiers specify that the container type's element value can or cannot be NULL, respectively. The semantics of value_null and value_not_null are similar to those of the null and not_null specifiers - (Section 12.3.3, "null/not_null"). + (Section 14.3.3, "null/not_null"). For example:

@@ -11920,7 +12688,7 @@ typedef std::vector<shared_ptr<account> > accounts;
      as not allowing a NULL value.

-

12.3.14 id_options

+

14.3.14 id_options

The id_options specifier specifies additional column definition options that should be used for the container's @@ -11933,11 +12701,11 @@ typedef std::vector<std::string> nicknames;

The semantics of the id_options specifier for a container type are similar to those of the id_options specifier for - a container data member (Section 12.4.24, + a container data member (Section 14.4.26, "id_options").

-

12.3.15 index_options

+

14.3.15 index_options

The index_options specifier specifies additional column definition options that should be used for the container's @@ -11950,11 +12718,11 @@ typedef std::vector<std::string> nicknames;

The semantics of the index_options specifier for a container type are similar to those of the index_options specifier for - a container data member (Section 12.4.25, + a container data member (Section 14.4.27, "index_options").

-

12.3.16 key_options

+

14.3.16 key_options

The key_options specifier specifies additional column definition options that should be used for the container's @@ -11967,11 +12735,11 @@ typedef std::map<std::string, std::string> properties;

The semantics of the key_options specifier for a container type are similar to those of the key_options specifier for - a container data member (Section 12.4.26, + a container data member (Section 14.4.28, "key_options").

-

12.3.17 value_options

+

14.3.17 value_options

The value_options specifier specifies additional column definition options that should be used for the container's @@ -11984,11 +12752,11 @@ typedef std::set<std::string> nicknames;

The semantics of the value_options specifier for a container type are similar to those of the value_options specifier for - a container data member (Section 12.4.27, + a container data member (Section 14.4.29, "value_options").

-

12.3.18 id_column

+

14.3.18 id_column

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

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

-

12.3.19 index_column

+

14.3.19 index_column

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

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

-

12.3.20 key_column

+

14.3.20 key_column

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

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

-

12.3.21 value_column

+

14.3.21 value_column

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

12.4 Data Member Pragmas

+

14.4 Data Member Pragmas

A pragma with the member qualifier or a positioned pragma without a qualifier describes a data member. It can @@ -12065,193 +12833,205 @@ typedef std::map<unsigned short, float> age_weight_map; id member is an object id - 12.4.1 + 14.4.1 auto id is assigned by the database - 12.4.2 + 14.4.2 type database type for a member - 12.4.3 + 14.4.3 id_type database type for a member when used as an object id - 12.4.4 + 14.4.4 get/set/access member accessor/modifier expressions - 12.4.5 + 14.4.5 null/not_null member can/cannot be NULL - 12.4.6 + 14.4.6 default default value for a member - 12.4.7 + 14.4.7 options database options for a member - 12.4.8 + 14.4.8 column column name for a member of an object or composite value - 12.4.9 + 14.4.9 column column name for a member of a view - 12.4.10 + 14.4.10 transient member is not stored in the database - 12.4.11 + 14.4.11 readonly member is read-only - 12.4.12 + 14.4.12 virtual declare a virtual data member - 12.4.13 + 14.4.13 inverse member is an inverse side of a bidirectional relationship - 12.4.14 + 14.4.14 version member stores object version - 12.4.15 + 14.4.15 index define database index for a member - 12.4.16 + 14.4.16 unique define unique database index for a member - 12.4.17 + 14.4.17 unordered ordered container should be stored unordered - 12.4.18 + 14.4.18 table table name for a container - 12.4.19 + 14.4.19 + + + + load/update + loading/updating behavior for a section + 14.4.20 + + + + section + member belongs to a section + 14.4.21 index_type database type for a container's index type - 12.4.20 + 14.4.22 key_type database type for a container's key type - 12.4.21 + 14.4.23 value_type database type for a container's value type - 12.4.22 + 14.4.24 value_null/value_not_null container's value can/cannot be NULL - 12.4.23 + 14.4.25 id_options database options for a container's id column - 12.4.24 + 14.4.26 index_options database options for a container's index column - 12.4.25 + 14.4.27 key_options database options for a container's key column - 12.4.26 + 14.4.28 value_options database options for a container's value column - 12.4.27 + 14.4.29 id_column column name for a container's object id - 12.4.28 + 14.4.30 index_column column name for a container's index - 12.4.29 + 14.4.31 key_column column name for a container's key - 12.4.30 + 14.4.32 value_column column name for a container's value - 12.4.31 + 14.4.33

Many of the member specifiers have corresponding value type - specifiers with the same names (Section 12.3, + specifiers with the same names (Section 14.3, "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 the @@ -12261,7 +13041,7 @@ typedef std::map<unsigned short, float> age_weight_map; take precedence over and override parameters specified with value specifiers.

-

12.4.1 id

+

14.4.1 id

The id specifier specifies that the data member contains the object id. In a relational database, an identifier member is @@ -12281,12 +13061,12 @@ class person

Normally, every persistent class has a data member designated as an object's identifier. However, it is possible to declare a persistent class without an id using the object no_id - specifier (Section 12.1.6, "no_id").

+ specifier (Section 14.1.6, "no_id").

Note also that the id specifier cannot be used for data members of composite value types or views.

-

12.4.2 auto

+

14.4.2 auto

The auto specifier specifies that the object's identifier is automatically assigned by the database. Only a member that was @@ -12316,7 +13096,7 @@ class person

Note also that the auto specifier cannot be specified for data members of composite value types or views.

-

12.4.3 type

+

14.4.3 type

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

@@ -12332,14 +13112,14 @@ class person };
-

The null and not_null (Section - 12.4.6, "null/not_null") specifiers +

The null and not_null (Section + 14.4.6, "null/not_null") specifiers can be used to control the NULL semantics of a data member. It is also possible to specify the database type on the per-type instead of the per-member basis using the value type - specifier (Section 12.3.1, "type").

+ specifier (Section 14.3.1, "type").

-

12.4.4 id_type

+

14.4.4 id_type

The id_type specifier specifies the native database type that should be used for the data member when it is part of an @@ -12372,7 +13152,7 @@ class person }; -

12.4.5 get/set/access

+

14.4.5 get/set/access

The get and set specifiers specify the data member accessor and modifier expressions, respectively. If @@ -12561,7 +13341,7 @@ class person types. They can be used for data members in persistent classes, composite value types, and views. There is also a mechanism related to accessors and modifiers called virtual data members - and which is discussed in Section 12.4.13, + and which is discussed in Section 14.4.13, "virtual".

There are, however, certain limitations when it comes to using @@ -12604,7 +13384,7 @@ private: limitations, refer to the "Limitations" sections in Part II, "Database Systems".

-

12.4.6 null/not_null

+

14.4.6 null/not_null

The null and not_null specifiers specify that the data member can or cannot be NULL, respectively. @@ -12616,8 +13396,8 @@ private: not allow NULL values, depending on the semantics of each value type. Consult the relevant documentation to find out more about the NULL semantics for such value - types. A data member containing the object id (Section - 12.4.1, "id") is automatically treated as not + types. A data member containing the object id (Section + 14.4.1, "id") is automatically treated as not allowing a NULL value. Data members that allow NULL values are mapped in a relational database to columns that allow NULL values. For example:

@@ -12645,7 +13425,7 @@ class account

The NULL semantics can also be specified on the - per-type basis (Section 12.3.3, + per-type basis (Section 14.3.3, "null/not_null"). If both a type and a member have null/not_null specifiers, then the member specifier takes precedence. If a member specifier @@ -12659,7 +13439,7 @@ class account discussion of the NULL semantics for object pointers, refer to Chapter 6, "Relationships".

-

12.4.7 default

+

14.4.7 default

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

@@ -12680,8 +13460,8 @@ class person an integer literal, a floating point literal, a string literal, or an enumerator name. If you need to specify a default value that is an expression, for example an SQL function call, then you can use - the options specifier (Section - 12.4.8, "options") instead. For example:

+ the options specifier (Section + 14.4.8, "options") instead. For example:

 enum gender {male, female, undisclosed};
@@ -12734,7 +13514,7 @@ class person
   

A default value can also be specified on the per-type basis - (Section 12.3.4, "default"). + (Section 14.3.4, "default"). An empty default specifier can be used to reset a default value that was previously specified on the per-type basis. For example:

@@ -12752,8 +13532,8 @@ class person }; -

A data member containing the object id (Section - 12.4.1, "id" ) is automatically treated as not +

A data member containing the object id (Section + 14.4.1, "id" ) is automatically treated as not having a default value even if its type specifies a default value.

Note also that default values do not affect the generated C++ code @@ -12767,7 +13547,7 @@ class person

Additionally, the default specifier cannot be specified for view data members.

-

12.4.8 options

+

14.4.8 options

The options specifier specifies additional column definition options that should be used for the data member. For @@ -12785,7 +13565,7 @@ class person

Options can also be specified on the per-type basis - (Section 12.3.5, "options"). + (Section 14.3.5, "options"). By default, options are accumulating. That is, the ODB compiler first adds all the options specified for a value type followed by all the options specified for a data member. To clear the @@ -12814,10 +13594,10 @@ class person

ODB provides dedicated specifiers for specifying column types - (Section 12.4.3, "type"), - NULL constraints (Section 12.4.6, + (Section 14.4.3, "type"), + NULL constraints (Section 14.4.6, "null/not_null"), and default - values (Section 12.4.7, "default"). + values (Section 14.4.7, "default"). For ODB to function correctly these specifiers should always be used instead of the opaque options specifier for these components of a column definition.

@@ -12825,7 +13605,7 @@ class person

Note also that the options specifier cannot be specified for view data members.

-

12.4.9 column (object, composite value)

+

14.4.9 column (object, composite value)

The column specifier specifies the column name that should be used to store the data member of a persistent class @@ -12851,15 +13631,15 @@ class person the common data member name decorations, such as leading and trailing underscores, the m_ prefix, etc.

-

12.4.10 column (view)

+

14.4.10 column (view)

The column specifier can be used to specify the associated object data member, the potentially qualified column name, or the column expression for the data member of a view class. For more information, - refer to Section 9.1, "Object Views" and - Section 9.2, "Table Views".

+ refer to Section 10.1, "Object Views" and + Section 10.2, "Table Views".

-

12.4.11 transient

+

14.4.11 transient

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

@@ -12881,7 +13661,7 @@ class person references that are only meaningful in the application's memory, as well as utility members such as mutexes, etc.

-

12.4.12 readonly

+

14.4.12 readonly

The readonly specifier specifies that the data member of an object or composite value type is read-only. Changes to a read-only @@ -12889,8 +13669,8 @@ class person (Section 3.10, "Updating Persistent Objects") containing such a member. Since views are read-only, it is not necessary to use this specifier for view data members. Object id - (Section 12.4.1, "id") - and inverse (Section 12.4.14, + (Section 14.4.1, "id") + and inverse (Section 14.4.14, "inverse") data members are automatically treated as read-only and must not be explicitly declared as such. For example:

@@ -12970,11 +13750,11 @@ class person as read-only.

Note that it is also possible to declare composite value types - (Section 12.3.6, "readonly") - as well as whole objects (Section 12.1.4, + (Section 14.3.6, "readonly") + as well as whole objects (Section 14.1.4, "readonly") as read-only.

-

12.4.13 virtual

+

14.4.13 virtual

The virtual specifier is used to declare a virtual data member in an object, view, or composite value type. A virtual @@ -12992,7 +13772,7 @@ class person specifier. Finally, the virtual data member declaration must also specify the accessor and modifier expressions, unless suitable accessor and modifier functions can automatically be - found by the ODB compiler (Section 12.4.5, + found by the ODB compiler (Section 14.4.5, "get/set/access"). For example:

@@ -13144,11 +13924,11 @@ private: data as transient. If all the real data members in a class are treated as transient, then we can use the class-level transient specifier - (Section 12.1.12, "transient + (Section 14.1.12, "transient (object)", - Section 12.3.8, "transient + Section 14.3.8, "transient (composite value)", - Section 12.2.7, "transient + Section 14.2.7, "transient (view)") instead of doing it for each individual member. For example:

@@ -13207,7 +13987,7 @@ private: container, or object pointer types. They can be used in persistent classes, composite value types, and views.

-

12.4.14 inverse

+

14.4.14 inverse

The inverse specifier specifies that the data member of an object pointer or a container of object pointers type is an @@ -13245,18 +14025,18 @@ class person relationship 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 - (Section 12.4.18, "unordered").

+ (Section 14.4.18, "unordered").

For a more detailed discussion of inverse members, refer to Section 6.2, "Bidirectional Relationships".

-

12.4.15 version

+

14.4.15 version

The version specifier specifies that the data member stores the object version used to support optimistic concurrency. If a class has a version data member, then it must also be declared as having the optimistic concurrency model using the optimistic pragma - (Section 12.1.5, "optimistic"). For + (Section 14.1.5, "optimistic"). For example:

@@ -13278,9 +14058,9 @@ class person
      choice.

For a more detailed discussion of optimistic concurrency, refer to - Chapter 11, "Optimistic Concurrency".

+ Chapter 12, "Optimistic Concurrency".

-

12.4.16 index

+

14.4.16 index

The index specifier instructs the ODB compiler to define a database index for the data member. For example:

@@ -13297,9 +14077,9 @@ class person

For more information on defining database indexes, refer to - Section 12.6, "Index Definition Pragmas".

+ Section 14.6, "Index Definition Pragmas".

-

12.4.17 unique

+

14.4.17 unique

The index specifier instructs the ODB compiler to define a unique database index for the data member. For example:

@@ -13316,9 +14096,9 @@ class person

For more information on defining database indexes, refer to - Section 12.6, "Index Definition Pragmas".

+ Section 14.6, "Index Definition Pragmas".

-

12.4.18 unordered

+

14.4.18 unordered

The unordered specifier specifies that the member of an ordered container type should be stored unordered in the database. @@ -13341,7 +14121,7 @@ class person storage in the database, refer to Section 5.1, "Ordered Containers".

-

12.4.19 table

+

14.4.19 table

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

@@ -13386,16 +14166,35 @@ class person

For more information on database schemas and the format of the - qualified names, refer to Section 12.1.8, + qualified names, refer to Section 14.1.8, "schema".

-

12.4.20 index_type

+

14.4.20 load/update

+ +

The load and update specifiers specify the + loading and updating behavior for an object section, respectively. + Valid values for the load specifier are + eager (default) and lazy. Valid values for + the update specifier are always (default), + change, and manual. For more information + on object sections, refer to Chapter 9, "Sections".

+ +

14.4.21 section

+ +

The section specifier indicates that a data member + of a persistent class belongs to an object section. The single + required argument to this specifier is the name of the section + data member. This specifier can only be used on direct data + members of a persistent class. For more information on object + sections, refer to Chapter 9, "Sections".

+ +

14.4.22 index_type

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

@@ -13409,13 +14208,13 @@ class person
 };
   
-

12.4.21 key_type

+

14.4.23 key_type

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

@@ -13429,13 +14228,13 @@ class person
 };
   
-

12.4.22 value_type

+

14.4.24 value_type

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

@@ -13450,18 +14249,18 @@ class person
   

The value_null and value_not_null - (Section 12.4.23, + (Section 14.4.25, "value_null/value_not_null") specifiers can be used to control the NULL semantics of a value column.

-

12.4.23 value_null/value_not_null

+

14.4.25 value_null/value_not_null

The value_null and value_not_null specifiers specify that a container's element value for the data member can or cannot be NULL, respectively. The semantics of value_null and value_not_null are similar to those of the null and not_null specifiers - (Section 12.4.6, "null/not_null"). + (Section 14.4.6, "null/not_null"). For example:

@@ -13487,7 +14286,7 @@ class account
      Multiset Containers") the element value is automatically treated
      as not allowing a NULL value.

-

12.4.24 id_options

+

14.4.26 id_options

The id_options specifier specifies additional column definition options that should be used for a container's @@ -13508,10 +14307,10 @@ class person

The semantics of id_options are similar to those - of the options specifier (Section - 12.4.8, "options").

+ of the options specifier (Section + 14.4.8, "options").

-

12.4.25 index_options

+

14.4.27 index_options

The index_options specifier specifies additional column definition options that should be used for a container's @@ -13529,10 +14328,10 @@ class person

The semantics of index_options are similar to those - of the options specifier (Section - 12.4.8, "options").

+ of the options specifier (Section + 14.4.8, "options").

-

12.4.26 key_options

+

14.4.28 key_options

The key_options specifier specifies additional column definition options that should be used for a container's @@ -13550,10 +14349,10 @@ class person

The semantics of key_options are similar to those - of the options specifier (Section - 12.4.8, "options").

+ of the options specifier (Section + 14.4.8, "options").

-

12.4.27 value_options

+

14.4.29 value_options

The value_options specifier specifies additional column definition options that should be used for a container's @@ -13571,17 +14370,17 @@ class person

The semantics of value_options are similar to those - of the options specifier (Section - 12.4.8, "options").

+ of the options specifier (Section + 14.4.8, "options").

-

12.4.28 id_column

+

14.4.30 id_column

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

@@ -13598,14 +14397,14 @@ class person
   

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

-

12.4.29 index_column

+

14.4.31 index_column

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

@@ -13622,14 +14421,14 @@ class person
   

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

-

12.4.30 key_column

+

14.4.32 key_column

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

@@ -13646,14 +14445,14 @@ class person
   

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

-

12.4.31 value_column

+

14.4.33 value_column

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

@@ -13670,7 +14469,7 @@ class person
   

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

-

12.5 Namespace Pragmas

+

14.5 Namespace Pragmas

A pragma with the namespace qualifier describes a C++ namespace. Similar to other qualifiers, namespace @@ -13707,30 +14506,30 @@ namespace test pointer pointer type for persistent classes and views inside a namespace - 12.5.1 + 14.5.1 table table name prefix for persistent classes inside a namespace - 12.5.2 + 14.5.2 schema database schema for persistent classes inside a namespace - 12.5.3 + 14.5.3 session enable/disable session support for persistent classes inside a namespace - 12.5.4 + 14.5.4 -

12.5.1 pointer

+

14.5.1 pointer

The pointer specifier specifies the default pointer type for persistent classes and views inside the namespace. For @@ -13786,7 +14585,7 @@ namespace accounting

For a more detailed discussion of object and view pointers, refer to Section 3.3, "Object and View Pointers".

-

12.5.2 table

+

14.5.2 table

The table specifier specifies a table prefix that should be added to table names of persistent classes inside @@ -13845,17 +14644,17 @@ class employer test_audit_employers.

Table prefixes can be used as an alternative to database schemas - (Section 12.1.8, "schema") if + (Section 14.1.8, "schema") if the target database system does not support schemas.

-

12.5.3 schema

+

14.5.3 schema

The schema specifier specifies a database schema that should be used for persistent classes inside the namespace. For more information on specifying a database schema refer to - Section 12.1.8, "schema".

+ Section 14.1.8, "schema".

-

12.5.4 session

+

14.5.4 session

The session specifier specifies whether to enable session support for persistent classes inside the namespace. For @@ -13882,11 +14681,11 @@ namespace hr

Session support is disabled by default unless the --generate-session ODB compiler option is specified. Session support specified at the namespace level can be overridden - on the per object basis (Section 12.1.10, + on the per object basis (Section 14.1.10, "session"). For more information on sessions, - refer to Chapter 10, "Session".

+ refer to Chapter 11, "Session".

-

12.6 Index Definition Pragmas

+

14.6 Index Definition Pragmas

While it is possible to manually add indexes to the generated database schema, it is more convenient to do this as part of @@ -13965,9 +14764,9 @@ class object

ODB also offers a shortcut for defining an index with the default method and options for a single data member. Such an index can - be defined using the index (Section - 12.4.16, "index") or unique - (Section 12.4.17, "unique") + be defined using the index (Section + 14.4.16, "index") or unique + (Section 14.4.17, "unique") member specifier. For example:

@@ -14086,7 +14885,7 @@ class object
 };
 
-

12.7 Database Type Mapping Pragmas

+

14.7 Database Type Mapping Pragmas

A pragma with the map qualifier describes a mapping between two database types. For each database system @@ -14287,7 +15086,7 @@ class object for each database, shows how to provide custom mapping for some of the extended types.

-

12.8 C++ Compiler Warnings

+

14.8 C++ Compiler Warnings

When a C++ header file defining persistent classes and containing ODB pragmas is used to build the application, the C++ compiler may @@ -14340,7 +15139,7 @@ class person

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

-

12.8.1 GNU C++

+

14.8.1 GNU C++

GNU g++ does not issue warnings about unknown pragmas unless requested with the -Wall command line option. @@ -14352,7 +15151,7 @@ class person g++ -Wall -Wno-unknown-pragmas ...

-

12.8.2 Visual C++

+

14.8.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 @@ -14386,7 +15185,7 @@ class person #pragma warning (pop)

-

12.8.3 Sun C++

+

14.8.3 Sun C++

The Sun C++ compiler does not issue warnings about unknown pragmas unless the +w or +w2 option is specified. @@ -14398,7 +15197,7 @@ class person CC +w -erroff=unknownpragma ...

-

12.8.4 IBM XL C++

+

14.8.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 @@ -14408,7 +15207,7 @@ CC +w -erroff=unknownpragma ... xlC -qsuppress=1540-1401 ...

-

12.8.5 HP aC++

+

14.8.5 HP aC++

HP aC++ (aCC) issues an unknown pragma warning (2161) by default. To disable this warning we can add the +W2161 @@ -14418,7 +15217,7 @@ xlC -qsuppress=1540-1401 ... aCC +W2161 ... -

12.8.6 Clang

+

14.8.6 Clang

Clang does not issue warnings about unknown pragmas unless requested with the -Wall command line option. @@ -14457,12 +15256,12 @@ class person


-

13 Advanced Techniques and Mechanisms

+

15 Advanced Techniques and Mechanisms

This chapter covers more advanced techniques and mechanisms provided by ODB that may be useful in certain situations.

-

13.1 Transaction Callbacks

+

15.1 Transaction Callbacks

The ODB transaction class (odb::transaction) allows an application to register a callback that will be called after @@ -14559,7 +15358,7 @@ namespace odb

The following example shows how we can use transaction callbacks together with database operation callbacks - (Section 12.1.7, "callback") + (Section 14.1.7, "callback") to manage the object's "dirty" flag.

@@ -14639,12 +15438,12 @@ class object
      II consists of the following chapters.

- - - - - - + + + + + +
14Multi-Database Support
15MySQL Database
16SQLite Database
17PostgreSQL Database
18Oracle Database
19Microsoft SQL Server Database
16Multi-Database Support
17MySQL Database
18SQLite Database
19PostgreSQL Database
20Oracle Database
21Microsoft SQL Server Database
@@ -14652,7 +15451,7 @@ class object
-

14 Multi-Database Support

+

16 Multi-Database Support

Some applications may need to access multiple database systems, either simultaneously or one at a time. For example, an application may @@ -14713,7 +15512,7 @@ class object separate libraries and loaded dynamically by the application. The disadvantages of dynamic support are slight overhead and certain limitations in functionality compared to static support (see - Section 14.2, "Dynamic Multi-Database Support" + Section 16.2, "Dynamic Multi-Database Support" for details). As a result, dynamic multi-database support is most suitable to situations where we need the same code to work with a range of database systems. For example, if your @@ -14843,7 +15642,7 @@ odb --odb-file-suffix common:-odb-common ... support in more detail.

-

14.1 Static Multi-Database Support

+

16.1 Static Multi-Database Support

With static multi-database support, instead of including person-odb.hxx, application source code has @@ -15015,7 +15814,7 @@ odb -m static -d common -d pgsql -d sqlite --default-database pgsql ... support is enabled, then the common (dynamic) interface is always made the default database.

-

14.2 Dynamic Multi-Database Support

+

16.2 Dynamic Multi-Database Support

With dynamic multi-database support, application source code only needs to include the person-odb.hxx header file, just @@ -15186,7 +15985,7 @@ namespace odb expression). Every odb::<db>::query class provides such a translation constructor.

-

14.2.2 Dynamic Loading of Database Support Code

+

16.2.2 Dynamic Loading of Database Support Code

With dynamic multi-database support, the generated database support code automatically registers itself with the function tables that @@ -15364,7 +16163,7 @@ person.hxx


-

15 MySQL Database

+

17 MySQL Database

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

-

15.1 MySQL Type Mapping

+

17.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 - Pragma Language (Chapter 12, "ODB Pragma + Pragma Language (Chapter 14, "ODB Pragma Language").

@@ -15488,7 +16287,7 @@ person.hxx

It is possible to map the char C++ type to an integer database type (for example, TINYINT) using the - db type pragma (Section 12.4.3, + db type pragma (Section 14.4.3, "type").

Note that the std::string type is mapped @@ -15527,10 +16326,10 @@ class object

It is also possible to add support for additional MySQL types, such as geospatial types. For more information, refer to - Section 12.7, "Database Type Mapping + Section 14.7, "Database Type Mapping Pragmas".

-

15.1.1 String Type Mapping

+

17.1.1 String Type Mapping

The MySQL ODB runtime library provides support for mapping the std::string, char[N], and @@ -15540,8 +16339,8 @@ class object by default (in particular, by default, std::array will be treated as a container). To enable the alternative mappings for these types we need to specify the database type explicitly using - the db type pragma (Section - 12.4.3, "type"), for example:

+ the db type pragma (Section + 14.4.3, "type"), for example:

 #pragma db object
@@ -15576,7 +16375,7 @@ class object
      database, ODB will append the zero terminator if there is enough
      space.

-

15.1.2 Binary Type Mapping

+

17.1.2 Binary Type Mapping

The MySQL ODB runtime library provides support for mapping the std::vector<char>, @@ -15590,7 +16389,7 @@ class object std::array will be treated as containers). To enable the alternative mappings for these types we need to specify the database type explicitly using the db type pragma - (Section 12.4.3, "type"), for + (Section 14.4.3, "type"), for example:

@@ -15637,7 +16436,7 @@ db.query<object> ("uuid = " + query::_val<odb::mysql::id_blob> (u));
 db.query<object> (query::uuid == query::_ref (u));
   
-

15.2 MySQL Database Class

+

17.2 MySQL Database Class

The MySQL database class has the following interface:

@@ -15797,7 +16596,7 @@ namespace odb

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

The static print_usage() function prints the list of options @@ -15816,10 +16615,10 @@ namespace odb

The connection() function returns a pointer to the MySQL database connection encapsulated by the odb::mysql::connection class. For more information - on mysql::connection, refer to Section - 15.3, "MySQL Connection and Connection Factory".

+ on mysql::connection, refer to Section + 17.3, "MySQL Connection and Connection Factory".

-

15.3 MySQL Connection and Connection Factory

+

17.3 MySQL Connection and Connection Factory

The mysql::connection class has the following interface:

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

15.4 MySQL Exceptions

+

17.4 MySQL Exceptions

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

@@ -16063,12 +16862,12 @@ namespace odb what() function returns a human-readable description of an error.

-

15.5 MySQL Limitations

+

17.5 MySQL Limitations

The following sections describe MySQL-specific limitations imposed by the current MySQL and ODB runtime versions.

-

15.5.1 Foreign Key Constraints

+

17.5.1 Foreign Key Constraints

ODB relies on standard SQL behavior which requires that foreign key constraints checking is deferred until the transaction is @@ -16086,9 +16885,9 @@ namespace odb which you persist, update, and erase objects within a transaction becomes important.

-

15.6 MySQL Index Definitions

+

17.6 MySQL Index Definitions

-

When the index pragma (Section 12.6, +

When the index pragma (Section 14.6, "Index Definition Pragmas") is used to define a MySQL index, the type clause specifies the index type (for example, UNIQUE, FULLTEXT, SPATIAL), @@ -16115,7 +16914,7 @@ class object


-

16 SQLite Database

+

18 SQLite Database

To generate support code for the SQLite database you will need to pass the "--database sqlite" @@ -16124,12 +16923,12 @@ class object library (libodb-sqlite). All SQLite-specific ODB classes are defined in the odb::sqlite namespace.

-

16.1 SQLite Type Mapping

+

18.1 SQLite Type Mapping

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

@@ -16251,15 +17050,15 @@ class object

It is possible to map the char C++ type to the INTEGER SQLite type using the db type - pragma (Section 12.4.3, "type").

+ pragma (Section 14.4.3, "type").

SQLite represents the NaN FLOAT value as a NULL value. As a result, columns of the float and double types are by default declared as NULL. However, you can override this by explicitly declaring them as NOT NULL with the - db not_null pragma (Section - 12.4.6, "null/not_null").

+ db not_null pragma (Section + 14.4.6, "null/not_null").

Additionally, by default, C++ enumerations are automatically mapped to the SQLite INTEGER type with the default NULL @@ -16273,10 +17072,10 @@ class object

It is also possible to add support for additional SQLite types, such as NUMERIC. For more information, refer to - Section 12.7, "Database Type Mapping + Section 14.7, "Database Type Mapping Pragmas".

-

16.1.1 String Type Mapping

+

18.1.1 String Type Mapping

The SQLite ODB runtime library provides support for mapping the std::array<char, N> and, on Windows, @@ -16285,8 +17084,8 @@ class object default (in particular, by default, std::array will be treated as a container). To enable the alternative mapping for this type we need to specify the database type explicitly using - the db type pragma (Section - 12.4.3, "type"), for example:

+ the db type pragma (Section + 14.4.3, "type"), for example:

 #pragma db object
@@ -16320,7 +17119,7 @@ class object
      from the database, ODB will append the zero terminator if there is
      enough space.

-

16.1.2 Binary Type Mapping

+

18.1.2 Binary Type Mapping

The SQLite ODB runtime library provides support for mapping the std::vector<char>, @@ -16333,7 +17132,7 @@ class object std::vector and std::array will be treated as containers). To enable the alternative mappings for these types we need to specify the database type explicitly using the - db type pragma (Section 12.4.3, + db type pragma (Section 14.4.3, "type"), for example:

@@ -16380,7 +17179,7 @@ db.query<object> ("uuid = " + query::_val<odb::sqlite::id_blob> (u));
 db.query<object> (query::uuid == query::_ref (u));
   
-

16.2 SQLite Database Class

+

18.2 SQLite Database Class

The SQLite database class has the following interface:

@@ -16453,7 +17252,7 @@ namespace odb values, refer to the sqlite3_open_v2() function description in the SQLite C API documentation. The foreign_keys argument specifies whether foreign key constraints checking - should be enabled. See Section 16.5.3, + should be enabled. See Section 18.5.3, "Foreign Key Constraints" for more information on foreign keys. The vfs argument specifies the SQLite virtual file system module that should be used to access the @@ -16509,7 +17308,7 @@ auto_ptr<odb::database> db (

The third constructor throws the odb::sqlite::cli_exception exception if the SQLite option values are missing or invalid. - See Section 16.4, "SQLite Exceptions" + See Section 18.4, "SQLite Exceptions" for more information on this exception.

The static print_usage() function prints the list of options @@ -16538,10 +17337,10 @@ auto_ptr<odb::database> db (

The connection() function returns a pointer to the SQLite database connection encapsulated by the odb::sqlite::connection class. For more information - on sqlite::connection, refer to Section - 16.3, "SQLite Connection and Connection Factory".

+ on sqlite::connection, refer to Section + 18.3, "SQLite Connection and Connection Factory".

-

16.3 SQLite Connection and Connection Factory

+

18.3 SQLite Connection and Connection Factory

The sqlite::connection class has the following interface:

@@ -16586,7 +17385,7 @@ namespace odb functions allow us to start an immediate and an exclusive SQLite transaction on the connection, respectively. Their semantics are equivalent to the corresponding functions defined in the - sqlite::database class (Section 16.2, + sqlite::database class (Section 18.2, "SQLite Database Class"). The handle() accessor returns the SQLite handle corresponding to the connection.

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

16.4 SQLite Exceptions

+

18.4 SQLite Exceptions

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

@@ -16834,7 +17633,7 @@ namespace odb

The odb::sqlite::forced_rollback exception is thrown if SQLite is forcing the current transaction to roll back. For more - information on this behavior refer to Section 16.5.6, + information on this behavior refer to Section 18.5.6, "Forced Rollback".

The odb::sqlite::database_exception is thrown if @@ -16851,12 +17650,12 @@ namespace odb of an error.

-

16.5 SQLite Limitations

+

18.5 SQLite Limitations

The following sections describe SQLite-specific limitations imposed by the current SQLite and ODB runtime versions.

-

16.5.1 Query Result Caching

+

18.5.1 Query Result Caching

SQLite ODB runtime implementation does not perform query result caching (Section 4.4, "Query Result") even when explicitly @@ -16871,17 +17670,17 @@ namespace odb thrown. Future versions of the SQLite ODB runtime library may add support for result caching.

-

16.5.2 Automatic Assignment of Object Ids

+

18.5.2 Automatic Assignment of Object Ids

Due to SQLite API limitations, every automatically assigned object id - (Section 12.4.2, "auto") should have + (Section 14.4.2, "auto") should have the INTEGER SQLite type. While SQLite will treat other integer type names (such as INT, BIGINT, etc.) as INTEGER, automatic id assignment will not work. By default, ODB maps all C++ integral types to INTEGER. This means that the only situation that requires consideration is the assignment of a custom database type using the db type pragma - (Section 12.4.3, "type"). For + (Section 14.4.3, "type"). For example:

@@ -16897,7 +17696,7 @@ class person
 };
   
-

16.5.3 Foreign Key Constraints

+

18.5.3 Foreign Key Constraints

By default the SQLite ODB runtime enables foreign key constraints checking (PRAGMA foreign_keys=ON). You can disable foreign @@ -16967,7 +17766,7 @@ CREATE TABLE Employee ( which you persist, update, and erase objects within a transaction becomes important.

-

16.5.4 Constraint Violations

+

18.5.4 Constraint Violations

Due to the granularity of the SQLite error codes, it is impossible to distinguish between the duplicate primary key and other constraint @@ -16976,7 +17775,7 @@ CREATE TABLE Employee ( object_already_persistent exception (Section 3.14, "ODB Exceptions").

-

16.5.5 Sharing of Queries

+

18.5.5 Sharing of Queries

As discussed in Section 4.3, "Executing a Query", a query instance that does not have any by-reference parameters is @@ -16985,7 +17784,7 @@ CREATE TABLE Employee ( functionality. Future versions of the library will remove this limitation.

-

16.5.6 Forced Rollback

+

18.5.6 Forced Rollback

In SQLite 3.7.11 or later, if one of the connections participating in the shared cache rolls back a transaction, then ongoing transactions @@ -16996,14 +17795,14 @@ CREATE TABLE Employee (

If a transaction is being forced to roll back by SQLite, then ODB throws odb::sqlite::forced_rollback - (Section 16.4, "SQLite Exceptions") which is + (Section 18.4, "SQLite Exceptions") which is a recoverable exception (3.7 Error Handling and Recovery). As a result, the recommended way to handle this exception is to re-execute the affected transaction.

-

16.6 SQLite Index Definitions

+

18.6 SQLite Index Definitions

-

When the index pragma (Section 12.6, +

When the index pragma (Section 14.6, "Index Definition Pragmas") is used to define an SQLite index, the type clause specifies the index type (for example, UNIQUE) while the method and @@ -17030,7 +17829,7 @@ class object


-

17 PostgreSQL Database

+

19 PostgreSQL Database

To generate support code for the PostgreSQL database you will need to pass the "--database pgsql" @@ -17044,12 +17843,12 @@ class object of the messaging protocol version 3.0. For this reason, ODB supports only PostgreSQL version 7.4 and later.

-

17.1 PostgreSQL Type Mapping

+

19.1 PostgreSQL Type Mapping

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

@@ -17159,7 +17958,7 @@ class object

It is possible to map the char C++ type to an integer database type (for example, SMALLINT) using the - db type pragma (Section 12.4.3, + db type pragma (Section 14.4.3, "type").

Additionally, by default, C++ enumerations are automatically @@ -17179,10 +17978,10 @@ class object such as NUMERIC, geometry types, XML, JSON, enumeration types, composite types, arrays, geospatial types, and the key-value store (HSTORE). - For more information, refer to Section 12.7, + For more information, refer to Section 14.7, "Database Type Mapping Pragmas".

-

17.1.1 String Type Mapping

+

19.1.1 String Type Mapping

The PostgreSQL ODB runtime library provides support for mapping the std::string, char[N], and @@ -17192,7 +17991,7 @@ class object particular, by default, std::array will be treated as a container). To enable the alternative mappings for these types we need to specify the database type explicitly using the - db type pragma (Section 12.4.3, + db type pragma (Section 14.4.3, "type"), for example:

@@ -17228,7 +18027,7 @@ class object
      database, ODB will append the zero terminator if there is enough
      space.

-

17.1.2 Binary Type and UUID Mapping

+

19.1.2 Binary Type and UUID Mapping

The PostgreSQL ODB runtime library provides support for mapping the std::vector<char>, @@ -17242,7 +18041,7 @@ class object default, std::vector and std::array will be treated as containers). To enable the alternative mappings for these types we need to specify the database type explicitly using the - db type pragma (Section 12.4.3, + db type pragma (Section 14.4.3, "type"), for example:

@@ -17293,7 +18092,7 @@ db.query<object> ("buf = " + query::_val<odb::pgsql::id_bytea> (u));
 db.query<object> (query::uuid == query::_ref (u));
   
-

17.2 PostgreSQL Database Class

+

19.2 PostgreSQL Database Class

The PostgreSQL database class has the following interface:

@@ -17413,7 +18212,7 @@ namespace odb

This constructor throws the odb::pgsql::cli_exception exception if the PostgreSQL option values are missing or invalid. - See section Section 17.4, "PostgreSQL Exceptions" + See section Section 19.4, "PostgreSQL Exceptions" for more information on this exception.

The static print_usage() function prints the list of options @@ -17439,10 +18238,10 @@ namespace odb

The connection() function returns a pointer to the PostgreSQL database connection encapsulated by the odb::pgsql::connection class. For more information - on pgsql::connection, refer to Section - 17.3, "PostgreSQL Connection and Connection Factory".

+ on pgsql::connection, refer to Section + 19.3, "PostgreSQL Connection and Connection Factory".

-

17.3 PostgreSQL Connection and Connection Factory

+

19.3 PostgreSQL Connection and Connection Factory

The pgsql::connection class has the following interface:

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

17.4 PostgreSQL Exceptions

+

19.4 PostgreSQL Exceptions

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

@@ -17672,12 +18471,12 @@ namespace odb what() function returns a human-readable description of an error.

-

17.5 PostgreSQL Limitations

+

19.5 PostgreSQL Limitations

The following sections describe PostgreSQL-specific limitations imposed by the current PostgreSQL and ODB runtime versions.

-

17.5.1 Query Result Caching

+

19.5.1 Query Result Caching

The PostgreSQL ODB runtime implementation will always return a cached query result (Section 4.4, "Query Result") @@ -17685,7 +18484,7 @@ namespace odb PostgreSQL client library (libpq) which does not support uncached (streaming) query results.

-

17.5.2 Foreign Key Constraints

+

19.5.2 Foreign Key Constraints

ODB assumes the standard SQL behavior which requires that foreign key constraints checking is deferred until the @@ -17710,7 +18509,7 @@ CREATE TABLE Employee ( which you persist, update, and erase objects within a transaction becomes important.

-

17.5.3 Unique Constraint Violations

+

19.5.3 Unique Constraint Violations

Due to the granularity of the PostgreSQL error codes, it is impossible to distinguish between the duplicate primary key and other unique @@ -17719,7 +18518,7 @@ CREATE TABLE Employee ( errors to the object_already_persistent exception (Section 3.14, "ODB Exceptions").

-

17.5.4 Date-Time Format

+

19.5.4 Date-Time Format

ODB expects the PostgreSQL server to use integers as a binary format for the date-time types, which is the default for most @@ -17734,14 +18533,14 @@ CREATE TABLE Employee ( SHOW integer_datetimes

-

17.5.5 Timezones

+

19.5.5 Timezones

ODB does not currently natively support the PostgreSQL date-time types with timezone information. However, these types can be accessed by mapping them to one of the natively supported types, as discussed - in Section 12.7, "Database Type Mapping Pragmas".

+ in Section 14.7, "Database Type Mapping Pragmas".

-

17.5.6 NUMERIC Type Support

+

19.5.6 NUMERIC Type Support

Support for the PostgreSQL NUMERIC type is limited to providing a binary buffer containing the binary representation @@ -17749,13 +18548,13 @@ SHOW integer_datetimes store NUMERIC values refer to the PostgreSQL documentation. An alternative approach to accessing NUMERIC values is to map this type to one of the natively supported - ones, as discussed in Section 12.7, "Database + ones, as discussed in Section 14.7, "Database Type Mapping Pragmas".

-

17.6 PostgreSQL Index Definitions

+

19.6 PostgreSQL Index Definitions

-

When the index pragma (Section 12.6, +

When the index pragma (Section 14.6, "Index Definition Pragmas") is used to define a PostgreSQL index, the type clause specifies the index type (for example, UNIQUE), the method clause specifies the @@ -17792,7 +18591,7 @@ class object


-

18 Oracle Database

+

20 Oracle Database

To generate support code for the Oracle database you will need to pass the "--database oracle" @@ -17801,12 +18600,12 @@ class object library (libodb-oracle). All Oracle-specific ODB classes are defined in the odb::oracle namespace.

-

18.1 Oracle Type Mapping

+

20.1 Oracle Type Mapping

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

@@ -17916,7 +18715,7 @@ class object

It is possible to map the char C++ type to an integer database type (for example, NUMBER(3)) using the - db type pragma (Section 12.4.3, + db type pragma (Section 14.4.3, "type").

In Oracle empty VARCHAR2 and NVARCHAR2 @@ -17925,8 +18724,8 @@ class object types are by default declared as NULL except for primary key columns. However, you can override this by explicitly declaring such columns as NOT NULL with the - db not_null pragma (Section - 12.4.6, "null/not_null"). This also means that for + db not_null pragma (Section + 14.4.6, "null/not_null"). This also means that for object ids that are mapped to these Oracle types, an empty string is an invalid value.

@@ -17937,10 +18736,10 @@ class object

It is also possible to add support for additional Oracle types, such as XML, geospatial types, user-defined types, and collections (arrays, table types). For more information, refer to - Section 12.7, "Database Type Mapping + Section 14.7, "Database Type Mapping Pragmas".

-

18.1.1 String Type Mapping

+

20.1.1 String Type Mapping

The Oracle ODB runtime library provides support for mapping the std::string, char[N], and @@ -17951,7 +18750,7 @@ class object default, std::array will be treated as a container). To enable the alternative mappings for these types we need to specify the database type explicitly using the db type - pragma (Section 12.4.3, "type"), + pragma (Section 14.4.3, "type"), for example:

@@ -17993,7 +18792,7 @@ class object
      database, ODB will append the zero terminator if there is enough
      space.

-

18.1.2 Binary Type Mapping

+

20.1.2 Binary Type Mapping

The Oracle ODB runtime library provides support for mapping the std::vector<char>, @@ -18006,7 +18805,7 @@ class object default, std::vector and std::array will be treated as containers). To enable the alternative mappings for these types we need to specify the database type explicitly using the - db type pragma (Section 12.4.3, + db type pragma (Section 14.4.3, "type"), for example:

@@ -18053,7 +18852,7 @@ db.query<object> ("uuid = " + query::_val<odb::oracle::id_raw> (u));
 db.query<object> (query::uuid == query::_ref (u));
   
-

18.2 Oracle Database Class

+

20.2 Oracle Database Class

The Oracle database class encapsulates the OCI environment handle as well as the database connection string and user credentials @@ -18178,7 +18977,7 @@ namespace odb

This constructor throws the odb::oracle::cli_exception exception if the Oracle option values are missing or invalid. See section - Section 18.4, "Oracle Exceptions" for more + Section 20.4, "Oracle Exceptions" for more information on this exception.

The static print_usage() function prints the list of options @@ -18228,10 +19027,10 @@ namespace odb

The connection() function returns a pointer to the Oracle database connection encapsulated by the odb::oracle::connection class. For more information - on oracle::connection, refer to Section - 18.3, "Oracle Connection and Connection Factory".

+ on oracle::connection, refer to Section + 20.3, "Oracle Connection and Connection Factory".

-

18.3 Oracle Connection and Connection Factory

+

20.3 Oracle Connection and Connection Factory

The oracle::connection class has the following interface:

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

18.4 Oracle Exceptions

+

20.4 Oracle Exceptions

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

@@ -18514,12 +19313,12 @@ namespace odb condition. The what() function returns a human-readable description of an error.

-

18.5 Oracle Limitations

+

20.5 Oracle Limitations

The following sections describe Oracle-specific limitations imposed by the current Oracle and ODB runtime versions.

-

18.5.1 Identifier Truncation

+

20.5.1 Identifier Truncation

Oracle limits the length of database identifiers (table, column, etc., names) to 30 characters. The ODB compiler automatically truncates @@ -18529,7 +19328,7 @@ namespace odb that a database object with the same name already exists. To resolve this problem we can assign custom, shorter identifiers using the db table and db column - pragmas (Chapter 12, "ODB Pragma Language"). For + pragmas (Chapter 14, "ODB Pragma Language"). For example:

@@ -18564,7 +19363,7 @@ class long_class_name
 };
   
-

18.5.2 Query Result Caching

+

20.5.2 Query Result Caching

Oracle ODB runtime implementation does not perform query result caching (Section 4.4, "Query Result") even when explicitly @@ -18579,7 +19378,7 @@ class long_class_name always thrown. Future versions of the Oracle ODB runtime library may add support for result caching.

-

18.5.3 Foreign Key Constraints

+

20.5.3 Foreign Key Constraints

ODB assumes the standard SQL behavior which requires that foreign key constraints checking is deferred until the @@ -18605,7 +19404,7 @@ CREATE TABLE Employee ( which you persist, update, and erase objects within a transaction becomes important.

-

18.5.4 Unique Constraint Violations

+

20.5.4 Unique Constraint Violations

Due to the granularity of the Oracle error codes, it is impossible to distinguish between the duplicate primary key and other unique @@ -18614,7 +19413,7 @@ CREATE TABLE Employee ( errors to the object_already_persistent exception (Section 3.14, "ODB Exceptions").

-

18.5.5 Large FLOAT and +

20.5.5 Large FLOAT and NUMBER Types

The Oracle FLOAT type with a binary precision greater @@ -18632,31 +19431,31 @@ CREATE TABLE Employee (

An alternative approach to accessing large FLOAT and NUMBER values is to map these type to one of the - natively supported ones, as discussed in Section - 12.7, "Database Type Mapping Pragmas".

+ natively supported ones, as discussed in Section + 14.7, "Database Type Mapping Pragmas".

Note that a NUMBER type that is used to represent a floating point number (declared by specifying NUMBER without any range and scale) can be extracted into the C++ float and double types.

-

18.5.6 Timezones

+

20.5.6 Timezones

ODB does not currently support the Oracle date-time types with timezone information. However, these types can be accessed by mapping them to one of the natively supported types, as discussed in - Section 12.7, "Database Type Mapping Pragmas".

+ Section 14.7, "Database Type Mapping Pragmas".

-

18.5.7 LONG Types

+

20.5.7 LONG Types

ODB does not support the deprecated Oracle LONG and LONG RAW data types. However, these types can be accessed by mapping them to one of the natively supported types, as discussed - in Section 12.7, "Database Type Mapping Pragmas".

+ in Section 14.7, "Database Type Mapping Pragmas".

-

18.5.8 LOB Types and By-Value Accessors/Modifiers

+

20.5.8 LOB Types and By-Value Accessors/Modifiers

-

As discussed in Section 12.4.5, +

As discussed in Section 14.4.5, "get/set/access", by-value accessor and modifier expressions cannot be used with data members of Oracle large object (LOB) data types: BLOB, @@ -18665,9 +19464,9 @@ CREATE TABLE Employee ( data members. As a result, by-reference accessors and modifiers should be used for these data types.

-

18.6 Oracle Index Definitions

+

20.6 Oracle Index Definitions

-

When the index pragma (Section 12.6, +

When the index pragma (Section 14.6, "Index Definition Pragmas") is used to define an Oracle index, the type clause specifies the index type (for example, UNIQUE, BITMAP), the method @@ -18699,7 +19498,7 @@ class object


-

19 Microsoft SQL Server Database

+

21 Microsoft SQL Server Database

To generate support code for the SQL Server database you will need to pass the "--database mssql" @@ -18708,12 +19507,12 @@ class object library (libodb-mssql). All SQL Server-specific ODB classes are defined in the odb::mssql namespace.

-

19.1 SQL Server Type Mapping

+

21.1 SQL Server Type Mapping

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

+ Pragma Language (Chapter 14, "ODB Pragma Language").

@@ -18841,7 +19640,7 @@ class object

It is possible to map the char C++ type to an integer database type (for example, TINYINT) using the - db type pragma (Section 12.4.3, + db type pragma (Section 14.4.3, "type").

Note that the std::string and std::wstring @@ -18853,7 +19652,7 @@ class object is mapped to VARCHAR(512) and std::wstring — to NVARCHAR(512). Note also that you can always change this mapping using the db type pragma - (Section 12.4.3, "type").

+ (Section 14.4.3, "type").

Additionally, by default, C++ enumerations are automatically mapped to INT with the default NULL @@ -18875,10 +19674,10 @@ class object

It is also possible to add support for additional SQL Server types, such as geospatial types, XML, and user-defined types. - For more information, refer to Section 12.7, "Database + For more information, refer to Section 14.7, "Database Type Mapping Pragmas".

-

19.1.1 String Type Mapping

+

21.1.1 String Type Mapping

The SQL Server ODB runtime library provides support for mapping the std::string, char[N], and @@ -18891,7 +19690,7 @@ class object std::array will be treated as a container). To enable the alternative mappings for these types we need to specify the database type explicitly using the db type pragma - (Section 12.4.3, "type"), for + (Section 14.4.3, "type"), for example:

@@ -18928,10 +19727,10 @@ class object
      from the database, ODB will append the zero terminator if there is
      enough space.

-

See also Section 19.1.4, "Long String and Binary +

See also Section 21.1.4, "Long String and Binary Types" for certain limitations of long string types.

-

19.1.2 Binary Type and UNIQUEIDENTIFIER Mapping

+

21.1.2 Binary Type and UNIQUEIDENTIFIER Mapping

The SQL Server ODB runtime library also provides support for mapping the std::vector<char>, @@ -18945,7 +19744,7 @@ class object by default, std::vector and std::array will be treated as containers). To enable the alternative mappings for these types we need to specify the database type explicitly using the - db type pragma (Section 12.4.3, + db type pragma (Section 14.4.3, "type"), for example:

@@ -18997,17 +19796,17 @@ db.query<object> (
 db.query<object> (query::uuid == query::_ref (u));
   
-

See also Section 19.1.4, "Long String and Binary +

See also Section 21.1.4, "Long String and Binary Types" for certain limitations of long binary types.

-

19.1.3 ROWVERSION Mapping

+

21.1.3 ROWVERSION Mapping

ROWVERSION is a special SQL Server data type that is automatically incremented by the database server whenever a row is inserted or updated. As such, it is normally used to implement optimistic concurrency and ODB provides support for using ROWVERSION instead of the more portable approach - for optimistic concurrency (Chapter 11, "Optimistic + for optimistic concurrency (Chapter 12, "Optimistic Concurrency").

ROWVERSION is a 64-bit value which is mapped by ODB @@ -19029,7 +19828,7 @@ class person };

-

19.1.4 Long String and Binary Types

+

21.1.4 Long String and Binary Types

For SQL Server, ODB handles character, national character, and binary data in two different ways depending on its maximum length. @@ -19052,7 +19851,7 @@ class person than once as part of a query result iteration (Section 4.4, "Query Result"). Any such attempt will result in the odb::mssql::long_data_reload exception - (Section 19.4, "SQL Server Exceptions"). For + (Section 21.4, "SQL Server Exceptions"). For example:

@@ -19086,12 +19885,12 @@ for (result::iterator i (r.begin ()); i != r.end (); ++i)
 t.commit ();
   
-

Finally, if a native view (Section 9.5, "Native +

Finally, if a native view (Section 10.5, "Native Views") contains one or more long data members, then such members should come last both in the select-list of the native SQL query and the list of data members in the C++ class.

-

19.2 SQL Server Database Class

+

21.2 SQL Server Database Class

The SQL Server database class encapsulates the ODBC environment handle as well as the server instance address and @@ -19388,7 +20187,7 @@ odb::mssql::database dbA ("test",

This constructor throws the odb::mssql::cli_exception exception if the SQL Server option values are missing or invalid. See - section Section 19.4, "SQL Server Exceptions" for + section Section 21.4, "SQL Server Exceptions" for more information on this exception.

The static print_usage() function prints the list of options @@ -19419,10 +20218,10 @@ odb::mssql::database dbA ("test",

The connection() function returns a pointer to the SQL Server database connection encapsulated by the odb::mssql::connection class. For more information - on mssql::connection, refer to Section - 19.3, "SQL Server Connection and Connection Factory".

+ on mssql::connection, refer to Section + 21.3, "SQL Server Connection and Connection Factory".

-

19.3 SQL Server Connection and Connection Factory

+

21.3 SQL Server Connection and Connection Factory

The mssql::connection class has the following interface:

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

19.4 SQL Server Exceptions

+

21.4 SQL Server Exceptions

The SQL Server ODB runtime library defines the following SQL Server-specific exceptions:

@@ -19698,14 +20497,14 @@ namespace odb

The odb::mssql::long_data_reload is thrown if an attempt is made to re-load an object or view with long data as part of a query result iteration. For more information, refer - to Section 19.1, "SQL Server Type Mapping".

+ to Section 21.1, "SQL Server Type Mapping".

-

19.5 SQL Server Limitations

+

21.5 SQL Server Limitations

The following sections describe SQL Server-specific limitations imposed by the current SQL Server and ODB runtime versions.

-

19.5.1 Query Result Caching

+

21.5.1 Query Result Caching

SQL Server ODB runtime implementation does not perform query result caching (Section 4.4, "Query Result") even when @@ -19720,7 +20519,7 @@ namespace odb 3.14, "ODB Exceptions") is always thrown. Future versions of the SQL Server ODB runtime library may add support for result caching.

-

19.5.2 Foreign Key Constraints

+

21.5.2 Foreign Key Constraints

ODB assumes the standard SQL behavior which requires that foreign key constraints checking is deferred until the transaction is @@ -19736,7 +20535,7 @@ namespace odb which you persist, update, and erase objects within a transaction becomes important.

-

19.5.3 Unique Constraint Violations

+

21.5.3 Unique Constraint Violations

Due to the granularity of the ODBC error codes, it is impossible to distinguish between the duplicate primary key and other unique @@ -19745,7 +20544,7 @@ namespace odb errors to the object_already_persistent exception (Section 3.14, "ODB Exceptions").

-

19.5.4 Multi-threaded Windows Applications

+

21.5.4 Multi-threaded Windows Applications

Multi-threaded Windows applications must use the _beginthread()/_beginthreadex() and @@ -19754,7 +20553,7 @@ namespace odb Win32 functions to start and terminate threads. This is a limitation of the ODBC implementation on Windows.

-

19.5.5 Affected Row Count and DDL Statements

+

21.5.5 Affected Row Count and DDL Statements

SQL Server always returns zero as the number of affected rows for DDL statements. In particular, this means that the @@ -19762,7 +20561,7 @@ namespace odb "Executing Native SQL Statements") function will always return zero for such statements.

-

19.5.6 Long Data and Auto Object Ids, ROWVERSION

+

21.5.6 Long Data and Auto Object Ids, ROWVERSION

SQL Server 2005 has a bug that causes it to fail on an INSERT or UPDATE statement with the OUTPUT clause @@ -19772,7 +20571,7 @@ namespace odb by the database::persist() or database::update() function when used on an object that contains long data and has an automatically assigned object id or uses ROWVERSION-based - optimistic concurrency (Section 19.1.1, + optimistic concurrency (Section 21.1.1, "ROWVERSION Support"). The error message reads "This operation conflicts with another pending operation on this transaction. The operation failed."

@@ -19789,9 +20588,9 @@ namespace odb objects that use ROWVERSION for optimistic concurrency and containing long data.

-

19.5.7 Long Data and By-Value Accessors/Modifiers

+

21.5.7 Long Data and By-Value Accessors/Modifiers

-

As discussed in Section 12.4.5, +

As discussed in Section 14.4.5, "get/set/access", by-value accessor and modifier expressions cannot be used with data members of long data types. The SQL Server ODB runtime uses streaming for @@ -19799,9 +20598,9 @@ namespace odb by-reference accessors and modifiers should be used for these data types.

-

19.6 SQL Server Index Definitions

+

21.6 SQL Server Index Definitions

-

When the index pragma (Section 12.6, +

When the index pragma (Section 14.6, "Index Definition Pragmas") is used to define an SQL Server index, the type clause specifies the index type (for example, UNIQUE, CLUSTERED), the method @@ -19836,9 +20635,9 @@ class object and libraries. It consists of the following chapters.

- - - + + +
20Profiles Introduction
21Boost Profile
22Qt Profile
22Profiles Introduction
23Boost Profile
24Qt Profile
@@ -19846,7 +20645,7 @@ class object
-

20 Profiles Introduction

+

22 Profiles Introduction

ODB profiles are a generic mechanism for integrating ODB with widely-used C++ frameworks and libraries. A profile provides glue @@ -19900,7 +20699,7 @@ odb --profile boost/date-time ...


-

21 Boost Profile

+

23 Boost Profile

The ODB profile implementation for Boost is provided by the libodb-boost library and consists of multiple sub-profiles @@ -19925,7 +20724,7 @@ odb --profile boost/date-time ... that can be thrown by the Boost sub-profiles are described in the following sections.

-

21.1 Smart Pointers Library

+

23.1 Smart Pointers Library

The smart-ptr sub-profile provides persistence support for a subset of smart pointers from the Boost @@ -19995,7 +20794,7 @@ class employee this behavior, add the --default-pointer option specifying the alternative pointer type after the --profile option.

-

21.2 Unordered Containers Library

+

23.2 Unordered Containers Library

The unordered sub-profile provides persistence support for the containers from the Boost unordered library. To enable @@ -20021,7 +20820,7 @@ class person }; -

21.3 Multi-Index Container Library

+

23.3 Multi-Index Container Library

The multi-index sub-profile provides persistence support for boost::multi_index_container from the Boost Multi-Index @@ -20106,7 +20905,7 @@ class person }; -

21.4 Optional Library

+

23.4 Optional Library

The optional sub-profile provides persistence support for the boost::optional container from the Boost @@ -20138,7 +20937,7 @@ class person this profile is used, the NULL values are automatically enabled for data members of the boost::optional type.

-

21.5 Date Time Library

+

23.5 Date Time Library

The date-time sub-profile provides persistence support for a subset of types from the Boost date_time library. It is @@ -20211,7 +21010,7 @@ namespace odb exceptions are thrown are database system dependent and are discussed in more detail in the following sub-sections.

-

21.5.1 MySQL Database Type Mapping

+

23.5.1 MySQL Database Type Mapping

The following table summarizes the default mapping between the currently supported Boost date_time types and the MySQL database @@ -20251,7 +21050,7 @@ namespace odb support for mapping posix_time::ptime to the TIMESTAMP MySQL type. However, this mapping has to be explicitly requested using the db type pragma - (Section 12.4.3, "type"), as shown in + (Section 14.4.3, "type"), as shown in the following example:

@@ -20304,7 +21103,7 @@ class person
      the out_of_range exception.  Refer to the MySQL
      documentation for more information on the MySQL data type ranges.

-

21.5.2 SQLite Database Type Mapping

+

23.5.2 SQLite Database Type Mapping

The following table summarizes the default mapping between the currently supported Boost date_time types and the SQLite database @@ -20347,7 +21146,7 @@ class person alternative mapping for posix_time::time_duration to the INTEGER type represents the duration as a number of seconds. These mappings have to be explicitly requested using the - db type pragma (Section 12.4.3, + db type pragma (Section 14.4.3, "type"), as shown in the following example:

@@ -20382,7 +21181,7 @@ class person
      will result in the out_of_range exception.

-

21.5.3 PostgreSQL Database Type Mapping

+

23.5.3 PostgreSQL Database Type Mapping

The following table summarizes the default mapping between the currently supported Boost date_time types and the PostgreSQL database @@ -20433,7 +21232,7 @@ class person result in the special_value exception.

-

21.5.4 Oracle Database Type Mapping

+

23.5.4 Oracle Database Type Mapping

The following table summarizes the default mapping between the currently supported Boost date_time types and the Oracle database @@ -20474,7 +21273,7 @@ class person DATE Oracle type with fractional seconds that may be stored in a ptime instance being ignored. This alternative mapping has to be explicitly requested using the - db type pragma (Section 12.4.3, + db type pragma (Section 14.4.3, "type"), as shown in the following example:

@@ -20495,7 +21294,7 @@ class person
      the special_value exception.

-

21.5.5 SQL Server Database Type Mapping

+

23.5.5 SQL Server Database Type Mapping

The following table summarizes the default mapping between the currently supported Boost date_time types and the SQL Server database @@ -20542,7 +21341,7 @@ class person support for mapping posix_time::ptime to the DATETIME and SMALLDATETIME types, however, this mapping has to be explicitly requested using the - db type pragma (Section 12.4.3, + db type pragma (Section 14.4.3, "type"), as shown in the following example:

@@ -20566,7 +21365,7 @@ class person
      posix_time::time_duration value out of this range will
      result in the value_out_of_range exception.

-

21.6 Uuid Library

+

23.6 Uuid Library

The uuid sub-profile provides persistence support for the uuid type from the Boost uuid library. To @@ -20579,8 +21378,8 @@ class person database column with NULL enabled and nil uuid instances are stored as a NULL value. However, you can change this behavior by declaring the data member NOT NULL - with the not_null pragma (Section - 12.4.6, "null/not_null"). In this + with the not_null pragma (Section + 14.4.6, "null/not_null"). In this case, or if the data member is an object id, the implementation will store nil uuid instances as zero UUID values ({00000000-0000-0000-0000-000000000000}). For example:

@@ -20598,7 +21397,7 @@ class object };
-

21.6.1 MySQL Database Type Mapping

+

23.6.1 MySQL Database Type Mapping

The following table summarizes the default mapping between the Boost uuid type and the MySQL database type.

@@ -20618,7 +21417,7 @@ class object -

21.6.2 SQLite Database Type Mapping

+

23.6.2 SQLite Database Type Mapping

The following table summarizes the default mapping between the Boost uuid type and the SQLite database type.

@@ -20638,7 +21437,7 @@ class object -

21.6.3 PostgreSQL Database Type Mapping

+

23.6.3 PostgreSQL Database Type Mapping

The following table summarizes the default mapping between the Boost uuid type and the PostgreSQL database type.

@@ -20658,7 +21457,7 @@ class object -

21.6.4 Oracle Database Type Mapping

+

23.6.4 Oracle Database Type Mapping

The following table summarizes the default mapping between the Boost uuid type and the Oracle database type.

@@ -20678,7 +21477,7 @@ class object -

21.6.5 SQL Server Database Type Mapping

+

23.6.5 SQL Server Database Type Mapping

The following table summarizes the default mapping between the Boost uuid type and the SQL Server database type.

@@ -20703,7 +21502,7 @@ class object
-

22 Qt Profile

+

24 Qt Profile

The ODB profile implementation for Qt is provided by the libodb-qt library. Both Qt4 and Qt5 as well @@ -20732,7 +21531,7 @@ class object that can be thrown by the Qt sub-profiles are described in the following sections.

-

22.1 Basic Types Library

+

24.1 Basic Types Library

The basic sub-profile provides persistence support for basic types defined by Qt. To enable only this profile, pass @@ -20759,8 +21558,8 @@ class Person database column with NULL enabled and null QUuid instances are stored as a NULL value. However, you can change this behavior by declaring the data member NOT NULL - with the not_null pragma (Section - 12.4.6, "null/not_null"). In this + with the not_null pragma (Section + 14.4.6, "null/not_null"). In this case, or if the data member is an object id, the implementation will store null QUuid instances as zero UUID values ({00000000-0000-0000-0000-000000000000}). For example:

@@ -20778,7 +21577,7 @@ class object };
-

22.1.1 MySQL Database Type Mapping

+

24.1.1 MySQL Database Type Mapping

The following table summarizes the default mapping between the currently supported basic Qt types and the MySQL database types.

@@ -20826,7 +21625,7 @@ class object NCHAR, and NVARCHAR MySQL types. However, these alternative mappings have to be explicitly requested using the db type pragma - (Section 12.4.3, "type"), as shown in + (Section 14.4.3, "type"), as shown in the following example:

@@ -20841,7 +21640,7 @@ class Person
   
-

22.1.2 SQLite Database Type Mapping

+

24.1.2 SQLite Database Type Mapping

The following table summarizes the default mapping between the currently supported basic Qt types and the SQLite database types.

@@ -20877,7 +21676,7 @@ class Person are stored as a NULL value if their isNull() member function returns true.

-

22.1.3 PostgreSQL Database Type Mapping

+

24.1.3 PostgreSQL Database Type Mapping

The following table summarizes the default mapping between the currently supported basic Qt types and the PostgreSQL database types.

@@ -20918,7 +21717,7 @@ class Person and VARCHAR PostgreSQL types. However, these alternative mappings have to be explicitly requested using the db type pragma - (Section 12.4.3, "type"), as shown in + (Section 14.4.3, "type"), as shown in the following example:

@@ -20932,7 +21731,7 @@ class Person
 };
   
-

22.1.4 Oracle Database Type Mapping

+

24.1.4 Oracle Database Type Mapping

The following table summarizes the default mapping between the currently supported basic Qt types and the Oracle database types.

@@ -20974,7 +21773,7 @@ class Person NCLOB Oracle types, and for mapping QByteArray to the RAW Oracle type. However, these alternative mappings have to be explicitly requested using the db type - pragma (Section 12.4.3, "type"), as shown in the + pragma (Section 14.4.3, "type"), as shown in the following example:

@@ -20991,7 +21790,7 @@ class Person
 };
   
-

22.1.5 SQL Server Database Type Mapping

+

24.1.5 SQL Server Database Type Mapping

The following table summarizes the default mapping between the currently supported basic Qt types and the SQL Server database types.

@@ -21041,7 +21840,7 @@ class Person QByteArray to the BINARY and IMAGE SQL Server types. However, these alternative mappings have to be explicitly requested using the db type - pragma (Section 12.4.3, "type"), as shown in the + pragma (Section 14.4.3, "type"), as shown in the following example:

@@ -21058,7 +21857,7 @@ class Person
 };
   
-

22.2 Smart Pointers Library

+

24.2 Smart Pointers Library

The smart-ptr sub-profile provides persistence support the Qt smart pointers. To enable only this profile, pass @@ -21127,7 +21926,7 @@ class Employee this behavior, add the --default-pointer option specifying the alternative pointer type after the --profile option.

-

22.3 Containers Library

+

24.3 Containers Library

The containers sub-profile provides persistence support for Qt containers. To enable only this profile, pass @@ -21153,13 +21952,13 @@ class Person

The containers sub-profile also provide a change-tracking - equivalent for QList (Section 22.3.1, + equivalent for QList (Section 24.3.1, "Change-Tracking QList") with support for other Qt container equivalents planned for future releases. For general information on change-tracking containers refer to Section 5.4, "Change-Tracking Containers".

-

22.3.1 Change-Tracking QList

+

24.3.1 Change-Tracking QList

Class template QOdbList, defined in <odb/qt/list.hxx>, is a change-tracking @@ -21175,8 +21974,8 @@ class Person

QOdbList incurs 2-bit per element overhead in order to store the change state. It cannot - be stored unordered in the database (Section - 12.4.18 "unordered") but can be used as an inverse + be stored unordered in the database (Section + 14.4.18 "unordered") but can be used as an inverse side of a relationship (6.2 "Bidirectional Relationships"). In this case, no change tracking is performed since no state for such a container is stored in the database.

@@ -21340,7 +22139,7 @@ qSort (l.modifyBegin (), l.modifyEnd ()); that any element that such an iterator passes over with the call to next() is marked as modified.

-

22.4 Date Time Library

+

24.4 Date Time Library

The date-time sub-profile provides persistence support for the Qt date-time types. To enable only this profile, pass @@ -21393,7 +22192,7 @@ namespace odb system dependent and is discussed in more detail in the following sub-sections.

-

22.4.1 MySQL Database Type Mapping

+

24.4.1 MySQL Database Type Mapping

The following table summarizes the default mapping between the currently supported Qt date-time types and the MySQL database types.

@@ -21433,7 +22232,7 @@ namespace odb support for mapping QDateTime to the TIMESTAMP MySQL type. However, this mapping has to be explicitly requested using the db type pragma - (Section 12.4.3, "type"), as shown in + (Section 14.4.3, "type"), as shown in the following example:

@@ -21484,7 +22283,7 @@ class Person
      the MySQL documentation for more information on the MySQL data type
      ranges.

-

22.4.2 SQLite Database Type Mapping

+

24.4.2 SQLite Database Type Mapping

The following table summarizes the default mapping between the currently supported Qt date-time types and the SQLite database types.

@@ -21527,7 +22326,7 @@ class Person the INTEGER type represents a clock time as the number of seconds since midnight. These mappings have to be explicitly requested using the db type pragma - (Section 12.4.3, "type"), as shown + (Section 14.4.3, "type"), as shown in the following example:

@@ -21546,7 +22345,7 @@ class Person
      epoch) as an SQLite INTEGER will result in the
      out_of_range exception.

-

22.4.3 PostgreSQL Database Type Mapping

+

24.4.3 PostgreSQL Database Type Mapping

The following table summarizes the default mapping between the currently supported Qt date-time types and the PostgreSQL database types.

@@ -21582,7 +22381,7 @@ class Person QDateTime types are stored as a NULL value if their isNull() member function returns true.

-

22.4.4 Oracle Database Type Mapping

+

24.4.4 Oracle Database Type Mapping

The following table summarizes the default mapping between the currently supported Qt date-time types and the Oracle database types.

@@ -21623,7 +22422,7 @@ class Person DATE Oracle type with fractional seconds that may be stored in a QDateTime instance being ignored. This alternative mapping has to be explicitly requested using the - db type pragma (Section 12.4.3, + db type pragma (Section 14.4.3, "type"), as shown in the following example:

@@ -21636,7 +22435,7 @@ class person
 };
   
-

22.4.5 SQL Server Database Type Mapping

+

24.4.5 SQL Server Database Type Mapping

The following table summarizes the default mapping between the currently supported Qt date-time types and the SQL Server database types.

@@ -21683,7 +22482,7 @@ class person support for mapping QDateTime to the DATETIME and SMALLDATETIME types, however, this mapping has to be explicitly requested using the db type pragma - (Section 12.4.3, "type"), as + (Section 14.4.3, "type"), as shown in the following example:

diff --git a/odb/common.cxx b/odb/common.cxx
index 01df033..365fe19 100644
--- a/odb/common.cxx
+++ b/odb/common.cxx
@@ -193,6 +193,14 @@ traverse_member (semantics::data_member& m, semantics::type& t)
     traverse_simple (m);
 }
 
+bool object_members_base::
+section_test (data_member_path const& mp)
+{
+  // By default ignore members from the wrong section.
+  //
+  return section_ == 0 || *section_ == section (mp);
+}
+
 void object_members_base::member::
 traverse (semantics::data_member& m)
 {
@@ -201,14 +209,19 @@ traverse (semantics::data_member& m)
 
   om_.member_path_.push_back (&m);
 
-  semantics::type& t (utype (m));
+  // Ignore members from the wrong section.
+  //
+  if (om_.section_test (om_.member_path_))
+  {
+    semantics::type& t (utype (m));
 
-  if (semantics::type* c = context::container (m))
-    om_.traverse_container (m, *c);
-  else if (semantics::class_* c = object_pointer (t))
-    om_.traverse_pointer (m, *c);
-  else
-    om_.traverse_member (m, t);
+    if (semantics::type* c = context::container (m))
+      om_.traverse_container (m, *c);
+    else if (semantics::class_* c = object_pointer (t))
+      om_.traverse_pointer (m, *c);
+    else
+      om_.traverse_member (m, t);
+  }
 
   om_.member_path_.pop_back ();
 }
@@ -393,6 +406,14 @@ traverse_member (semantics::data_member& m, semantics::type& t)
   }
 }
 
+bool object_columns_base::
+section_test (data_member_path const& mp)
+{
+  // By default ignore members from the wrong section.
+  //
+  return section_ == 0 || *section_ == section (mp);
+}
+
 void object_columns_base::member::
 traverse (semantics::data_member& m)
 {
@@ -406,12 +427,15 @@ traverse (semantics::data_member& m)
 
   oc_.member_path_.push_back (&m);
 
-  semantics::type& t (utype (m));
+  if (oc_.section_test (oc_.member_path_))
+  {
+    semantics::type& t (utype (m));
 
-  if (semantics::class_* c = object_pointer (t))
-    oc_.traverse_pointer (m, *c);
-  else
-    oc_.traverse_member (m, t);
+    if (semantics::class_* c = object_pointer (t))
+      oc_.traverse_pointer (m, *c);
+    else
+      oc_.traverse_member (m, t);
+  }
 
   oc_.member_path_.pop_back ();
 }
diff --git a/odb/common.hxx b/odb/common.hxx
index 5ca5107..c4ecbaf 100644
--- a/odb/common.hxx
+++ b/odb/common.hxx
@@ -67,9 +67,13 @@ struct object_members_base: traversal::class_, virtual context
   virtual void
   traverse_view (semantics::class_&);
 
+  virtual bool
+  section_test (data_member_path const&);
+
 public:
-  object_members_base (bool traverse_poly_base = false)
-      : top_level_ (true), member_ (*this)
+  object_members_base (bool traverse_poly_base = false,
+                       object_section* section = 0)
+      : section_ (section), top_level_ (true), member_ (*this)
   {
     init (false, false, false, traverse_poly_base);
   }
@@ -77,8 +81,9 @@ public:
   object_members_base (bool build_flat_prefix,
                        bool build_table_prefix,
                        bool build_member_prefix,
-                       bool traverse_poly_base = false)
-      : top_level_ (true), member_ (*this)
+                       bool traverse_poly_base = false,
+                       object_section* section = 0)
+      : section_ (section), top_level_ (true), member_ (*this)
   {
     init (build_flat_prefix,
           build_table_prefix,
@@ -88,6 +93,7 @@ public:
 
   object_members_base (object_members_base const& x)
       : context (), //@@ -Wextra
+        section_ (x.section_),
         top_level_ (true),
         member_ (*this)
   {
@@ -108,6 +114,8 @@ protected:
   data_member_path member_path_;
   data_member_scope member_scope_;
 
+  object_section* section_;
+
 protected:
   semantics::data_member*
   id () const
@@ -138,10 +146,7 @@ private:
 
   struct member: traversal::data_member
   {
-    member (object_members_base& om)
-        : om_ (om)
-    {
-    }
+    member (object_members_base& om): om_ (om) {}
 
     virtual void
     traverse (semantics::data_member&);
@@ -211,10 +216,15 @@ struct object_columns_base: traversal::class_, virtual context
   virtual void
   flush ();
 
+  virtual bool
+  section_test (data_member_path const&);
+
 public:
   object_columns_base (bool first = true,
-                       column_prefix const& cp = column_prefix ())
+                       column_prefix const& cp = column_prefix (),
+                       object_section* section = 0)
       : column_prefix_ (cp),
+        section_ (section),
         root_ (0),
         traverse_poly_base_ (false),
         first_ (first),
@@ -224,8 +234,11 @@ public:
     init ();
   }
 
-  object_columns_base (bool first, bool traverse_poly_base)
-      : root_ (0),
+  object_columns_base (bool first,
+                       bool traverse_poly_base,
+                       object_section* section = 0)
+      : section_ (section),
+        root_ (0),
         traverse_poly_base_ (traverse_poly_base),
         first_ (first),
         top_level_ (true),
@@ -237,6 +250,7 @@ public:
   object_columns_base (object_columns_base const& x)
       : context (), //@@ -Wextra
         column_prefix_ (x.column_prefix_),
+        section_ (x.section_),
         root_ (0),
         traverse_poly_base_ (x.traverse_poly_base_),
         first_ (x.first_),
@@ -274,6 +288,8 @@ protected:
   data_member_path member_path_;
   data_member_scope member_scope_;
 
+  object_section* section_;
+
 protected:
   semantics::data_member*
   id () const
diff --git a/odb/context.cxx b/odb/context.cxx
index 760d653..a4609df 100644
--- a/odb/context.cxx
+++ b/odb/context.cxx
@@ -291,6 +291,179 @@ translate (string const& obj, string const& val) const
   return r;
 }
 
+// Sections.
+//
+main_section_type main_section;
+
+bool main_section_type::
+compare (object_section const& s) const
+{
+  main_section_type const* ms (dynamic_cast (&s));
+  return ms != 0 && *this == *ms;
+}
+
+bool user_section::
+compare (object_section const& s) const
+{
+  user_section const* us (dynamic_cast (&s));
+  return us != 0 && *this == *us;
+}
+
+user_section* user_section::
+total_base () const
+{
+  if (base != 0)
+  {
+    semantics::class_* poly_root (context::polymorphic (*object));
+    if (poly_root != 0 && poly_root != *object)
+      return base;
+  }
+
+  return 0;
+}
+
+size_t user_sections::
+count (unsigned short f) const
+{
+  size_t r (0);
+
+  semantics::class_* poly_root (context::polymorphic (*object));
+  bool poly_derived (poly_root != 0 && poly_root != object);
+
+  if (poly_derived && (f & count_total) != 0)
+    r = context::polymorphic_base (*object).get (
+      "user-sections").count (f);
+
+  for (const_iterator i (begin ()); i != end (); ++i)
+  {
+    // Skip special sections unless we were explicitly asked to count them.
+    //
+    if (i->special == user_section::special_version &&
+        (f & count_special_version) == 0)
+      continue;
+
+    bool ovd (i->base != 0 && poly_derived);
+
+    if (i->load != user_section::load_eager)
+    {
+      if (i->load_empty ())
+      {
+        if ((f & count_load_empty) != 0)
+        {
+          if (ovd)
+          {
+            if ((f & count_override) != 0)
+            {
+              r++;
+              continue; // Count each section only once.
+            }
+          }
+          else
+          {
+            if ((f & count_new) != 0 || (f & count_total) != 0)
+            {
+              r++;
+              continue;
+            }
+          }
+        }
+      }
+      else
+      {
+        if ((f & count_load) != 0)
+        {
+          if (ovd)
+          {
+            if ((f & count_override) != 0)
+            {
+              r++;
+              continue;
+            }
+          }
+          else
+          {
+            if ((f & count_new) != 0 || (f & count_total) != 0)
+            {
+              r++;
+              continue;
+            }
+          }
+        }
+      }
+    }
+
+    if (i->update_empty ())
+    {
+      if ((f & count_update_empty) != 0)
+      {
+        if (ovd)
+        {
+          if ((f & count_override) != 0)
+          {
+            r++;
+            continue;
+          }
+        }
+        else
+        {
+          if ((f & count_new) != 0 || (f & count_total) != 0)
+          {
+            r++;
+            continue;
+          }
+        }
+      }
+    }
+    else
+    {
+      if ((f & count_update) != 0)
+      {
+        if (ovd)
+        {
+          if ((f & count_override) != 0)
+          {
+            r++;
+            continue;
+          }
+        }
+        else
+        {
+          if ((f & count_new) != 0 || (f & count_total) != 0)
+          {
+            r++;
+            continue;
+          }
+        }
+      }
+    }
+
+    if (i->optimistic ())
+    {
+      if ((f & count_optimistic) != 0)
+      {
+        if (ovd)
+        {
+          if ((f & count_override) != 0)
+          {
+            r++;
+            continue;
+          }
+        }
+        else
+        {
+          if ((f & count_new) != 0 || (f & count_total) != 0)
+          {
+            r++;
+            continue;
+          }
+        }
+      }
+    }
+  }
+
+  return r;
+}
+
 //
 // context
 //
@@ -2181,6 +2354,11 @@ namespace
 {
   struct column_count_impl: object_members_base
   {
+    column_count_impl (object_section* section = 0)
+        : object_members_base (false, section)
+    {
+    }
+
     virtual void
     traverse_pointer (semantics::data_member& m, semantics::class_& c)
     {
@@ -2189,7 +2367,14 @@ namespace
       object_members_base::traverse_pointer (m, c);
 
       if (context::inverse (m))
-        c_.inverse += (c_.total - t);
+      {
+        size_t n (c_.total - t);
+
+        c_.inverse += n;
+
+        if (separate_update (member_path_))
+          c_.separate_update -= n;
+      }
     }
 
     virtual void
@@ -2197,9 +2382,11 @@ namespace
     {
       c_.total++;
 
+      bool ro (context::readonly (member_path_, member_scope_));
+
       if (id ())
         c_.id++;
-      else if (context::readonly (member_path_, member_scope_))
+      else if (ro)
         c_.readonly++;
       else if (context::version (m))
         c_.optimistic_managed++;
@@ -2208,6 +2395,12 @@ namespace
       //
       if (discriminator (m))
         c_.discriminator++;
+
+      if (separate_load (member_path_))
+        c_.separate_load++;
+
+      if (separate_update (member_path_) && !ro)
+        c_.separate_update++;
     }
 
     context::column_count_type c_;
@@ -2215,24 +2408,35 @@ namespace
 }
 
 context::column_count_type context::
-column_count (semantics::class_& c)
+column_count (semantics::class_& c, object_section* s)
 {
-  if (!c.count ("column-count"))
+  if (s == 0)
   {
-    column_count_impl t;
+    // Whole class.
+    //
+    if (!c.count ("column-count"))
+    {
+      column_count_impl t;
+      t.traverse (c);
+      c.set ("column-count", t.c_);
+    }
+
+    return c.get ("column-count");
+  }
+  else
+  {
+    column_count_impl t (s);
     t.traverse (c);
-    c.set ("column-count", t.c_);
+    return t.c_;
   }
-
-  return c.get ("column-count");
 }
 
 namespace
 {
   struct has_a_impl: object_members_base
   {
-    has_a_impl (unsigned short flags)
-        : object_members_base ((flags & context::include_base) != 0),
+    has_a_impl (unsigned short flags, object_section* s)
+        : object_members_base ((flags & context::include_base) != 0, s),
           r_ (0),
           flags_ (flags)
     {
@@ -2244,6 +2448,20 @@ namespace
       return r_;
     }
 
+    virtual bool
+    section_test (data_member_path const& mp)
+    {
+      object_section& s (section (mp));
+
+      // Include eager loaded members into the main section if requested.
+      //
+      return section_ == 0 ||
+        *section_ == s ||
+        ((flags_ & include_eager_load) != 0 &&
+         *section_ == main_section &&
+         !s.separate_load ());
+    }
+
     virtual void
     traverse_pointer (semantics::data_member& m, semantics::class_&)
     {
@@ -2275,6 +2493,7 @@ namespace
                                   context::test_straight_container |
                                   context::test_inverse_container |
                                   context::test_readonly_container |
+                                  context::test_readwrite_container |
                                   context::test_smart_container));
 
       if (context::is_a (member_path_,
@@ -2325,6 +2544,7 @@ is_a (data_member_path const& mp,
             test_straight_container |
             test_inverse_container |
             test_readonly_container |
+            test_readwrite_container |
             test_smart_container)) != 0 &&
       (c = container (m)) != 0)
   {
@@ -2340,6 +2560,9 @@ is_a (data_member_path const& mp,
     if (f & test_readonly_container)
       r = r || readonly (mp, ms);
 
+    if (f & test_readwrite_container)
+      r = r || (!inverse (m, kp) && !readonly (mp, ms));
+
     if (f & test_smart_container)
       r = r || (!inverse (m, kp) && !unordered (m) && container_smart (*c));
   }
@@ -2348,9 +2571,9 @@ is_a (data_member_path const& mp,
 }
 
 size_t context::
-has_a (semantics::class_& c, unsigned short flags)
+has_a (semantics::class_& c, unsigned short flags, object_section* s)
 {
-  has_a_impl impl (flags);
+  has_a_impl impl (flags, s);
   impl.dispatch (c);
   return impl.result ();
 }
diff --git a/odb/context.hxx b/odb/context.hxx
index 306d2e4..862b74e 100644
--- a/odb/context.hxx
+++ b/odb/context.hxx
@@ -9,6 +9,7 @@
 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -267,6 +268,236 @@ struct model_version
   bool open;
 };
 
+// Sections.
+//
+struct object_section
+{
+  virtual bool
+  compare (object_section const&) const = 0;
+
+  virtual bool
+  separate_load () const = 0;
+
+  virtual bool
+  separate_update () const = 0;
+};
+
+inline bool
+operator== (object_section const& x, object_section const& y)
+{
+  return x.compare (y);
+}
+
+inline bool
+operator!= (object_section const& x, object_section const& y)
+{
+  return !x.compare (y);
+}
+
+// Main section.
+//
+struct main_section_type: object_section
+{
+  virtual bool
+  compare (object_section const& s) const;
+
+  virtual bool
+  separate_load () const {return false;}
+
+  virtual bool
+  separate_update () const {return false;}
+};
+
+inline bool
+operator== (main_section_type const&, main_section_type const&)
+{
+  return true; // There is only one main section.
+}
+
+extern main_section_type main_section;
+
+// User-defined section.
+//
+struct user_section: object_section
+{
+  enum load_type
+  {
+    load_eager,
+    load_lazy
+  };
+
+  enum update_type
+  {
+    update_always,
+    update_change,
+    update_manual
+  };
+
+  enum special_type
+  {
+    special_ordinary,
+    special_version  // Fake section for optimistic version update.
+  };
+
+  user_section (semantics::data_member& m,
+                semantics::class_& o,
+                std::size_t i,
+                load_type l,
+                update_type u,
+                special_type s = special_ordinary)
+      : member (&m), object (&o), base (0), index (i),
+        load (l), update (u), special (s),
+        total (0), inverse (0), readonly (0),
+        containers (false), readwrite_containers (false) {}
+
+  virtual bool
+  compare (object_section const& s) const;
+
+  virtual bool
+  separate_load () const {return load != load_eager;}
+
+  virtual bool
+  separate_update () const
+  {
+    // A separately-loaded section is always separately-updated since
+    // it might not be loaded when update is requested.
+    //
+    return separate_load () || update != update_always;
+  }
+
+  bool
+  load_empty () const;
+
+  bool
+  update_empty () const;
+
+  bool
+  empty () const
+  {
+    return load_empty () && update_empty ();
+  }
+
+  // A section is optimistic if the object that contains it is optimistic.
+  // For polymorphic hierarchies, only sections contained in the root are
+  // considered optimistic.
+  //
+  bool
+  optimistic () const;
+
+  semantics::data_member* member; // Data member of this section.
+  semantics::class_* object;      // Object containing this section.
+  user_section* base;             // Base of this section.
+  std::size_t index;              // Index of this sections.
+
+  load_type load;
+  update_type update;
+  special_type special;
+
+  // Column counts.
+  //
+  std::size_t total;
+  std::size_t inverse;
+  std::size_t readonly;
+
+  bool containers;
+  bool readwrite_containers;
+
+  // Total counts across overrides.
+  //
+  std::size_t
+  total_total () const
+  {
+    user_section* b (total_base ());
+    return total + (b == 0 ? 0 : b->total_total ());
+  }
+
+  std::size_t
+  total_inverse () const
+  {
+    user_section* b (total_base ());
+    return inverse + (b == 0 ? 0 : b->total_inverse ());
+  }
+
+  std::size_t
+  total_readonly () const
+  {
+    user_section* b (total_base ());
+    return readonly + (b == 0 ? 0 : b->total_readonly ());
+  }
+
+  bool
+  total_containers ()
+  {
+    user_section* b (total_base ());
+    return containers || (b != 0 && b->total_containers ());
+  }
+
+  bool
+  total_readwrite_containers ()
+  {
+    user_section* b (total_base ());
+    return readwrite_containers ||
+      (b != 0 && b->total_readwrite_containers ());
+  }
+
+private:
+  user_section*
+  total_base () const;
+};
+
+inline bool
+operator== (user_section const& x, user_section const& y)
+{
+  return x.member == y.member;
+}
+
+// Using list for pointer for element stability (see user_section::base).
+//
+struct user_sections: std::list
+{
+  // Count sections that have something to load.
+  //
+  static unsigned short const count_load         = 0x01;
+
+  // Count sections that are non-eager but have nothing to load.
+  //
+  static unsigned short const count_load_empty   = 0x02;
+
+  // Count sections that have something to update.
+  //
+  static unsigned short const count_update       = 0x04;
+
+  // Count sections that have nothing to update.
+  //
+  static unsigned short const count_update_empty = 0x08;
+
+  // Count sections that are optimistic.
+  //
+  static unsigned short const count_optimistic = 0x10;
+
+  // Don't exclude fake optimistic version update section from the count.
+  //
+  static unsigned short const count_special_version = 0x20;
+
+  // Count all sections, including special.
+  //
+  static unsigned short const count_all = count_update       |
+                                          count_update_empty |
+                                          count_special_version;
+
+  static unsigned short const count_new      = 0x1000;
+  static unsigned short const count_override = 0x2000;
+  static unsigned short const count_total    = 0x4000;
+
+  std::size_t
+  count (unsigned short flags) const;
+
+  user_sections (semantics::class_& o): object (&o) {};
+  semantics::class_* object;
+};
+
+// Context.
+//
 class context
 {
 public:
@@ -555,6 +786,12 @@ public:
     return m.count ("version");
   }
 
+  static bool
+  version (const data_member_path& mp)
+  {
+    return mp.size () == 1 && mp.back ()->count ("version");
+  }
+
   // Polymorphic inheritance. Return root of the hierarchy or NULL if
   // not polymorphic.
   //
@@ -605,6 +842,51 @@ public:
     return unit.get ("model-version");
   }
 
+  // Object sections.
+  //
+  static object_section&
+  section (semantics::data_member& m)
+  {
+    object_section* s (m.get ("section", 0));
+    return s == 0 ? main_section : *s;
+  }
+
+  static object_section&
+  section (data_member_path const& mp)
+  {
+    // The direct member of the object specifies the section.
+    //
+    return section (*mp.front ());
+  }
+
+  // Member belongs to a section that is loaded separately.
+  //
+  static bool
+  separate_load (semantics::data_member& m)
+  {
+    return section (m).separate_load ();
+  }
+
+  static bool
+  separate_load (data_member_path const& mp)
+  {
+    return section (mp).separate_load ();
+  }
+
+  // Member belongs to a section that is updated separately.
+  //
+  static bool
+  separate_update (semantics::data_member& m)
+  {
+    return section (m).separate_update ();
+  }
+
+  static bool
+  separate_update (data_member_path const& mp)
+  {
+    return section (mp).separate_update ();
+  }
+
   //
   //
   typedef ::class_kind class_kind_type;
@@ -813,7 +1095,9 @@ public:
           inverse (0),
           readonly (0),
           optimistic_managed (0),
-          discriminator (0)
+          discriminator (0),
+          separate_load (0),
+          separate_update (0)
     {
     }
 
@@ -823,10 +1107,13 @@ public:
     size_t readonly;
     size_t optimistic_managed;
     size_t discriminator;
+
+    size_t separate_load;
+    size_t separate_update; // Only readwrite.
   };
 
   static column_count_type
-  column_count (semantics::class_&);
+  column_count (semantics::class_&, object_section* = 0);
 
   static semantics::data_member*
   id_member (semantics::class_& c)
@@ -932,7 +1219,7 @@ public:
     return false;
   }
 
-  // The 'is a' and 'has a' tests. The has_a test currently does not
+  // The 'is a' and 'has a' tests. The has_a() test currently does not
   // cross the container boundaries.
   //
 public:
@@ -943,7 +1230,13 @@ public:
   static unsigned short const test_straight_container = 0x10;
   static unsigned short const test_inverse_container = 0x20;
   static unsigned short const test_readonly_container = 0x40;
-  static unsigned short const test_smart_container = 0x80;
+  static unsigned short const test_readwrite_container = 0x80;
+  static unsigned short const test_smart_container = 0x100;
+
+  // Treat eager loaded members as belonging to the main section.
+  // If this flag is specified, then section must be main_section.
+  //
+  static unsigned short const include_eager_load = 0x2000;
 
   // By default the test goes into bases for non-polymorphic
   // hierarchies and doesn't go for polymorphic. The following
@@ -971,7 +1264,7 @@ public:
   // a bool value (0 means no match).
   //
   size_t
-  has_a (semantics::class_&, unsigned short flags);
+  has_a (semantics::class_&, unsigned short flags, object_section* = 0);
 
 public:
   // Process include path by adding the prefix, putting it through
@@ -1193,4 +1486,6 @@ has (Y& y)
   return false;
 }
 
+#include 
+
 #endif // ODB_CONTEXT_HXX
diff --git a/odb/context.ixx b/odb/context.ixx
new file mode 100644
index 0000000..285d364
--- /dev/null
+++ b/odb/context.ixx
@@ -0,0 +1,27 @@
+// file      : odb/context.ixx
+// copyright : Copyright (c) 2009-2013 Code Synthesis Tools CC
+// license   : GNU GPL v3; see accompanying LICENSE file
+
+inline bool user_section::
+load_empty () const
+{
+  return !separate_load () || (total == 0 && !containers && !optimistic ());
+}
+
+inline bool user_section::
+update_empty () const
+{
+  return total == inverse + readonly && !readwrite_containers;
+}
+
+inline bool user_section::
+optimistic () const
+{
+  if (!context::optimistic (*object))
+    return false;
+  else
+  {
+    semantics::class_* poly_root (context::polymorphic (*object));
+    return poly_root == 0 || poly_root == object;
+  }
+}
diff --git a/odb/features.hxx b/odb/features.hxx
index bc3a9c8..e7c1cdb 100644
--- a/odb/features.hxx
+++ b/odb/features.hxx
@@ -19,6 +19,7 @@ struct features
   bool polymorphic_object;
   bool no_id_object;
   bool session_object;
+  bool section;
   bool view;
 };
 
diff --git a/odb/header.cxx b/odb/header.cxx
index 9af82c8..f09c1e8 100644
--- a/odb/header.cxx
+++ b/odb/header.cxx
@@ -60,6 +60,8 @@ traverse_object (type& c)
   bool abst (abstract (c));
   bool reuse_abst (abst && !poly);
 
+  user_sections& uss (c.get ("user-sections"));
+
   string const& type (class_fq_name (c));
 
   os << "// " << class_name (c) << endl
@@ -384,27 +386,37 @@ traverse_object (type& c)
 
     os << "void (*erase2) (database&, const object_type&" <<
       (poly ? ", bool, bool" : "") << ");";
+
+    // Sections.
+    //
+    if (uss.count (user_sections::count_total |
+                   user_sections::count_load  |
+                   (poly ? user_sections::count_load_empty : 0)) != 0)
+      os << "bool (*load_section) (connection&, object_type&, section&" <<
+        (poly ? ", const info_type*" : "") << ");";
+
+    if (uss.count (user_sections::count_total  |
+                   user_sections::count_update |
+                   (poly ? user_sections::count_update_empty : 0)) != 0)
+      os << "bool (*update_section) (connection&, const object_type&, " <<
+        "const section&" << (poly ? ", const info_type*" : "") << ");";
   }
 
   if (options.generate_query ())
   {
     if (!options.omit_unprepared ())
-      os << "result (*query) (database&, const query_base_type&);"
-         << endl;
+      os << "result (*query) (database&, const query_base_type&);";
 
     os << "unsigned long long (*erase_query) (database&, " <<
-      "const query_base_type&);"
-       << endl;
+      "const query_base_type&);";
 
     if (options.generate_prepared ())
     {
       os << "odb::details::shared_ptr " <<
-        "(*prepare_query) (connection&, const char*, const query_base_type&);"
-         << endl;
+        "(*prepare_query) (connection&, const char*, const query_base_type&);";
 
       os << "odb::details::shared_ptr (*execute_query) ("
-        "prepared_query_impl&);"
-         << endl;
+        "prepared_query_impl&);";
     }
   }
 
@@ -461,6 +473,22 @@ traverse_object (type& c)
     os << "static void" << endl
        << "erase (database&, const object_type&);"
        << endl;
+
+    // Sections.
+    //
+    if (uss.count (user_sections::count_total |
+                   user_sections::count_load  |
+                   (poly ? user_sections::count_load_empty : 0)) != 0)
+      os << "static bool" << endl
+         << "load (connection&, object_type&, section&);"
+         << endl;
+
+    if (uss.count (user_sections::count_total  |
+                   user_sections::count_update |
+                   (poly ? user_sections::count_update_empty : 0)) != 0)
+      os << "static bool" << endl
+         << "update (connection&, const object_type&, const section&);"
+         << endl;
   }
 
   if (options.generate_query ())
diff --git a/odb/inline.cxx b/odb/inline.cxx
index d6b8c32..036a728 100644
--- a/odb/inline.cxx
+++ b/odb/inline.cxx
@@ -107,6 +107,8 @@ traverse_object (type& c)
   bool abst (abstract (c));
   bool reuse_abst (abst && !poly);
 
+  user_sections& uss (c.get ("user-sections"));
+
   string const& type (class_fq_name (c));
   string traits ("access::object_traits< " + type + " >");
 
@@ -269,6 +271,30 @@ traverse_object (type& c)
        << "function_table[db.id ()]->erase2 (db, o" <<
       (poly ? ", true, true" : "") << ");"
        << "}";
+
+    // Sections.
+    //
+    if (uss.count (user_sections::count_total |
+                   user_sections::count_load  |
+                   (poly ? user_sections::count_load_empty : 0)) != 0)
+      os << "inline" << endl
+         << "bool " << traits << "::" << endl
+         << "load (connection& c, object_type& o, section& s)"
+         << "{"
+         << "return function_table[c.database ().id ()]->load_section (" <<
+        "c, o, s" << (poly ? ", 0" : "") << ");"
+         << "}";
+
+    if (uss.count (user_sections::count_total  |
+                   user_sections::count_update |
+                   (poly ? user_sections::count_update_empty : 0)) != 0)
+      os << "inline" << endl
+         << "bool " << traits << "::" << endl
+         << "update (connection& c, const object_type& o, const section& s)"
+         << "{"
+         << "return function_table[c.database ().id ()]->update_section (" <<
+        "c, o, s" << (poly ? ", 0" : "") << ");"
+         << "}";
   }
 
   if (options.generate_query ())
diff --git a/odb/pragma.cxx b/odb/pragma.cxx
index 13ebeb0..2b4dc3d 100644
--- a/odb/pragma.cxx
+++ b/odb/pragma.cxx
@@ -386,6 +386,9 @@ check_spec_decl_type (declaration const& d,
            p == "auto"      ||
            p == "column"    ||
            p == "inverse"   ||
+           p == "section"   ||
+           p == "load"      ||
+           p == "update"    ||
            p == "version"   ||
            p == "index"     ||
            p == "unique"    ||
@@ -430,7 +433,8 @@ check_spec_decl_type (declaration const& d,
            p == "object" ||
            p == "optimistic" ||
            p == "polymorphic" ||
-           p == "definition")
+           p == "definition" ||
+           p == "sectionable")
   {
     if (tc != RECORD_TYPE)
     {
@@ -1405,6 +1409,18 @@ handle_pragma (cxx_lexer& l,
     val = l.location ();
     tt = l.next (tl, &tn);
   }
+  else if (p == "sectionable")
+  {
+    // sectionable
+    //
+
+    // Make sure we've got the correct declaration type.
+    //
+    if (decl && !check_spec_decl_type (decl, decl_name, p, loc))
+      return;
+
+    tt = l.next (tl, &tn);
+  }
   else if (p == "callback")
   {
     // callback (name)
@@ -2112,6 +2128,121 @@ handle_pragma (cxx_lexer& l,
 
     tt = l.next (tl, &tn);
   }
+  else if (p == "section")
+  {
+    // section (name)
+    //
+
+    // Make sure we've got the correct declaration type.
+    //
+    if (decl && !check_spec_decl_type (decl, decl_name, p, loc))
+      return;
+
+    if (l.next (tl, &tn) != CPP_OPEN_PAREN)
+    {
+      error (l) << "'(' expected after db pragma " << p << endl;
+      return;
+    }
+
+    tt = l.next (tl, &tn);
+
+    if (tt != CPP_NAME)
+    {
+      error (l) << "member name expected in db pragma " << p << endl;
+      return;
+    }
+
+    name = "section-member";
+    val = tl;
+
+    if (l.next (tl, &tn) != CPP_CLOSE_PAREN)
+    {
+      error (l) << "')' expected at the end of db pragma " << p << endl;
+      return;
+    }
+
+    tt = l.next (tl, &tn);
+  }
+  else if (p == "load")
+  {
+    // load (eager|lazy)
+    //
+
+    // Make sure we've got the correct declaration type.
+    //
+    if (decl && !check_spec_decl_type (decl, decl_name, p, loc))
+      return;
+
+    if (l.next (tl, &tn) != CPP_OPEN_PAREN)
+    {
+      error (l) << "'(' expected after db pragma " << p << endl;
+      return;
+    }
+
+    tt = l.next (tl, &tn);
+
+    if (tt != CPP_NAME || (tl != "eager" && tl != "lazy"))
+    {
+      error (l) << "eager or lazy expected in db pragma " << p << endl;
+      return;
+    }
+
+    name = "section-load";
+    val = (tl == "eager"
+           ? user_section::load_eager
+           : user_section::load_lazy);
+
+    if (l.next (tl, &tn) != CPP_CLOSE_PAREN)
+    {
+      error (l) << "')' expected at the end of db pragma " << p << endl;
+      return;
+    }
+
+    tt = l.next (tl, &tn);
+  }
+  else if (p == "update")
+  {
+    // update (always|change|manual)
+    //
+
+    // Make sure we've got the correct declaration type.
+    //
+    if (decl && !check_spec_decl_type (decl, decl_name, p, loc))
+      return;
+
+    if (l.next (tl, &tn) != CPP_OPEN_PAREN)
+    {
+      error (l) << "'(' expected after db pragma " << p << endl;
+      return;
+    }
+
+    tt = l.next (tl, &tn);
+
+    if (tt != CPP_NAME ||
+        (tl != "always" && tl != "change" && tl != "manual"))
+    {
+      error (l) << "always, change, or manual expected in db pragma " <<
+        p << endl;
+      return;
+    }
+
+    name = "section-update";
+
+    if (tl == "always")
+      val = user_section::update_always;
+    else if (tl == "change")
+      val = user_section::update_change;
+    else
+      val = user_section::update_manual;
+
+    if (l.next (tl, &tn) != CPP_CLOSE_PAREN)
+    {
+      error (l) << "')' expected at the end of db pragma " << p << endl;
+      return;
+    }
+
+    tt = l.next (tl, &tn);
+  }
   else if (p == "unordered")
   {
     // unordered
@@ -3055,6 +3186,9 @@ handle_pragma_qualifier (cxx_lexer& l, string p)
            p == "value_null" ||
            p == "value_not_null" ||
            p == "default" ||
+           p == "section" ||
+           p == "load"    ||
+           p == "update"  ||
            p == "inverse" ||
            p == "unordered" ||
            p == "readonly" ||
@@ -3400,6 +3534,24 @@ handle_pragma_db_default (cpp_reader* r)
 }
 
 extern "C" void
+handle_pragma_db_section (cpp_reader* r)
+{
+  handle_pragma_qualifier (r, "section");
+}
+
+extern "C" void
+handle_pragma_db_load (cpp_reader* r)
+{
+  handle_pragma_qualifier (r, "load");
+}
+
+extern "C" void
+handle_pragma_db_update (cpp_reader* r)
+{
+  handle_pragma_qualifier (r, "update");
+}
+
+extern "C" void
 handle_pragma_db_inverse (cpp_reader* r)
 {
   handle_pragma_qualifier (r, "inverse");
@@ -3503,6 +3655,9 @@ register_odb_pragmas (void*, void*)
   c_register_pragma_with_expansion ("db", "value_null", handle_pragma_db_value_null);
   c_register_pragma_with_expansion ("db", "value_not_null", handle_pragma_db_value_not_null);
   c_register_pragma_with_expansion ("db", "default", handle_pragma_db_default);
+  c_register_pragma_with_expansion ("db", "section", handle_pragma_db_section);
+  c_register_pragma_with_expansion ("db", "load", handle_pragma_db_load);
+  c_register_pragma_with_expansion ("db", "update", handle_pragma_db_update);
   c_register_pragma_with_expansion ("db", "inverse", handle_pragma_db_inverse);
   c_register_pragma_with_expansion ("db", "unordered", handle_pragma_db_unordered);
   c_register_pragma_with_expansion ("db", "readonly", handle_pragma_db_readonly);
diff --git a/odb/processor.cxx b/odb/processor.cxx
index 4e50efe..bc6c408 100644
--- a/odb/processor.cxx
+++ b/odb/processor.cxx
@@ -5,6 +5,7 @@
 #include 
 
 #include 
+#include  // std::find
 
 #include 
 #include 
@@ -37,8 +38,65 @@ namespace
       if (transient (m))
         return;
 
-      process_access (m, "get");
-      process_access (m, "set");
+      semantics::names* hint;
+      semantics::type& t (utype (m, hint));
+
+      // See if this member is a section.
+      //
+      if (t.fq_name () == "::odb::section")
+      {
+        using semantics::class_;
+
+        class_& c (dynamic_cast (m.scope ()));
+        class_* poly_root (polymorphic (c));
+        semantics::data_member* opt (optimistic (c));
+
+        // If we have sections in a polymorphic optimistic hierarchy,
+        // then the version member should be in the root.
+        //
+        if (poly_root == &c && opt != 0 && &opt->scope () != &c)
+        {
+          error (m.location ()) << "version must be a direct data member " <<
+            "of a class that contains sections" << endl;
+          info (opt->location ()) << "version member is declared here" << endl;
+          throw operation_failed ();
+        }
+
+        process_user_section (m, c);
+
+        // We don't need a modifier but the accessor should be by-reference.
+        //
+        process_access (m, "get");
+
+        member_access& ma (m.get ("get"));
+        if (ma.by_value)
+        {
+          error (ma.loc) << "accessor returning a value cannot be used "
+                         << "for a section" << endl;
+          info (ma.loc) << "accessor returning a const reference is required"
+                        << endl;
+          info (m.location ()) << "data member is defined here" << endl;
+          throw operation_failed ();
+        }
+
+        // Mark this member as transient since we don't store it in the
+        // database.
+        //
+        m.set ("transient", true);
+
+        features.section = true;
+        return;
+      }
+      else
+      {
+        process_access (m, "get");
+        process_access (m, "set");
+      }
+
+      // See if this member belongs to a section.
+      //
+      if (m.count ("section-member") != 0)
+        process_section_member (m);
 
       // We don't need to do any further processing for common if we
       // are generating static multi-database code.
@@ -46,9 +104,6 @@ namespace
       if (multi_static && options.database ()[0] == database::common)
         return;
 
-      semantics::names* hint;
-      semantics::type& t (utype (m, hint));
-
       // Handle wrappers.
       //
       semantics::type* wt (0), *qwt (0);
@@ -587,6 +642,182 @@ namespace
     }
 
     //
+    // Process section.
+    //
+
+    user_section&
+    process_user_section (semantics::data_member& m, semantics::class_& c)
+    {
+      user_sections& uss (c.get ("user-sections"));
+
+      user_section::load_type l (
+        m.get ("section-load", user_section::load_eager));
+
+      user_section::update_type u (
+        m.get ("section-update", user_section::update_always));
+
+      if (l == user_section::load_eager && u == user_section::update_always)
+      {
+        location const& l (m.location ());
+
+        error (l) << "eager-loaded, always-updated section is pointless"
+                  << endl;
+
+        info (l) << "use '#pragma db load' and/or '#pragma db update' to "
+          "specify an alternative loading and/or updating strategy" << endl;
+
+        info (l) << "or remove the section altogether" << endl;
+
+        throw operation_failed ();
+      }
+
+      size_t n (uss.count (user_sections::count_total |
+                           user_sections::count_all));
+      user_section us (m, c, n, l, u);
+
+      // We may already have seen this section (e.g., forward reference
+      // from a member of this section).
+      //
+      user_sections::iterator i (find (uss.begin (), uss.end (), us));
+
+      if (i != uss.end ())
+        return *i;
+
+      // If we are adding a new section to an optimistic class with
+      // version in a base, make sure the base is sectionable.
+      //
+      semantics::data_member* opt (optimistic (c));
+      if (opt != 0 && &opt->scope () != &c)
+      {
+        semantics::class_* poly_root (polymorphic (c));
+        semantics::node* base (poly_root ? poly_root : &opt->scope ());
+
+        if (!base->count ("sectionable"))
+        {
+          error (m.location ()) << "adding new section to a derived class " <<
+            "in an optimistic hierarchy requires sectionable base class" <<
+            endl;
+
+          info (base->location ()) << "use '#pragma db object sectionable' " <<
+            "to make the base class of this hierarchy sectionable" << endl;
+
+          throw operation_failed ();
+        }
+      }
+
+      uss.push_back (us);
+      return uss.back ();
+    }
+
+    void
+    process_section_member (semantics::data_member& m)
+    {
+      using semantics::class_;
+      using semantics::data_member;
+
+      string name (m.get ("section-member"));
+      location_t loc (m.get ("section-member-location"));
+      class_& c (dynamic_cast (m.scope ()));
+
+      class_* poly_root (polymorphic (c));
+      bool poly_derived (poly_root != 0 && poly_root != &c);
+
+      try
+      {
+        data_member& us (c.lookup (name, class_::include_hidden));
+
+        // Make sure we are referencing a section.
+        //
+        if (utype (us).fq_name () != "::odb::section")
+        {
+          error (loc) << "data member '" << name << "' in '#pragma db " <<
+            "section' is not of the odb::section type" << endl;
+          throw operation_failed ();
+        }
+
+        // If the section is in the base, handle polymorphic inheritance.
+        //
+        class_& b (dynamic_cast (us.scope ()));
+        object_section* s (0);
+
+        if (&c != &b && poly_derived)
+        {
+          user_sections& uss (c.get ("user-sections"));
+
+          // This is a section override. See if we have already handled
+          // this section.
+          //
+          for (user_sections::iterator i (uss.begin ());
+               s == 0 && i != uss.end ();
+               ++i)
+          {
+            if (i->member == &us)
+              s = &*i;
+          }
+
+          // Otherwise, find and copy the nearest override in the base.
+          // The result should be a chain of overrides leading all the
+          // way to the original section.
+          //
+          if (s == 0)
+          {
+            for (class_* b (&polymorphic_base (c));;
+                 b = &polymorphic_base (*b))
+            {
+              user_sections& buss (b->get ("user-sections"));
+
+              for (user_sections::iterator i (buss.begin ());
+                   s == 0 && i != buss.end ();
+                   ++i)
+              {
+                if (i->member == &us)
+                {
+                  uss.push_back (*i);
+                  uss.back ().object = &c;
+                  uss.back ().base = &*i;
+                  s = &uss.back ();
+                }
+              }
+
+              if (s != 0)
+                break;
+
+              assert (b != poly_root); // We should have found it by now.
+            }
+          }
+        }
+        else
+          s = &process_user_section (us, c);
+
+        m.set ("section", s); // Insert as object_section.
+      }
+      catch (semantics::unresolved const& e)
+      {
+        if (e.type_mismatch)
+          error (loc) << "name '" << name << "' in '#pragma db section' " <<
+            "does not refer to a data member" << endl;
+        else
+          error (loc) << "unable to resolve data member '" << name << "' " <<
+            "specified with '#pragma db section'" << endl;
+
+        throw operation_failed ();
+      }
+      catch (semantics::ambiguous const& e)
+      {
+        error (loc) << "data member name '" << name << "' specified " <<
+          "with '#pragma db section' is ambiguous" << endl;
+
+        info (e.first.named ().location ()) << "could resolve to this " <<
+          "data member" << endl;
+
+        info (e.second.named ().location ()) << "or could resolve to " <<
+          "this data member" << endl;
+
+        throw operation_failed ();
+      }
+    }
+
+    //
     // Process wrapper.
     //
 
@@ -1040,10 +1271,28 @@ namespace
           data_member& im (
             c->lookup (name, class_::include_hidden));
 
+          if (im.count ("transient"))
+          {
+            error (loc) << "data member '" << name << "' specified with " <<
+              "'#pragma db inverse' is transient" << endl;
+            info (im.location ()) << "data member '" << name << "' is " <<
+              "defined here" << endl;
+            throw operation_failed ();
+          }
+
+          if (im.count ("inverse") || im.count ("value-inverse"))
+          {
+            error (loc) << "data member '" << name << "' specified with " <<
+              "'#pragma db inverse' is inverse" << endl;
+            info (im.location ()) << "data member '" << name << "' is " <<
+              "defined here" << endl;
+            throw operation_failed ();
+          }
+
           // @@ Would be good to check that the other end is actually
-          // an object pointer, is not marked as transient or inverse,
-          // and points to the correct object. But the other class may
-          // not have been processed yet.
+          // an object pointer and points to the correct object. But
+          // the other class may not have been processed yet. Need to
+          // do in validator, pass 2.
           //
           m.remove ("inverse");
           m.set (kp + (kp.empty () ? "": "-") + "inverse", &im);
@@ -1413,6 +1662,8 @@ namespace
       //
       m.set ("id-tree-type", &id_tree_type);
 
+      // Has to be first to handle inverse.
+      //
       process_container_value (*vt, m, "value", true);
 
       if (it != 0)
@@ -1643,11 +1894,14 @@ namespace
         assign_pointer (c);
 
       if (k == class_object)
-        traverse_object (c);
+        traverse_object_pre (c);
       else if (k == class_view)
         traverse_view (c);
 
       names (c);
+
+      if (k == class_object)
+        traverse_object_post (c);
     }
 
     //
@@ -1655,10 +1909,45 @@ namespace
     //
 
     virtual void
-    traverse_object (type& c)
+    traverse_object_pre (type& c)
     {
       semantics::class_* poly_root (polymorphic (c));
 
+      // Sections.
+      //
+      user_sections& uss (c.set ("user-sections", user_sections (c)));
+
+      // Copy sections from reuse bases. For polymorphic classes, sections
+      // are overridden.
+      //
+      if (poly_root == 0 || poly_root == &c)
+      {
+        for (type::inherits_iterator i (c.inherits_begin ());
+             i != c.inherits_end (); ++i)
+        {
+          type& b (i->base ());
+
+          if (object (b))
+          {
+            user_sections& buss (b.get ("user-sections"));
+
+            for (user_sections::iterator j (buss.begin ());
+                 j != buss.end ();
+                 ++j)
+            {
+              // Don't copy the special version update section.
+              //
+              if (j->special == user_section::special_version)
+                continue;
+
+              uss.push_back (*j);
+              uss.back ().object = &c;
+              uss.back ().base = &*j;
+            }
+          }
+        }
+      }
+
       // Determine whether it is a session object.
       //
       if (!c.count ("session"))
@@ -1792,6 +2081,69 @@ namespace
       }
     }
 
+    virtual void
+    traverse_object_post (type& c)
+    {
+      semantics::class_* poly_root (polymorphic (c));
+      semantics::data_member* opt (optimistic (c));
+
+      // Sections.
+      //
+      user_sections& uss (c.get ("user-sections"));
+
+      // See if we need to add a special fake section for version update.
+      //
+      if (c.count ("sectionable"))
+      {
+        uss.push_back (
+          user_section (*opt,
+                        c,
+                        uss.count (user_sections::count_total |
+                                   user_sections::count_all),
+                        user_section::load_lazy,
+                        user_section::update_manual,
+                        user_section::special_version));
+
+        // If we are a root of a polymorphic hierarchy and the version is in
+        // a reuse-base, then we need to make sure that base is sectionable
+        // and derive from its special version update section.
+        //
+        semantics::node& opt_base (opt->scope ());
+        if (poly_root == &c && &opt_base != &c)
+        {
+          if (!opt_base.count ("sectionable"))
+          {
+            location_t l (c.get ("sectionable-location"));
+
+            error (l) << "reuse base class of a sectionable polymorphic " <<
+              "root class must be sectionable" << endl;
+
+            info (opt_base.location ()) << "use '#pragma db object " <<
+              "sectionable' to make the base class of this hierarchy " <<
+              "sectionable" << endl;
+
+            throw operation_failed ();
+          }
+
+          uss.back ().base =
+            &opt_base.get ("user-sections").back ();
+        }
+      }
+
+      // Calculate column counts for sections.
+      //
+      for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i)
+      {
+        column_count_type cc (column_count (c, &*i));
+        i->total = cc.total;
+        i->inverse = cc.inverse;
+        i->readonly = cc.readonly;
+
+        if ((i->containers = has_a (c, test_container, &*i)))
+          i->readwrite_containers = has_a (c, test_readwrite_container, &*i);
+      }
+    }
+
     //
     // View.
     //
diff --git a/odb/relational/common.hxx b/odb/relational/common.hxx
index 1e6a319..abe5e31 100644
--- a/odb/relational/common.hxx
+++ b/odb/relational/common.hxx
@@ -19,21 +19,25 @@ namespace relational
 
     member_base (semantics::type* type,
                  string const& fq_type,
-                 string const& key_prefix)
+                 string const& key_prefix,
+                 object_section* section = 0)
         : type_override_ (type),
           fq_type_override_ (fq_type),
-          key_prefix_ (key_prefix)
+          key_prefix_ (key_prefix),
+          section_ (section)
     {
     }
 
     member_base (string const& var,
                  semantics::type* type,
                  string const& fq_type,
-                 string const& key_prefix)
+                 string const& key_prefix,
+                 object_section* section = 0)
         : var_override_ (var),
           type_override_ (type),
           fq_type_override_ (fq_type),
-          key_prefix_ (key_prefix)
+          key_prefix_ (key_prefix),
+          section_ (section)
     {
     }
 
@@ -47,6 +51,7 @@ namespace relational
     semantics::type* type_override_;
     string fq_type_override_;
     string key_prefix_;
+    object_section* section_;
   };
 
   // Template argument is the database SQL type (sql_type).
diff --git a/odb/relational/context.cxx b/odb/relational/context.cxx
index c8c0ae2..b525802 100644
--- a/odb/relational/context.cxx
+++ b/odb/relational/context.cxx
@@ -103,7 +103,7 @@ namespace relational
   }
 
   bool context::
-  grow_impl (semantics::class_&)
+  grow_impl (semantics::class_&, user_section*)
   {
     return false;
   }
diff --git a/odb/relational/context.hxx b/odb/relational/context.hxx
index 02c4b9c..d0211c6 100644
--- a/odb/relational/context.hxx
+++ b/odb/relational/context.hxx
@@ -75,10 +75,12 @@ namespace relational
   {
   public:
     // Return true if an object or value type has members for which
-    // the image can grow.
+    // the image can grow. If section is not specified, then ignore
+    // separately loaded members. Otherwise ignore members that do
+    // not belong to the section.
     //
     bool
-    grow (semantics::class_&);
+    grow (semantics::class_&, user_section* = 0);
 
     // The same for a member's value type.
     //
@@ -206,7 +208,7 @@ namespace relational
     // The default implementation returns false.
     //
     virtual bool
-    grow_impl (semantics::class_&);
+    grow_impl (semantics::class_&, user_section*);
 
     virtual bool
     grow_impl (semantics::data_member&);
diff --git a/odb/relational/context.ixx b/odb/relational/context.ixx
index afc93da..1f959de 100644
--- a/odb/relational/context.ixx
+++ b/odb/relational/context.ixx
@@ -5,9 +5,9 @@
 namespace relational
 {
   inline bool context::
-  grow (semantics::class_& c)
+  grow (semantics::class_& c, user_section* s)
   {
-    return current ().grow_impl (c);
+    return current ().grow_impl (c, s);
   }
 
   inline bool context::
diff --git a/odb/relational/header.cxx b/odb/relational/header.cxx
index 04a73a2..da7f80c 100644
--- a/odb/relational/header.cxx
+++ b/odb/relational/header.cxx
@@ -29,6 +29,10 @@ traverse_object (type& c)
   string const& type (class_fq_name (c));
   column_count_type const& cc (column_count (c));
 
+  // Sections.
+  //
+  user_sections& uss (c.get ("user-sections"));
+
   os << "// " << class_name (c) << endl
      << "//" << endl;
 
@@ -109,13 +113,18 @@ traverse_object (type& c)
   {
     if (base_id)
     {
-      semantics::class_& b (
-        dynamic_cast (id->scope ()));
-      string const& type ();
+      if (poly_derived)
+        os << "typedef root_traits::id_image_type id_image_type;"
+           << endl;
+      else
+      {
+        semantics::class_& b (
+          dynamic_cast (id->scope ()));
 
-      os << "typedef object_traits_impl< " << class_fq_name (b) << ", " <<
-        "id_" << db << " >::id_image_type id_image_type;"
-         << endl;
+        os << "typedef object_traits_impl< " << class_fq_name (b) << ", " <<
+          "id_" << db << " >::id_image_type id_image_type;"
+           << endl;
+      }
     }
     else
     {
@@ -147,6 +156,12 @@ traverse_object (type& c)
   //
   image_type_->traverse (c);
 
+  // Extra (container, section) statement cache (forward declaration).
+  //
+  if (!reuse_abst && id != 0)
+    os << "struct extra_statement_cache_type;"
+       << endl;
+
   //
   // Containers (abstract and concrete).
   //
@@ -157,6 +172,16 @@ traverse_object (type& c)
   }
 
   //
+  // Sections (abstract and concrete).
+  //
+
+  for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i)
+  {
+    instance t (c);
+    t->traverse (*i);
+  }
+
+  //
   // Query (abstract and concrete).
   //
 
@@ -352,11 +377,9 @@ traverse_object (type& c)
   // Containers (concrete).
   //
 
-  // Statement cache (forward declaration).
   //
-  if (id != 0)
-    os << "struct container_statement_cache_type;"
-       << endl;
+  // Sections (concrete).
+  //
 
   // column_count
   //
@@ -373,7 +396,12 @@ traverse_object (type& c)
     os << "static const std::size_t discriminator_column_count = " <<
       cc.discriminator << "UL;";
 
-  os << endl;
+  os << endl
+     << "static const std::size_t separate_load_column_count = " <<
+    cc.separate_load << "UL;"
+     << "static const std::size_t separate_update_column_count = " <<
+    cc.separate_update << "UL;"
+     << endl;
 
   // Statements.
   //
@@ -396,7 +424,7 @@ traverse_object (type& c)
         os << "static const char find_discriminator_statement[];";
     }
 
-    if (cc.total != cc.id + cc.inverse + cc.readonly)
+    if (cc.total != cc.id + cc.inverse + cc.readonly + cc.separate_update)
       os << "static const char update_statement[];";
 
     os << "static const char erase_statement[];";
@@ -499,6 +527,28 @@ traverse_object (type& c)
 
     os << ");"
        << endl;
+
+    // Sections.
+    //
+    // We treat all polymorphic sections as (potentially) having something
+    // to load or to update since we cannot predict what will be added to
+    // them in overrides.
+    //
+    if (uss.count (user_sections::count_total |
+                   user_sections::count_load  |
+                   (poly ? user_sections::count_load_empty : 0)) != 0)
+      os << "static bool" << endl
+         << "load (connection&, object_type&, section&" <<
+        (poly ? ", const info_type* = 0" : "") << ");"
+         << endl;
+
+    if (uss.count (user_sections::count_total  |
+                   user_sections::count_update |
+                   (poly ? user_sections::count_update_empty : 0)) != 0)
+      os << "static bool" << endl
+         << "update (connection&, const object_type&, const section&" <<
+        (poly ? ", const info_type* = 0" : "") << ");"
+         << endl;
   }
 
   // query ()
@@ -578,14 +628,16 @@ traverse_object (type& c)
        << "load_ (";
 
     if (poly && !poly_derived)
-      os << "base_statements_type&, ";
+      os << "base_statements_type&," << endl;
     else
-      os << "statements_type&, ";
+      os << "statements_type&," << endl;
 
-    os << "object_type&";
+    os << "object_type&," << endl
+       << "bool reload = false";
 
     if (poly_derived)
-      os << ", std::size_t = depth";
+      os << "," << endl
+         << "std::size_t = depth";
 
     os << ");"
        << endl;
diff --git a/odb/relational/header.hxx b/odb/relational/header.hxx
index de2cfe8..4616543 100644
--- a/odb/relational/header.hxx
+++ b/odb/relational/header.hxx
@@ -854,6 +854,204 @@ namespace relational
       semantics::class_& c_;
     };
 
+    //
+    //
+    struct section_traits: virtual context
+    {
+      typedef section_traits base;
+
+      section_traits (semantics::class_& c): c_ (c) {}
+
+      virtual void
+      section_public_extra_pre (user_section&)
+      {
+      }
+
+      virtual void
+      section_public_extra_post (user_section&)
+      {
+      }
+
+      virtual void
+      traverse (user_section& s)
+      {
+        semantics::class_* poly_root (polymorphic (c_));
+        bool poly (poly_root != 0);
+        bool poly_derived (poly && poly_root != &c_);
+
+        semantics::data_member* opt (optimistic (c_));
+
+        // Treat the special version update sections as abstract in reuse
+        // inheritance.
+        //
+        bool reuse_abst (!poly &&
+                         (abstract (c_) ||
+                          s.special == user_section::special_version));
+
+        bool load (s.total != 0 && s.separate_load ());
+        bool load_con (s.containers && s.separate_load ());
+        bool load_opt (s.optimistic () && s.separate_load ());
+
+        bool update (s.total != s.inverse + s.readonly); // Always separate.
+        bool update_con (s.readwrite_containers);
+        bool update_opt (s.optimistic () && (s.readwrite_containers || poly));
+
+        // Don't generate anything for empty sections.
+        //
+        if (!(load || load_con || load_opt ||
+              update || update_con || update_opt))
+          return;
+
+        // If we are adding a new section to a derived class in an optimistic
+        // hierarchy, then pretend it inherits from the special version update
+        // section.
+        //
+        user_section* rs (0);
+        if (opt != 0)
+        {
+          // Skip overrides and get to the new section if polymorphic.
+          //
+          for (rs = &s; poly && rs->base != 0; rs = rs->base) ;
+
+          if (rs != 0)
+          {
+            if (rs->object != &opt->scope ())
+              rs->base = &(poly ? poly_root : &opt->scope ())->
+                get ("user-sections").back ();
+            else
+              rs = 0;
+          }
+        }
+
+        string name (public_name (*s.member) + "_traits");
+
+        os << "// " << s.member->name () << endl
+           << "//" << endl
+           << "struct " << name
+           << "{";
+
+        os << "typedef object_traits_impl::image_type image_type;"
+           << endl;
+
+        section_public_extra_pre (s);
+
+        // bind (id, image_type)
+        //
+        // If id is NULL, then id is ignored (select). Otherwise, it is
+        // copied at the end (update).
+        //
+        if (load || load_opt || update || update_opt)
+          os << "static std::size_t" << endl
+             << "bind (" << bind_vector << "," << endl
+             << "const " << bind_vector << " id," << endl
+             << "std::size_t id_size," << endl
+             << "image_type&," << endl
+             << db << "::statement_kind);"
+             << endl;
+
+        // grow ()
+        //
+        // We have to have out own version because the truncated vector
+        // will have different number of elements.
+        //
+        if (generate_grow && (load || load_opt))
+          os << "static bool" << endl
+             << "grow (image_type&, " << truncated_vector << ");"
+             << endl;
+
+        // init (object, image)
+        //
+        if (load)
+          os << "static void" << endl
+             << "init (object_type&, const image_type&, database*);"
+             << endl;
+
+        // init (image, object)
+        //
+        if (update)
+          os << "static " << (generate_grow ? "bool" : "void") << endl
+             << "init (image_type&, const object_type&);"
+             << endl;
+
+        // The rest does not apply to reuse-abstract sections.
+        //
+        if (reuse_abst)
+        {
+          section_public_extra_post (s);
+          os << "};";
+          return;
+        }
+
+        // column_count
+        //
+        column_count_type const& cc (column_count (poly ? *poly_root : c_));
+
+        // Generate load and update column counts even when they are zero so
+        // that we can instantiate section_statements.
+        //
+        os << "static const std::size_t id_column_count = " << cc.id << "UL;";
+
+        os << "static const std::size_t managed_optimistic_load_column_count" <<
+          " = " << cc.optimistic_managed << "UL;"
+           << "static const std::size_t load_column_count = " <<
+          (load ? s.total_total () : 0) << "UL;";
+
+        os << "static const std::size_t managed_optimistic_update_column_count" <<
+          " = " << (poly_derived ? 0 : cc.optimistic_managed) << "UL;"
+           << "static const std::size_t update_column_count = " <<
+          (update ? s.total - s.inverse - s.readonly : 0) << "UL;"
+           << endl;
+
+        // Statements.
+        //
+        if (load || load_opt)
+          os << "static const char select_statement[];"
+             << endl;
+
+        if (update || update_opt)
+          os << "static const char update_statement[];"
+             << endl;
+
+        // Section statements.
+        //
+        if (load || load_opt || update || update_opt)
+          os << "typedef " << db << "::section_statements< object_type, " <<
+            name << " > statements_type;"
+             << endl;
+
+        // We pass statement cache instead of just statements because
+        // we may also need statements for containers.
+        //
+
+        // load ()
+        //
+        if (load || load_opt || load_con)
+          os << "static void" << endl
+             << "load (extra_statement_cache_type&, object_type&" <<
+            (poly ? ", bool top = true" : "") << ");"
+             << endl;
+
+        // update ()
+        //
+        if (update || update_opt || update_con)
+          os << "static void" << endl
+             << "update (extra_statement_cache_type&, const object_type&" <<
+            (poly_derived && s.base != 0 ? ", bool base = true" : "") << ");"
+             << endl;
+
+        section_public_extra_post (s);
+
+        os << "};";
+
+        if (rs != 0)
+          rs->base = 0;
+      }
+
+    protected:
+      semantics::class_& c_;
+    };
+
     // First pass over objects, views, and composites. Some code must be
     // split into two parts to deal with yet undefined types.
     //
diff --git a/odb/relational/inline.hxx b/odb/relational/inline.hxx
index 5f1caa7..928982e 100644
--- a/odb/relational/inline.hxx
+++ b/odb/relational/inline.hxx
@@ -165,10 +165,11 @@ namespace relational
       virtual void
       traverse_object (type& c)
       {
-        semantics::data_member* id (id_member (c));
-        bool base_id (id && &id->scope () != &c); // Comes from base.
+        using semantics::data_member;
 
-        semantics::data_member* optimistic (context::optimistic (c));
+        data_member* id (id_member (c));
+        bool base_id (id && &id->scope () != &c); // Comes from base.
+        data_member* optimistic (context::optimistic (c));
 
         // Base class that contains the object id and version for optimistic
         // concurrency.
@@ -187,6 +188,8 @@ namespace relational
         string traits ("access::object_traits_impl< " + type + ", id_" +
                        db.string () + " >");
 
+        user_sections& uss (c.get ("user-sections"));
+
         os << "// " << class_name (c) << endl
            << "//" << endl
            << endl;
@@ -313,14 +316,60 @@ namespace relational
              << "erase (database& db, const object_type& obj)"
              << "{"
              << "callback (db, obj, callback_event::pre_erase);"
-             << "erase (db, id (obj));"
-             << "callback (db, obj, callback_event::post_erase);"
+             << "erase (db, id (obj));";
+
+          // Note that we don't reset sections since the object is now
+          // transient and the state of a section in a transient object
+          // is undefined.
+
+          os << "callback (db, obj, callback_event::post_erase);"
+             << "}";
+        }
+
+        // load (section) [thunk version; poly_derived is true]
+        //
+        if (uss.count (user_sections::count_total |
+                       user_sections::count_load  |
+                       (poly ? user_sections::count_load_empty : 0)) != 0 &&
+            uss.count (user_sections::count_new   |
+                       user_sections::count_load  |
+                       (poly ? user_sections::count_load_empty : 0)) == 0)
+        {
+          os << "inline" << endl
+             << "bool " << traits << "::" << endl
+             << "load (connection& conn, object_type& obj, section& s, " <<
+            "const info_type* pi)"
+             << "{"
+             << "return base_traits::load (conn, obj, s, pi);"
+             << "}";
+        }
+
+        // update (section) [thunk version; poly_derived is true]
+        //
+        if (uss.count (user_sections::count_total  |
+                       user_sections::count_update |
+                       (poly ? user_sections::count_update_empty : 0)) != 0 &&
+            uss.count (user_sections::count_new    |
+                       user_sections::count_update |
+                       (poly ? user_sections::count_update_empty : 0)) == 0)
+        {
+          os << "inline" << endl
+             << "bool " << traits << "::" << endl
+             << "update (connection& conn, const object_type& obj, " <<
+            "const section& s, const info_type* pi)"
+             << "{"
+             << "return base_traits::update (conn, obj, s, pi);"
              << "}";
         }
 
         // load_()
         //
-        if (id != 0 && !(poly_derived || has_a (c, test_container)))
+        if (id != 0 &&
+            !(poly_derived ||
+              has_a (c, test_container | include_eager_load, &main_section) ||
+              uss.count (user_sections::count_new  |
+                         user_sections::count_load |
+                         (poly ? user_sections::count_load_empty : 0)) != 0))
         {
           os << "inline" << endl
              << "void " << traits << "::" << endl
@@ -331,9 +380,33 @@ namespace relational
           else
             os << "statements_type&, ";
 
-          os << "object_type&)"
+          os << "object_type& obj, bool)"
              << "{"
-             << "}";
+             << "ODB_POTENTIALLY_UNUSED (obj);"
+             << endl;
+
+          // Mark eager sections as loaded.
+          //
+          for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i)
+          {
+            // Skip special sections.
+            //
+            if (i->special == user_section::special_version)
+              continue;
+
+            data_member& m (*i->member);
+
+            // Section access is always by reference.
+            //
+            member_access& ma (m.get ("get"));
+            if (!ma.synthesized)
+              os << "// From " << location_string (ma.loc, true) << endl;
+
+            os << ma.translate ("obj") << ".reset (true, false);"
+               << endl;
+          }
+
+          os << "}";
         }
 
         if (poly && need_image_clone && options.generate_query ())
diff --git a/odb/relational/mssql/header.cxx b/odb/relational/mssql/header.cxx
index 1b868fc..5601755 100644
--- a/odb/relational/mssql/header.cxx
+++ b/odb/relational/mssql/header.cxx
@@ -47,6 +47,32 @@ namespace relational
       };
       entry class1_entry_;
 
+      struct section_traits: relational::section_traits, context
+      {
+        section_traits (base const& x): base (x) {}
+
+        virtual void
+        section_public_extra_pre (user_section&)
+        {
+          if (abstract (c_) && !polymorphic (c_))
+            return;
+
+          // rowvesion
+          //
+          bool rv (false);
+          if (semantics::data_member* m = optimistic (c_))
+          {
+            sql_type t (parse_sql_type (column_type (*m), *m));
+            rv = (t.type == sql_type::ROWVERSION);
+          }
+
+          os << "static const bool rowversion = " <<
+            (rv ? "true" : "false") << ";"
+             << endl;
+        }
+      };
+      entry section_traits_;
+
       struct image_type: relational::image_type, context
       {
         image_type (base const& x): base (x) {};
diff --git a/odb/relational/mssql/source.cxx b/odb/relational/mssql/source.cxx
index efd71f1..837313a 100644
--- a/odb/relational/mssql/source.cxx
+++ b/odb/relational/mssql/source.cxx
@@ -846,6 +846,53 @@ namespace relational
       };
       entry container_traits_;
 
+      struct section_traits: relational::section_traits,
+                             statement_columns_common
+      {
+        section_traits (base const& x): base (x) {}
+
+        virtual void
+        init_value_extra ()
+        {
+          os << "st.stream_result ();";
+        }
+
+        virtual void
+        process_statement_columns (relational::statement_columns& cols,
+                                   statement_kind sk)
+        {
+          statement_columns_common::process (cols, sk);
+        }
+
+        virtual string
+        optimistic_version_increment (semantics::data_member& m)
+        {
+          sql_type t (parse_sql_type (column_type (m), m));
+          return t.type != sql_type::ROWVERSION
+            ? "1"
+            : "sts.update_statement ().version ()";
+        }
+
+        virtual void
+        update_statement_extra (user_section&)
+        {
+          semantics::data_member* ver (optimistic (c_));
+
+          if (ver == 0 ||
+              parse_sql_type (column_type (*ver), *ver).type !=
+              sql_type::ROWVERSION)
+            return;
+
+          // Long data & SQL Server 2005 incompatibility is detected
+          // in persist_statement_extra.
+          //
+          os << strlit (
+            " OUTPUT INSERTED." + convert_from (
+              column_qname (*ver, column_prefix ()), *ver)) << endl;
+        }
+      };
+      entry section_traits_;
+
       struct class_: relational::class_, statement_columns_common
       {
         class_ (base const& x): base (x) {}
@@ -1018,14 +1065,14 @@ namespace relational
         }
 
         virtual string
-        optimimistic_version_init (semantics::data_member& m)
+        optimistic_version_init (semantics::data_member& m)
         {
           sql_type t (parse_sql_type (column_type (m), m));
           return t.type != sql_type::ROWVERSION ? "1" : "st.version ()";
         }
 
         virtual string
-        optimimistic_version_increment (semantics::data_member& m)
+        optimistic_version_increment (semantics::data_member& m)
         {
           sql_type t (parse_sql_type (column_type (m), m));
           return t.type != sql_type::ROWVERSION
diff --git a/odb/relational/mysql/context.cxx b/odb/relational/mysql/context.cxx
index 52c1dcc..75b71e9 100644
--- a/odb/relational/mysql/context.cxx
+++ b/odb/relational/mysql/context.cxx
@@ -145,8 +145,8 @@ namespace relational
     {
       struct has_grow: traversal::class_
       {
-        has_grow (bool& r)
-            : r_ (r)
+        has_grow (bool& r, user_section* s)
+            : r_ (r), section_ (s)
         {
           *this >> inherits_ >> *this;
         }
@@ -161,7 +161,7 @@ namespace relational
           if (!(context::object (c) || view || context::composite (c)))
             return;
 
-          if (c.count ("mysql-grow"))
+          if (section_ == 0 && c.count ("mysql-grow"))
             r_ = c.get ("mysql-grow");
           else
           {
@@ -173,29 +173,41 @@ namespace relational
             if (!r_)
               names (c);
 
-            c.set ("mysql-grow", r_);
+            if (section_ == 0)
+              c.set ("mysql-grow", r_);
           }
         }
 
       private:
         bool& r_;
+        user_section* section_;
         traversal::inherits inherits_;
       };
 
       struct has_grow_member: member_base
       {
         has_grow_member (bool& r,
+                         user_section* section = 0,
                          semantics::type* type = 0,
                          string const& key_prefix = string ())
-            : relational::member_base (type, string (), key_prefix),
+            : relational::member_base (type, string (), key_prefix, section),
               r_ (r)
         {
         }
 
+        virtual bool
+        pre (member_info& mi)
+        {
+          return (section_ == 0 && !separate_load (mi.m)) ||
+            (section_ != 0 && *section_ == section (mi.m));
+        }
+
         virtual void
         traverse_composite (member_info& mi)
         {
           // By calling grow() instead of recursing, we reset any overrides.
+          // We also don't pass section since they don't apply inside
+          // composites.
           //
           r_ = r_ || context::grow (dynamic_cast (mi.t));
         }
@@ -236,14 +248,14 @@ namespace relational
     }
 
     bool context::
-    grow_impl (semantics::class_& c)
+    grow_impl (semantics::class_& c, user_section* section)
     {
-      if (c.count ("mysql-grow"))
+      if (section == 0 && c.count ("mysql-grow"))
         return c.get ("mysql-grow");
 
       bool r (false);
-      has_grow ct (r);
-      has_grow_member mt  (r);
+      has_grow ct (r, section);
+      has_grow_member mt  (r, section);
       traversal::names names;
       ct >> names >> mt;
       ct.traverse (c);
@@ -263,7 +275,7 @@ namespace relational
     grow_impl (semantics::data_member& m, semantics::type& t, string const& kp)
     {
       bool r (false);
-      has_grow_member mt  (r, &t, kp);
+      has_grow_member mt  (r, 0, &t, kp);
       mt.traverse (m);
       return r;
     }
diff --git a/odb/relational/mysql/context.hxx b/odb/relational/mysql/context.hxx
index 5d5571e..681a56e 100644
--- a/odb/relational/mysql/context.hxx
+++ b/odb/relational/mysql/context.hxx
@@ -113,7 +113,7 @@ namespace relational
       convert_expr (string const&, semantics::data_member&, bool);
 
       virtual bool
-      grow_impl (semantics::class_&);
+      grow_impl (semantics::class_&, user_section*);
 
       virtual bool
       grow_impl (semantics::data_member&);
diff --git a/odb/relational/oracle/source.cxx b/odb/relational/oracle/source.cxx
index a0ef339..2fc2ad2 100644
--- a/odb/relational/oracle/source.cxx
+++ b/odb/relational/oracle/source.cxx
@@ -546,6 +546,18 @@ namespace relational
       };
       entry container_traits_;
 
+      struct section_traits: relational::section_traits, context
+      {
+        section_traits (base const& x): base (x) {}
+
+        virtual void
+        init_value_extra ()
+        {
+          os << "st.stream_result ();";
+        }
+      };
+      entry section_traits_;
+
       struct class_: relational::class_, context
       {
         class_ (base const& x): base (x) {}
diff --git a/odb/relational/pgsql/context.cxx b/odb/relational/pgsql/context.cxx
index 3d71144..947c6bd 100644
--- a/odb/relational/pgsql/context.cxx
+++ b/odb/relational/pgsql/context.cxx
@@ -114,8 +114,8 @@ namespace relational
     {
       struct has_grow: traversal::class_
       {
-        has_grow (bool& r)
-            : r_ (r)
+        has_grow (bool& r, user_section* s)
+            : r_ (r), section_ (s)
         {
           *this >> inherits_ >> *this;
         }
@@ -128,7 +128,7 @@ namespace relational
           if (!(context::object (c) || context::composite (c)))
             return;
 
-          if (c.count ("pgsql-grow"))
+          if (section_ == 0 && c.count ("pgsql-grow"))
             r_ = c.get ("pgsql-grow");
           else
           {
@@ -139,29 +139,41 @@ namespace relational
             if (!r_)
               names (c);
 
-            c.set ("pgsql-grow", r_);
+            if (section_ == 0)
+              c.set ("pgsql-grow", r_);
           }
         }
 
       private:
         bool& r_;
+        user_section* section_;
         traversal::inherits inherits_;
       };
 
       struct has_grow_member: member_base
       {
         has_grow_member (bool& r,
+                         user_section* section = 0,
                          semantics::type* type = 0,
                          string const& key_prefix = string ())
-            : relational::member_base (type, string (), key_prefix),
+            : relational::member_base (type, string (), key_prefix, section),
               r_ (r)
         {
         }
 
+        virtual bool
+        pre (member_info& mi)
+        {
+          return (section_ == 0 && !separate_load (mi.m)) ||
+            (section_ != 0 && *section_ == section (mi.m));
+        }
+
         virtual void
         traverse_composite (member_info& mi)
         {
           // By calling grow() instead of recursing, we reset any overrides.
+          // We also don't pass section since they don't apply inside
+          // composites.
           //
           r_ = r_ || context::grow (dynamic_cast (mi.t));
         }
@@ -189,22 +201,15 @@ namespace relational
       };
     }
 
-    string const& context::
-    convert_expr (string const& sqlt, semantics::data_member& m, bool to)
-    {
-      sql_type const& t (parse_sql_type (sqlt, m));
-      return to ? t.to : t.from;
-    }
-
     bool context::
-    grow_impl (semantics::class_& c)
+    grow_impl (semantics::class_& c, user_section* section)
     {
-      if (c.count ("pgsql-grow"))
+      if (section == 0 && c.count ("pgsql-grow"))
         return c.get ("pgsql-grow");
 
       bool r (false);
-      has_grow ct (r);
-      has_grow_member mt  (r);
+      has_grow ct (r, section);
+      has_grow_member mt  (r, section);
       traversal::names names;
       ct >> names >> mt;
       ct.traverse (c);
@@ -224,11 +229,18 @@ namespace relational
     grow_impl (semantics::data_member& m, semantics::type& t, string const& kp)
     {
       bool r (false);
-      has_grow_member mt  (r, &t, kp);
+      has_grow_member mt  (r, 0, &t, kp);
       mt.traverse (m);
       return r;
     }
 
+    string const& context::
+    convert_expr (string const& sqlt, semantics::data_member& m, bool to)
+    {
+      sql_type const& t (parse_sql_type (sqlt, m));
+      return to ? t.to : t.from;
+    }
+
     string context::
     database_type_impl (semantics::type& t,
                         semantics::names* hint,
diff --git a/odb/relational/pgsql/context.hxx b/odb/relational/pgsql/context.hxx
index d06e932..b7ec873 100644
--- a/odb/relational/pgsql/context.hxx
+++ b/odb/relational/pgsql/context.hxx
@@ -103,7 +103,7 @@ namespace relational
       convert_expr (string const&, semantics::data_member&, bool);
 
       virtual bool
-      grow_impl (semantics::class_&);
+      grow_impl (semantics::class_&, user_section*);
 
       virtual bool
       grow_impl (semantics::data_member&);
diff --git a/odb/relational/pgsql/header.cxx b/odb/relational/pgsql/header.cxx
index b924a42..a23ec9e 100644
--- a/odb/relational/pgsql/header.cxx
+++ b/odb/relational/pgsql/header.cxx
@@ -36,6 +36,9 @@ namespace relational
 
           column_count_type const& cc (column_count (c));
 
+          size_t update_columns (
+            cc.total - cc.id - cc.inverse - cc.readonly - cc.separate_update);
+
           // Statement names.
           //
           os << "static const char persist_statement_name[];";
@@ -51,7 +54,7 @@ namespace relational
             if (poly && !poly_derived)
               os << "static const char find_discriminator_statement_name[];";
 
-            if (cc.total != cc.id + cc.inverse + cc.readonly)
+            if (update_columns != 0)
               os << "static const char update_statement_name[];";
 
             os << "static const char erase_statement_name[];";
@@ -76,7 +79,7 @@ namespace relational
           {
             os << "static const unsigned int find_statement_types[];";
 
-            if (cc.total != cc.id + cc.inverse + cc.readonly)
+            if (update_columns != 0)
               os << "static const unsigned int update_statement_types[];";
 
             if (optimistic != 0)
@@ -125,8 +128,7 @@ namespace relational
 
           // Container statement types.
           //
-          os << "static const unsigned int select_types[];"
-             << "static const unsigned int insert_types[];";
+          os << "static const unsigned int insert_types[];";
 
           if (smart)
             os << "static const unsigned int update_types[];"
@@ -137,6 +139,44 @@ namespace relational
       };
       entry container_traits_;
 
+      struct section_traits: relational::section_traits, context
+      {
+        section_traits (base const& x): base (x) {}
+
+        virtual void
+        section_public_extra_post (user_section& s)
+        {
+          semantics::class_* poly_root (polymorphic (c_));
+          bool poly (poly_root != 0);
+
+          if (!poly && (abstract (c_) ||
+                        s.special == user_section::special_version))
+            return;
+
+          bool load (s.total != 0 && s.separate_load ());
+          bool load_opt (s.optimistic () && s.separate_load ());
+
+          bool update (s.total != s.inverse + s.readonly); // Always separate.
+          bool update_opt (s.optimistic () && (s.readwrite_containers || poly));
+
+          // Statement names.
+          //
+          if (load || load_opt)
+            os << "static const char select_name[];"
+               << endl;
+
+          if (update || update_opt)
+            os << "static const char update_name[];"
+               << endl;
+
+          // Statement types.
+          //
+          if (update || update_opt)
+            os << "static const unsigned int update_types[];";
+        }
+      };
+      entry section_traits_;
+
       struct image_member: relational::image_member, member_base
       {
         image_member (base const& x)
diff --git a/odb/relational/pgsql/source.cxx b/odb/relational/pgsql/source.cxx
index ceda512..d5fe8fd 100644
--- a/odb/relational/pgsql/source.cxx
+++ b/odb/relational/pgsql/source.cxx
@@ -98,11 +98,28 @@ namespace relational
 
       struct statement_oids: object_columns_base, context
       {
-        statement_oids (statement_kind sk, bool first = true)
-            : object_columns_base (first), sk_ (sk)
+        statement_oids (statement_kind sk,
+                        bool first = true,
+                        object_section* section = 0)
+            : object_columns_base (first, column_prefix (), section), sk_ (sk)
         {
         }
 
+        virtual bool
+        section_test (data_member_path const& mp)
+        {
+          object_section& s (section (mp));
+
+          // Include eager loaded members into the main section for
+          // SELECT statements.
+          //
+          return section_ == 0 ||
+            *section_ == s ||
+            (sk_ == statement_select &&
+             *section_ == main_section &&
+             !s.separate_load ());
+        }
+
         virtual void
         traverse_pointer (semantics::data_member& m, semantics::class_& c)
         {
@@ -265,6 +282,17 @@ namespace relational
           if (container (mi))
             return false;
 
+          if (section_ != 0 && *section_ != section (mi.m))
+            return false;
+
+          if (var_override_.empty ())
+          {
+            // Ignore separately loaded members.
+            //
+            if (section_ == 0 && separate_load (mi.m))
+              return false;
+          }
+
           // Ignore polymorphic id references; they are not returned by
           // the select statement.
           //
@@ -641,8 +669,12 @@ namespace relational
 
           semantics::data_member* id (id_member (c));
           semantics::data_member* optimistic (context::optimistic (c));
+
           column_count_type const& cc (column_count (c));
 
+          size_t update_columns (
+            cc.total - cc.id - cc.inverse - cc.readonly - cc.separate_update);
+
           string const& n (class_fq_name (c));
           string const& fn (flat_name (n));
           string traits ("access::object_traits_impl< " + n + ", id_pgsql >");
@@ -684,7 +716,7 @@ namespace relational
                 strlit (fn + "_find_discriminator") << ";"
                  << endl;
 
-            if (cc.total != cc.id + cc.inverse + cc.readonly)
+            if (update_columns != 0)
               os << "const char " << traits << "::" << endl
                  << "update_statement_name[] = " << strlit (fn + "_update") <<
                 ";"
@@ -745,7 +777,7 @@ namespace relational
                << "find_statement_types[] ="
                << "{";
 
-            statement_oids st (statement_select);
+            statement_oids st (statement_select, true);
             st.traverse (*id);
 
             os << "};";
@@ -753,19 +785,21 @@ namespace relational
 
           // update_statement_types.
           //
-          if (id != 0 && cc.total != cc.id + cc.inverse + cc.readonly)
+          if (id != 0 && update_columns != 0)
           {
             os << "const unsigned int " << traits << "::" << endl
                << "update_statement_types[] ="
                << "{";
 
             {
-              statement_oids st (statement_update);
+              statement_oids st (statement_update, true, &main_section);
               st.traverse (c);
             }
 
+            // Not the same as update_columns.
+            //
             bool first (cc.total == cc.id + cc.inverse + cc.readonly +
-                        cc.optimistic_managed);
+                        cc.separate_update + cc.optimistic_managed);
 
             statement_oids st (statement_where, first);
             st.traverse (*id);
@@ -791,11 +825,13 @@ namespace relational
         }
 
         virtual void
-        container_cache_extra_args (bool used)
+        extra_statement_cache_extra_args (bool c, bool s)
         {
+          bool u (c || s);
+
           os << "," << endl
-             << db << "::native_binding&" << (used ? " idn" : "") << "," << endl
-             << "const unsigned int*" << (used ? " idt" : "");
+             << db << "::native_binding&" << (u ? " idn" : "") << "," << endl
+             << "const unsigned int*" << (u ? " idt" : "");
         }
 
         virtual void
@@ -916,33 +952,6 @@ namespace relational
           semantics::type& vt (container_vt (t));
           semantics::type& idt (container_idt (m));
 
-          // select statement types.
-          //
-          {
-            os << "const unsigned int " << scope << "::" << endl
-               << "select_types[] ="
-               << "{";
-
-            statement_oids so (statement_where);
-
-            if (inv)
-            {
-              // many(i)-to-many
-              //
-              if (container (*inv_m))
-                so.traverse (*inv_m, idt, "value", "value");
-
-              // many(i)-to-one
-              //
-              else
-                so.traverse (*inv_m);
-            }
-            else
-              so.traverse (m, idt, "id", "object_id");
-
-            os << "};";
-          }
-
           // insert statement types.
           //
           {
@@ -1065,6 +1074,74 @@ namespace relational
       };
       entry container_traits_;
 
+      struct section_traits : relational::section_traits, context
+      {
+        section_traits (base const& x): base (x) {}
+
+        virtual void
+        section_extra (user_section& s)
+        {
+          semantics::class_* poly_root (polymorphic (c_));
+          bool poly (poly_root != 0);
+
+          if (!poly && (abstract (c_) ||
+                        s.special == user_section::special_version))
+            return;
+
+          semantics::data_member* opt (optimistic (c_));
+
+          bool load (s.total != 0 && s.separate_load ());
+          bool load_opt (s.optimistic () && s.separate_load ());
+
+          bool update (s.total != s.inverse + s.readonly); // Always separate.
+          bool update_opt (s.optimistic () && (s.readwrite_containers || poly));
+
+          string name (public_name (*s.member));
+          string scope (scope_ + "::" + name + "_traits");
+
+          // Statment names.
+          //
+
+          // Prefix object name to avoid conflicts with inherited member
+          // statement names.
+          //
+          string fn (flat_name (class_fq_name (c_) + "_" + name));
+
+          if (load || load_opt)
+            os << "const char " << scope << "::" << endl
+               << "select_name[] = " << strlit (fn + "_select") << ";"
+               << endl;
+
+          if (update || update_opt)
+            os << "const char " << scope << "::" << endl
+               << "update_name[] = " << strlit (fn + "_update") << ";"
+               << endl;
+
+          // Statement types.
+          //
+          if (update || update_opt)
+          {
+            os << "const unsigned int " << scope << "::" << endl
+               << "update_types[] ="
+               << "{";
+
+            {
+              statement_oids st (statement_update, true, &s);
+              st.traverse (c_);
+            }
+
+            statement_oids st (statement_where, !update);
+            st.traverse (*id_member (c_));
+
+            if (s.optimistic ()) // Note: not update_opt.
+              st.traverse (*opt);
+
+            os << "};";
+          }
+        }
+      };
+      entry section_traits_;
+
       struct container_cache_init_members:
         relational::container_cache_init_members
       {
@@ -1078,6 +1155,18 @@ namespace relational
       };
       entry container_cache_init_members_;
 
+      struct section_cache_init_members:
+        relational::section_cache_init_members
+      {
+        section_cache_init_members (base const& x): base (x) {}
+
+        virtual void
+        extra_members ()
+        {
+          os << ", idn, idt";
+        }
+      };
+      entry section_cache_init_members_;
     }
   }
 }
diff --git a/odb/relational/source.cxx b/odb/relational/source.cxx
index 8d7c913..ba5a464 100644
--- a/odb/relational/source.cxx
+++ b/odb/relational/source.cxx
@@ -2,6 +2,8 @@
 // copyright : Copyright (c) 2009-2013 Code Synthesis Tools CC
 // license   : GNU GPL v3; see accompanying LICENSE file
 
+#include 
+
 #include 
 
 #include 
@@ -52,6 +54,36 @@ traverse_object (type& c)
                  db.string () + " >");
   column_count_type const& cc (column_count (c));
 
+  user_sections& uss (c.get ("user-sections"));
+  user_sections* buss (poly_base != 0
+                       ? &poly_base->get ("user-sections")
+                       : 0);
+
+  // See what kind of containers we've got.
+  //
+  bool containers (has_a (c, test_container));
+  bool straight_containers (false);
+  bool smart_containers (false);
+
+  // Eager load and update containers.
+  //
+  bool load_containers (false);
+  bool update_containers (false);
+
+  if (containers)
+  {
+    load_containers = has_a (
+      c, test_container | include_eager_load, &main_section);
+
+    if ((straight_containers = has_a (c, test_straight_container)))
+    {
+      // Only straight containers can be readwrite or smart.
+      //
+      smart_containers = has_a (c, test_smart_container);
+      update_containers = has_a (c, test_readwrite_container, &main_section);
+    }
+  }
+
   os << "// " << class_name (c) << endl
      << "//" << endl
      << endl;
@@ -67,32 +99,77 @@ traverse_object (type& c)
   if (options.generate_query ())
     query_columns_type_->traverse (c);
 
+  // Statement cache (definition).
   //
-  // Containers (abstract and concrete).
-  //
-  bool containers (has_a (c, test_container));
-  bool straight_containers (false);
-  bool straight_readwrite_containers (false);
-  bool smart_containers (false);
-
-  if (containers)
+  if (!reuse_abst && id != 0)
   {
-    size_t scn (has_a (c, test_straight_container));
+    os << "struct " << traits << "::extra_statement_cache_type"
+       << "{";
 
-    if (scn != 0)
-    {
-      straight_containers = true;
+    instance cm;
+    cm->traverse (c);
+
+    if (containers)
+      os << endl;
 
-      // Inverse containers cannot be marked readonly.
+    bool sections (false);
+    instance sm;
+    for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i)
+    {
+      // Skip the special version update section in reuse inheritance (we
+      // always treat it as abstract).
       //
-      straight_readwrite_containers = scn > has_a (c, test_readonly_container);
+      if (i->special == user_section::special_version && !poly)
+        continue;
 
-      // Inverse containers cannot be smart.
+      // Generate an entry for a readonly section in optimistic
+      // polymorphic root since it can be overridden and we may
+      // need to update the version.
       //
-      smart_containers = has_a (c, test_smart_container);
+      if (!i->empty () || (poly && i->optimistic ()))
+      {
+        sm->traverse (*i);
+        sections = true;
+      }
     }
+
+    if (sections)
+      os << endl;
+
+    os << "extra_statement_cache_type (" << endl
+       << db << "::connection&" << (containers || sections ? " c" : "") <<
+      "," << endl
+       << "image_type&" << (sections ? " im" : "") << "," << endl
+       << db << "::binding&" << (containers || sections ? " id" : "") <<
+      "," << endl
+       << db << "::binding&" << (sections ? " idv" : "");
+
+    extra_statement_cache_extra_args (containers, sections);
+
+    os << ")";
+
+    instance cim;
+    cim->traverse (c);
+
+    instance sim (!containers);
+    for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i)
+    {
+      if (i->special == user_section::special_version && !poly)
+        continue;
+
+      if (!i->empty () || (poly && i->optimistic ()))
+        sim->traverse (*i);
+    }
+
+    os << "{"
+       << "}"
+       << "};";
   }
 
+  //
+  // Containers (abstract and concrete).
+  //
+
   if (containers)
   {
     instance t (c);
@@ -100,6 +177,16 @@ traverse_object (type& c)
   }
 
   //
+  // Sections (abstract and concrete).
+  //
+
+  for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i)
+  {
+    instance t (c);
+    t->traverse (*i);
+  }
+
+  //
   // Functions (abstract and concrete).
   //
 
@@ -207,7 +294,7 @@ traverse_object (type& c)
      << "bind (" << bind_vector << " b," << endl;
 
   // If we are a derived type in a polymorphic hierarchy, then
-  // we get the the external id binding.
+  // we get the external id binding.
   //
   if (poly_derived)
     os << "const " << bind_vector << " id," << endl
@@ -238,7 +325,7 @@ traverse_object (type& c)
        << "{"
        << "if (id != 0)" << endl
        << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));"
-       << "n += id_size;"
+       << "n += id_size;" // Not in if for "id unchanged" optimization.
        << "}";
   }
   else
@@ -257,7 +344,7 @@ traverse_object (type& c)
          << "{"
          << "if (id != 0)" << endl
          << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));"
-         << "n += id_size;"
+         << "n += id_size;" // Not in if for "id unchanged" optimization.
          << "}";
 
     // Bind the image chain for the select statement. Seeing that
@@ -412,32 +499,9 @@ traverse_object (type& c)
   // Containers (concrete).
   //
 
-  // Statement cache (definition).
   //
-  if (id != 0)
-  {
-    os << "struct " << traits << "::container_statement_cache_type"
-       << "{";
-
-    instance cm;
-    cm->traverse (c);
-
-    os << (containers ? "\n" : "")
-       << "container_statement_cache_type (" << endl
-       << db << "::connection&" << (containers ? " c" : "") << "," << endl
-       << db << "::binding&" << (containers ? " id" : "");
-
-    container_cache_extra_args (containers);
-
-    os << ")";
-
-    instance im;
-    im->traverse (c);
-
-    os << "{"
-       << "}"
-       << "};";
-  }
+  // Sections (concrete).
+  //
 
   // Polymorphic map.
   //
@@ -448,6 +512,73 @@ traverse_object (type& c)
          << traits << "::map;"
          << endl;
 
+    // Sections. Do we have any new sections or overrides?
+    //
+    bool sections (
+      uss.count (user_sections::count_new             |
+                 user_sections::count_override        |
+                 user_sections::count_load            |
+                 user_sections::count_update          |
+                 user_sections::count_special_version |
+                 (poly ? user_sections::count_optimistic : 0)) != 0);
+    if (sections)
+    {
+      map m;
+      for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i)
+        m[i->index] = &*i;
+
+      os << "static const "<< traits << "::" << (abst ? "abstract_" : "") <<
+        "info_type::section_functions" << endl
+         << "section_table_for_" << flat_name (type) << "[] ="
+         << "{";
+
+      for (size_t i (0), n (uss.count (user_sections::count_total |
+                                       user_sections::count_all));
+           i != n; ++i)
+      {
+        os << (i != 0 ? "," : "") << "{";
+
+        map::iterator j (m.find (i));
+
+        if (j != m.end ())
+        {
+          user_section& s (*j->second);
+          string n (public_name (*s.member));
+
+          // load
+          //
+          if (s.load_empty ())
+            os << "0";
+          else
+            os << "&odb::section_load_impl< " << type << ", id_" << db <<
+              ", " << traits << "::" << n << "_traits >";
+
+          os << "," << endl;
+
+          // update
+          //
+          // Generate an entry for a readonly section in optimistic
+          // polymorphic root since it can be overridden and we may
+          // need to update the version.
+          //
+          if (s.update_empty () && !(poly && s.optimistic ()))
+            os << "0";
+          else
+            os << "&odb::section_update_impl< " << type << ", id_" << db <<
+              ", " << traits << "::" << n << "_traits >";
+        }
+        else
+          os << "0," << endl
+             << "0";
+
+        os << "}";
+      }
+
+      os << "};";
+    }
+
+    //
+    //
     os << "const " << traits << "::" << (abst ? "abstract_" : "") <<
       "info_type" << endl
        << traits << "::info (" << endl
@@ -455,18 +586,21 @@ traverse_object (type& c)
 
     if (poly_derived)
       os << "&object_traits_impl< " << class_fq_name (*poly_base) <<
-        ", id_" << db << " >::info";
+        ", id_" << db << " >::info," << endl;
     else
-      os << "0";
+      os << "0," << endl;
 
-    string n;
+    // Sections.
+    //
+    if (sections)
+      os << "section_table_for_" << flat_name (type);
+    else
+      os << "0";
 
     if (!abst)
     {
-      n = class_fq_name (c);
-
       os << "," << endl
-         << strlit (string (n, 2, string::npos)) << "," << endl
+         << strlit (string (type, 2, string::npos)) << "," << endl
          << "&odb::create_impl< " << type << " >," << endl
          << "&odb::dispatch_impl< " << type << ", id_" << db << " >," << endl;
 
@@ -481,13 +615,15 @@ traverse_object (type& c)
 
     if (!abst)
       os << "static const " << traits << "::entry_type" << endl
-         << "polymorphic_entry_for_" << flat_name (n) << ";"
+         << "polymorphic_entry_for_" << flat_name (type) << ";"
          << endl;
   }
 
   //
   // Statements.
   //
+  size_t update_columns (
+    cc.total - cc.id - cc.inverse - cc.readonly - cc.separate_update);
 
   qname table (table_name (c));
   string qtable (quote_id (table));
@@ -562,7 +698,8 @@ traverse_object (type& c)
       statement_columns sc;
       {
         statement_kind sk (statement_select); // Imperfect forwarding.
-        instance t (qtable, sk, sc, d);
+        object_section* s (&main_section); // Imperfect forwarding.
+        instance t (qtable, sk, sc, d, s);
         t->traverse (c);
         process_statement_columns (sc, statement_select);
         find_column_counts[poly_depth - d] = sc.size ();
@@ -588,9 +725,14 @@ traverse_object (type& c)
           j->traverse (polymorphic_base (c));
         }
 
-        bool f (false); // @@ (im)perfect forwarding
-        instance j (c, f, d); // @@ (im)perfect forwarding
-        j->traverse (c);
+        {
+          // For the find statement, don't join section members.
+          //
+          bool f (false); // @@ (im)perfect forwarding
+          object_section* s (&main_section); // @@ (im)perfect forwarding
+          instance j (c, f, d, s);
+          j->traverse (c);
+        }
 
         instance qp (table);
         for (object_columns_list::iterator b (id_cols->begin ()), i (b);
@@ -690,7 +832,7 @@ traverse_object (type& c)
 
     // update_statement
     //
-    if (cc.total != cc.id + cc.inverse + cc.readonly)
+    if (update_columns != 0)
     {
       instance qp (table);
 
@@ -698,7 +840,8 @@ traverse_object (type& c)
       {
         query_parameters* p (qp.get ()); // Imperfect forwarding.
         statement_kind sk (statement_update); // Imperfect forwarding.
-        instance t (sk, sc, p);
+        object_section* s (&main_section); // Imperfect forwarding.
+        instance t (sk, sc, p, s);
         t->traverse (c);
         process_statement_columns (sc, statement_update);
       }
@@ -713,6 +856,18 @@ traverse_object (type& c)
         os << strlit (c + (++i != e ? "," : "")) << endl;
       }
 
+      // This didn't work out: cannot change the identity column.
+      //
+      //if (sc.empty ())
+      //{
+      //  // We can end up with nothing to set if we need to "touch" a row
+      //  // in order to increment its optimistic concurrency version. In
+      //  // this case just do a dummy assignment based on the id column.
+      //  //
+      //  string const& c (quote_id (id_cols->begin ()->name));
+      //  os << strlit (c + "=" + c) << endl;
+      //}
+
       update_statement_extra (c);
 
       for (object_columns_list::iterator b (id_cols->begin ()), i (b);
@@ -792,8 +947,9 @@ traverse_object (type& c)
     //
     statement_columns sc;
     {
-      statement_kind sk (statement_select); // Imperfect forwarding.
-      instance oc (qtable, sk, sc, poly_depth);
+      statement_kind sk (statement_select); //@@ Imperfect forwarding.
+      object_section* s (&main_section); //@@ Imperfect forwarding.
+      instance oc (qtable, sk, sc, poly_depth, s);
       oc->traverse (c);
       process_statement_columns (sc, statement_select);
     }
@@ -819,8 +975,11 @@ traverse_object (type& c)
 
     if (id != 0)
     {
+      // For the query statement we also join section members so that they
+      // can be used in the WHERE clause.
+      //
       bool t (true); //@@ (im)perfect forwarding
-      instance oj (c, t, poly_depth); //@@ (im)perfect forwarding
+      instance oj (c, t, poly_depth);
       oj->traverse (c);
     }
 
@@ -1009,7 +1168,7 @@ traverse_object (type& c)
     // If we don't have auto id, then obj is a const reference.
     //
     string obj (auto_id ? "obj" : "const_cast< object_type& > (obj)");
-    string init (optimimistic_version_init (*opt));
+    string init (optimistic_version_init (*opt));
 
     if (!opt_ma_set->synthesized)
       os << "// From " << location_string (opt_ma_set->loc, true) << endl;
@@ -1062,8 +1221,10 @@ traverse_object (type& c)
        << "{"
        << "bind (idb.bind, i);"
        << "sts.id_image_version (i.version);"
-       << "idb.version++;"
-       << "}";
+       << "idb.version++;";
+    if (opt != 0)
+      os << "sts.optimistic_id_image_binding ().version++;";
+    os << "}";
 
     if (poly && !straight_containers && !abst)
       os << "}";
@@ -1071,10 +1232,39 @@ traverse_object (type& c)
 
   if (straight_containers)
   {
+    os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());"
+       << endl;
+
     instance t (container_calls::persist_call);
     t->traverse (c);
   }
 
+  // Reset sections: loaded, unchanged.
+  //
+  for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i)
+  {
+    // Skip special sections.
+    //
+    if (i->special == user_section::special_version)
+      continue;
+
+    // Skip overridden sections; they have been reset by the base.
+    //
+    if (i->base != 0 && poly_derived)
+      continue;
+
+    data_member& m (*i->member);
+
+    // Section access is always by reference.
+    //
+    member_access& ma (m.get ("get"));
+    if (!ma.synthesized)
+      os << "// From " << location_string (ma.loc, true) << endl;
+
+    os << ma.translate ("obj") << ".reset (true, false);"
+       << endl;
+  }
+
   // Call callback (post_persist).
   //
   if (!abst) // If we are poly-abstract, then top will always be false.
@@ -1093,6 +1283,17 @@ traverse_object (type& c)
   //
   if (id != 0 && (!readonly || poly))
   {
+    // See if we have any sections that we might have to update.
+    //
+    bool sections (false);
+    for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i)
+    {
+      // This test will automatically skip the special version update section.
+      //
+      if (!i->update_empty () && i->update != user_section::update_manual)
+        sections = true;
+    }
+
     os << "void " << traits << "::" << endl
        << "update (database& db, const object_type& obj";
 
@@ -1146,16 +1347,71 @@ traverse_object (type& c)
            << endl;
       }
 
+      // If we have change-updated sections that contain change-tracking
+      // containers, then mark such sections as changed if any of the
+      // containers was changed. Do this before calling the base so that
+      // we account for the whole hierarchy before we actually start
+      // updating any sections (important for optimistic concurrency
+      // version).
+      //
+      for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i)
+      {
+        user_section& s (*i);
+
+        if (s.update != user_section::update_change)
+          continue;
+
+        if (!has_a (c, test_smart_container, &s))
+          continue;
+
+        data_member& m (*s.member);
+
+        os << "// " << m.name () << endl
+           << "//" << endl
+           << "{";
+
+        // Section access is always by reference.
+        //
+        member_access& ma (m.get ("get"));
+        if (!ma.synthesized)
+          os << "// From " << location_string (ma.loc, true) << endl;
+
+        // VC++ cannot grok the constructor syntax.
+        //
+        os << "const odb::section& s = " << ma.translate ("obj") << ";"
+           << endl;
+
+        os << "if (";
+
+        // Unless we are always loaded, test for being loaded.
+        //
+        if (s.load != user_section::load_eager)
+          os << "s.loaded () && ";
+
+        // And for not already being changed.
+        //
+        os << "!s.changed ())"
+           << "{";
+
+        instance t (container_calls::section_call, &s);
+        t->traverse (c);
+
+        os << "}" // if
+           << "}";
+      }
+
       if (poly_derived)
       {
         bool readonly_base (context::readonly (*poly_base));
 
         if (readonly_base ||
-            cc.total != cc.id + cc.inverse + cc.readonly ||
-            straight_readwrite_containers)
+            update_columns != 0 ||
+            update_containers ||
+            sections)
         {
-          os << db << "::connection& conn (" << endl
-             << db << "::transaction::current ().connection ());"
+          os << db << "::transaction& tr (" << db <<
+            "::transaction::current ());"
+             << db << "::connection& conn (tr.connection ());"
              << "statements_type& sts (" << endl
              << "conn.statement_cache ().find_object ());"
              << endl;
@@ -1171,13 +1427,11 @@ traverse_object (type& c)
         else
         {
           // Otherwise, we have to initialize the id image ourselves. If
-          // we don't have any columns or containers to update, then we
-          // only have to do it if this is not a top-level call. If we
-          // are abstract, then top is always false.
+          // we don't have any columns, containers or sections to update,
+          // then we only have to do it if this is not a top-level call.
+          // If we are abstract, then top is always false.
           //
-          if (cc.total == cc.id + cc.inverse + cc.readonly &&
-              !straight_readwrite_containers &&
-              !abst)
+          if (update_columns == 0 && !update_containers && !sections && !abst)
             os << "if (!top)";
 
           os << "{"
@@ -1195,17 +1449,16 @@ traverse_object (type& c)
              << "bind (idb.bind, i);"
              << "sts.id_image_version (i.version);"
              << "idb.version++;"
+            // Optimistic poly base cannot be readonly.
              << "}"
              << "}";
         }
 
-        if (cc.total != cc.id + cc.inverse + cc.readonly)
-        {
+        if (update_columns != 0)
           os << "const binding& idb (sts.id_image_binding ());"
              << endl;
-        }
 
-        if (cc.total != cc.id + cc.inverse + cc.readonly)
+        if (update_columns != 0)
         {
           // Initialize the object image.
           //
@@ -1255,16 +1508,23 @@ traverse_object (type& c)
         // to update.
         //
       }
-      else if (cc.total != cc.id + cc.inverse + cc.readonly)
+      else if (update_columns != 0)
       {
-        os << db << "::connection& conn (" << endl
-           << db << "::transaction::current ().connection ());"
+        os << db << "::transaction& tr (" << db <<
+          "::transaction::current ());"
+           << db << "::connection& conn (tr.connection ());"
            << "statements_type& sts (" << endl
            << "conn.statement_cache ().find_object ());"
            << endl;
 
-        // Initialize object and id images.
+        // Initialize id image.
         //
+        if (!id_ma->synthesized)
+          os << "// From " << location_string (id_ma->loc, true) << endl;
+
+        os << "const id_type& id (" << endl
+           << id_ma->translate ("obj") << ");";
+
         if (opt != 0)
         {
           if (!opt_ma_get->synthesized)
@@ -1274,31 +1534,30 @@ traverse_object (type& c)
              << opt_ma_get->translate ("obj") << ");";
         }
 
-        os << "id_image_type& i (sts.id_image ());";
-
-        if (!id_ma->synthesized)
-          os << "// From " << location_string (id_ma->loc, true) << endl;
-
-        os << "init (i, " << id_ma->translate ("obj");
+        os << "id_image_type& idi (sts.id_image ());"
+           << "init (idi, id";
 
         if (opt != 0)
           os << ", &v";
 
         os << ");"
-           << endl
-           << "image_type& im (sts.image ());";
+           << endl;
+
+        // Initialize object image.
+        //
+        os << "image_type& im (sts.image ());";
 
         if (generate_grow)
-            os << "if (";
+          os << "if (";
 
-          os << "init (im, obj, statement_update)";
+        os << "init (im, obj, statement_update)";
 
-          if (generate_grow)
-            os << ")" << endl
-               << "im.version++";
+        if (generate_grow)
+          os << ")" << endl
+             << "im.version++";
 
-          os << ";"
-             << endl;
+        os << ";"
+           << endl;
 
         // Update binding is bound to two images (object and id)
         // so we have to track both versions.
@@ -1319,23 +1578,25 @@ traverse_object (type& c)
         // suffix of the update bind array (see object_statements).
         //
         os << "binding& idb (sts.id_image_binding ());"
-           << "if (i.version != sts.update_id_image_version () ||" << endl
+           << "if (idi.version != sts.update_id_image_version () ||" << endl
            << "idb.version == 0)"
            << "{"
           // If the id binding is up-to-date, then that means update
           // binding is too and we just need to update the versions.
           //
-           << "if (i.version != sts.id_image_version () ||" << endl
+           << "if (idi.version != sts.id_image_version () ||" << endl
            << "idb.version == 0)"
            << "{"
-           << "bind (idb.bind, i);"
+           << "bind (idb.bind, idi);"
           // Update the id binding versions since we may use them later
           // to update containers.
           //
-           << "sts.id_image_version (i.version);"
-           << "idb.version++;"
-           << "}"
-           << "sts.update_id_image_version (i.version);"
+           << "sts.id_image_version (idi.version);"
+           << "idb.version++;";
+        if (opt != 0)
+          os << "sts.optimistic_id_image_binding ().version++;";
+        os << "}"
+           << "sts.update_id_image_version (idi.version);"
            << endl
            << "if (!u)" << endl
            << "imb.version++;"
@@ -1354,11 +1615,14 @@ traverse_object (type& c)
       {
         // We don't have any columns to update. Note that we still have
         // to make sure this object exists in the database. For that we
-        // will run the SELECT query using the find_() function.
-        //
+        // will run the SELECT query using the find_() function. Note
+        // that this case cannot be optimistic (we always update the
+        // version column) and we don't need to worry about initializing
+        // version in id image for sections below.
         //
-        os << db << "::connection& conn (" << endl
-           << db << "::transaction::current ().connection ());"
+        os << db << "::transaction& tr (" << db <<
+          "::transaction::current ());"
+           << db << "::connection& conn (tr.connection ());"
            << "statements_type& sts (" << endl
            << "conn.statement_cache ().find_object ());"
            << endl;
@@ -1379,7 +1643,9 @@ traverse_object (type& c)
           os << "discriminator_ (sts, id, 0);"
              << endl;
 
-          if (!abst)
+          // The same rationale as a couple of screens up.
+          //
+          if (!update_containers && !sections && !abst)
             os << "if (!top)";
 
           os << "{"
@@ -1394,6 +1660,7 @@ traverse_object (type& c)
              << "bind (idb.bind, i);"
              << "sts.id_image_version (i.version);"
              << "idb.version++;"
+            // Cannot be optimistic.
              << "}"
              << "}";
         }
@@ -1408,13 +1675,20 @@ traverse_object (type& c)
         }
       }
 
-      if (straight_readwrite_containers)
+      if (update_containers || sections)
+        os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());"
+           << endl;
+
+      if (update_containers)
       {
-        instance t (container_calls::update_call);
+        instance t (container_calls::update_call,
+                                     &main_section);
         t->traverse (c);
       }
 
       // Update the optimistic concurrency version in the object member.
+      // Do it before updating sections since they will update it as well.
+      // The same code as in section_traits.
       //
       if (opt != 0 && !poly_derived)
       {
@@ -1422,7 +1696,7 @@ traverse_object (type& c)
         // constness.
         //
         string obj ("const_cast< object_type& > (obj)");
-        string inc (optimimistic_version_increment (*opt));
+        string inc (optimistic_version_increment (*opt));
 
         if (!opt_ma_set->synthesized)
           os << "// From " << location_string (opt_ma_set->loc, true) << endl;
@@ -1465,6 +1739,123 @@ traverse_object (type& c)
         }
       }
 
+      // Update sections that are loaded and need updating.
+      //
+      for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i)
+      {
+        if (i->update_empty () || i->update == user_section::update_manual)
+          continue;
+
+        // Here is the deal: for polymorphic section overrides, we could
+        // have used the same approach as in load_() where the class that
+        // defines the section data member initiates the section update.
+        // However, if we used this approach, then we would get what can
+        // be called "interleaved" update statements. That is, a section
+        // update would update the section data in "derived" tables before
+        // updating the main section in these derived tables. While this
+        // does not feel right, it can also cause more real problems if,
+        // for example, there are constraints or some such. Generally,
+        // we always want to update the main section data first for each
+        // table in the hierarchy.
+        //
+        // So what we are going to do instead is call section traits
+        // update at each override point (and indicate to it not to
+        // call base). One tricky aspect is who is going to reset the
+        // changed state. We have to do it at the top since otherwise
+        // overrides won't know the section has changed. Finding out
+        // whether we are the final override (in section terms, not
+        // object terms) is tricky.
+        //
+        data_member& m (*i->member);
+
+        // Section access is always by reference.
+        //
+        member_access& ma (m.get ("get"));
+        if (!ma.synthesized)
+          os << "// From " << location_string (ma.loc, true) << endl;
+
+        os << "if (";
+
+        // Unless we are always loaded, test for being loaded.
+        //
+        string sep;
+        if (i->load != user_section::load_eager)
+        {
+          os << ma.translate ("obj") << ".loaded ()";
+          sep = " && ";
+        }
+
+        // If change-updated section, check that it has changed.
+        //
+        if (i->update == user_section::update_change)
+          os << sep << ma.translate ("obj") << ".changed ()";
+
+        os << ")"
+           << "{";
+
+        // Re-initialize the id+ver image with new version which may
+        // have changed. Here we take a bit of a shortcut and not
+        // re-bind the image since we know only version may have
+        // changed and that is always an integer (cannot grow).
+        //
+        if (opt != 0)
+        {
+          if (!opt_ma_get->synthesized)
+            os << "// From " << location_string (opt_ma_get->loc, true) << endl;
+
+          os << "const version_type& v (" << endl
+             << opt_ma_get->translate ("obj") << ");";
+
+          if (!id_ma->synthesized)
+          os << "// From " << location_string (id_ma->loc, true) << endl;
+
+          os << "init (sts.id_image (), " << id_ma->translate ("obj") <<
+            ", &v);"
+             << endl;
+        }
+
+        os << public_name (m) << "_traits::update (esc, obj";
+
+        if (poly_derived && i->base != 0)
+        {
+          // Normally we don't want to call base (base object's update()
+          // would have called it; see comment above). There is one
+          // exception, however, and that is a readonly base section
+          // in optimistic class. In this case, base object's update()
+          // won't call it (it is readonly) but it needs to be called
+          // in order to increment the version.
+          //
+          bool base (false);
+          for (user_section* b (i->base); !base && b != 0; b = b->base)
+          {
+            if (b->total != b->inverse + b->readonly ||
+                b->readwrite_containers)
+              break; // We have a readwrite base.
+            else if (b->optimistic ())
+              base = true; // We have a readonly optimistic root.
+          }
+
+          os << ", " << (base ? "true" : "false");
+        }
+
+        os << ");";
+
+        // Clear the change flag and arm the transaction rollback callback.
+        //
+        if (i->update == user_section::update_change)
+        {
+          // If polymorphic, do it only if we are the final override.
+          //
+          if (poly)
+            os << "if (root_traits::map->find (typeid (obj))." <<
+              "final_section_update (info, " << i->index << "UL))" << endl;
+
+          os << ma.translate ("obj") << ".reset (true, false, &tr);";
+        }
+
+        os << "}";
+      }
+
       // Call callback (post_update).
       //
       if (!abst) // If we are poly-abstract, then top will always be false.
@@ -1558,8 +1949,10 @@ traverse_object (type& c)
          << "{"
          << "bind (idb.bind, i);"
          << "sts.id_image_version (i.version);"
-         << "idb.version++;"
-         << "}";
+         << "idb.version++;";
+      if (opt != 0)
+        os << "sts.optimistic_id_image_binding ().version++;";
+      os << "}";
 
       if (poly)
         os << "}";
@@ -1572,6 +1965,9 @@ traverse_object (type& c)
     //
     if (straight_containers)
     {
+      os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());"
+         << endl;
+
       instance t (container_calls::erase_id_call);
       t->traverse (c);
     }
@@ -1654,7 +2050,9 @@ traverse_object (type& c)
          << "conn.statement_cache ().find_object ());";
 
       if (poly_derived)
-        os << "root_statements_type& rsts (sts.root_statements ());";
+        os << endl
+           << "root_statements_type& rsts (sts.root_statements ());"
+           << "ODB_POTENTIALLY_UNUSED (rsts);";
 
       os << endl;
 
@@ -1702,6 +2100,7 @@ traverse_object (type& c)
              << "bind (idb.bind, i);"
              << rsts << ".id_image_version (i.version);"
              << "idb.version++;"
+            // Cannot be optimistic.
              << "}";
 
           if (poly)
@@ -1713,8 +2112,16 @@ traverse_object (type& c)
         // here since in case of a custom schema, it might not be
         // there).
         //
-        instance t (container_calls::erase_obj_call);
-        t->traverse (c);
+
+        if (straight_containers)
+        {
+          os << "extra_statement_cache_type& esc (" <<
+            "sts.extra_statement_cache ());"
+             << endl;
+
+          instance t (container_calls::erase_obj_call);
+          t->traverse (c);
+        }
 
         os << "if (sts.erase_statement ().execute () != 1)" << endl
            << "throw object_not_persistent ();"
@@ -1741,32 +2148,14 @@ traverse_object (type& c)
              << "init (i, id, &v);"
              << endl;
 
-          // To update the id part of the optimistic id binding we have
-          // to do it indirectly via the id binding, since both id and
-          // optimistic id bindings just point to the suffix of the
-          // update bind array (see object_statements).
-          //
-          os << "binding& oidb (" << rsts << ".optimistic_id_image_binding ());"
-             << "if (i.version != " << rsts <<
-            ".optimistic_id_image_version () ||" << endl
-             << "oidb.version == 0)"
-             << "{"
-            // If the id binding is up-to-date, then that means optimistic
-            // id binding is too and we just need to update the versions.
-            //
-             << "binding& idb (" << rsts << ".id_image_binding ());"
+          os << "binding& idb (" << rsts << ".id_image_binding ());"
              << "if (i.version != " << rsts << ".id_image_version () ||" << endl
              << "idb.version == 0)"
              << "{"
              << "bind (idb.bind, i);"
-            // Update the id binding versions since we may use them later
-            // to delete containers.
-            //
              << rsts << ".id_image_version (i.version);"
              << "idb.version++;"
-             << "}"
-             << rsts << ".optimistic_id_image_version (i.version);"
-             << "oidb.version++;"
+             << rsts << ".optimistic_id_image_binding ().version++;"
              << "}";
 
           if (poly)
@@ -1841,6 +2230,10 @@ traverse_object (type& c)
         //
         if (straight_containers)
         {
+          os << "extra_statement_cache_type& esc (" <<
+            "sts.extra_statement_cache ());"
+             << endl;
+
           instance t (container_calls::erase_obj_call);
           t->traverse (c);
         }
@@ -1868,6 +2261,10 @@ traverse_object (type& c)
           os << "if (top)"
              << "{";
 
+        // Note that we don't reset sections since the object is now
+        // transient and the state of a section in a transient object
+        // is undefined.
+
         // Remove from the object cache.
         //
         os << "pointer_cache_traits::erase (db, id);";
@@ -1880,10 +2277,6 @@ traverse_object (type& c)
           os << "}";
       }
     }
-    else if (smart_containers)
-    {
-
-    }
     else
     {
       os << "callback (db, obj, callback_event::pre_erase);"
@@ -2272,7 +2665,7 @@ traverse_object (type& c)
       if (delay_freeing_statement_result)
         os << "ar.free ();";
 
-      os << "load_ (sts, obj);"
+      os << "load_ (sts, obj, true);"
          << rsts << ".load_delayed ();"
          << "l.unlock ();"
          << "callback (db, obj, callback_event::post_load);"
@@ -2282,6 +2675,307 @@ traverse_object (type& c)
     os << "}";
   }
 
+  // load (section) [non-thunk version]
+  //
+  if (uss.count (user_sections::count_new   |
+                 user_sections::count_load  |
+                 (poly ? user_sections::count_load_empty : 0)) != 0)
+  {
+    os << "bool " << traits << "::" << endl
+       << "load (connection& conn, object_type& obj, section& s" <<
+      (poly ? ", const info_type* pi" : "") << ")"
+       << "{"
+       << "using namespace " << db << ";"
+       << endl;
+
+    if (poly)
+    {
+      // Resolve type information if we are doing indirect calls.
+      //
+      os << "if (pi == 0)" // Top-level call.
+         << "{"
+         << "const std::type_info& t (typeid (obj));";
+
+      if (!abst)
+        os << endl
+           << "if (t == info.type)" << endl
+           << "pi = &info;"
+           << "else" << endl;
+
+      os << "pi = &root_traits::map->find (t);"
+         << "}";
+    }
+
+    // If our poly-base has load sections, then call the base version
+    // first. Besides checking for sections, it will also initialize
+    // the id image.
+    //
+    if (poly_derived &&
+        buss->count (user_sections::count_total |
+                     user_sections::count_load  |
+                     user_sections::count_load_empty) != 0)
+    {
+      os << "if (base_traits::load (conn, obj, s, pi))" << endl
+         << "return true;"
+         << endl;
+    }
+    else
+    {
+      // Resolve extra statements if we are doing direct calls.
+      //
+      os << db << "::connection& c (static_cast<" << db <<
+        "::connection&> (conn));"
+         << "statements_type& sts (c.statement_cache ()." <<
+        "find_object ());";
+
+      if (!poly)
+        os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());";
+
+      os << endl;
+
+      // Initialize id image. This is not necessarily the root of the
+      // polymorphic hierarchy.
+      //
+      os << "id_image_type& i (sts.id_image ());";
+
+      if (!id_ma->synthesized)
+        os << "// From " << location_string (id_ma->loc, true) << endl;
+
+      os << "init (i, " <<  id_ma->translate ("obj") << ");"
+         << endl;
+
+      os << "binding& idb (sts.id_image_binding ());"
+         << "if (i.version != sts.id_image_version () || idb.version == 0)"
+         << "{"
+         << "bind (idb.bind, i);"
+         << "sts.id_image_version (i.version);"
+         << "idb.version++;";
+      if (opt != 0)
+        os << "sts.optimistic_id_image_binding ().version++;";
+      os << "}";
+    }
+
+    // Dispatch.
+    //
+    bool e (false);
+    for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i)
+    {
+      // Skip special sections.
+      //
+      if (i->special == user_section::special_version)
+        continue;
+
+      if (i->load == user_section::load_eager)
+        continue;
+
+      // Overridden sections are handled by the base.
+      //
+      if (poly_derived && i->base != 0)
+        continue;
+
+      data_member& m (*i->member);
+
+      // Section access is always by reference.
+      //
+      member_access& ma (m.get ("get"));
+      if (!ma.synthesized)
+        os << "// From " << location_string (ma.loc, true) << endl;
+
+      if (e)
+        os << "else ";
+      else
+        e = true;
+
+      os << "if (&s == &" << ma.translate ("obj") << ")" << endl;
+
+      if (!poly)
+        os << public_name (m) << "_traits::load (esc, obj);";
+      else
+      {
+        // If this is an empty section, then there may not be any
+        // overrides.
+        //
+        os << "{"
+           << "info_type::section_load sl (" <<
+          "pi->find_section_load (" << i->index << "UL));";
+
+        if (i->load_empty ())
+          os << "if (sl != 0)" << endl;
+
+        os << "sl (conn, obj, true);"
+           << "}";
+      }
+    }
+
+    os << "else" << endl
+       << "return false;"
+       << endl
+       << "return true;"
+       << "}";
+  }
+
+  // update (section) [non-thunk version]
+  //
+  if (uss.count (user_sections::count_new   |
+                 user_sections::count_update  |
+                 (poly ? user_sections::count_update_empty : 0)) != 0)
+  {
+    os << "bool " << traits << "::" << endl
+       << "update (connection& conn, const object_type& obj, " <<
+      "const section& s" << (poly ? ", const info_type* pi" : "") << ")"
+       << "{"
+       << "using namespace " << db << ";"
+       << endl;
+
+    if (poly)
+    {
+      // Resolve type information if we are doing indirect calls.
+      //
+      os << "if (pi == 0)" // Top-level call.
+         << "{"
+         << "const std::type_info& t (typeid (obj));";
+
+      if (!abst)
+        os << endl
+           << "if (t == info.type)" << endl
+           << "pi = &info;"
+           << "else" << endl;
+
+      os << "pi = &root_traits::map->find (t);"
+         << "}";
+    }
+
+    // If our poly-base has update sections, then call the base version
+    // first. Besides checking for sections, it will also initialize the
+    // id image.
+    //
+    if (poly_derived &&
+        buss->count (user_sections::count_total  |
+                     user_sections::count_update |
+                     user_sections::count_update_empty) != 0)
+    {
+      os << "if (base_traits::update (conn, obj, s, pi))" << endl
+         << "return true;"
+         << endl;
+    }
+    else
+    {
+      // Resolve extra statements if we are doing direct calls.
+      //
+      os << db << "::connection& c (static_cast<" << db <<
+        "::connection&> (conn));"
+         << "statements_type& sts (c.statement_cache ()." <<
+        "find_object ());";
+
+      if (!poly)
+        os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());";
+
+      os << endl;
+
+      // Initialize id image. This is not necessarily the root of the
+      // polymorphic hierarchy.
+      //
+      if (opt != 0)
+      {
+        if (!opt_ma_get->synthesized)
+          os << "// From " << location_string (opt_ma_get->loc, true) << endl;
+
+        os << "const version_type& v (" << endl
+           << opt_ma_get->translate ("obj") << ");";
+      }
+
+      os << "id_image_type& i (sts.id_image ());";
+
+      if (!id_ma->synthesized)
+        os << "// From " << location_string (id_ma->loc, true) << endl;
+
+      os << "init (i, " <<  id_ma->translate ("obj") <<
+        (opt != 0 ? ", &v" : "") << ");"
+         << endl;
+
+      os << "binding& idb (sts.id_image_binding ());"
+         << "if (i.version != sts.id_image_version () || idb.version == 0)"
+         << "{"
+         << "bind (idb.bind, i);"
+         << "sts.id_image_version (i.version);"
+         << "idb.version++;";
+      if (opt != 0)
+        os << "sts.optimistic_id_image_binding ().version++;";
+      os << "}";
+    }
+
+    // Dispatch.
+    //
+    bool e (false);
+    for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i)
+    {
+      // Skip special sections.
+      //
+      if (i->special == user_section::special_version)
+        continue;
+
+      // Overridden sections are handled by the base.
+      //
+      if (poly_derived && i->base != 0)
+        continue;
+
+      // Skip read-only sections. Polymorphic sections are always
+      // (potentially) read-write.
+      //
+      if (!poly && i->update_empty ())
+        continue;
+
+      data_member& m (*i->member);
+
+      // Section access is always by reference.
+      //
+      member_access& ma (m.get ("get"));
+      if (!ma.synthesized)
+        os << "// From " << location_string (ma.loc, true) << endl;
+
+      if (e)
+        os << "else ";
+      else
+        e = true;
+
+      os << "if (&s == &" << ma.translate ("obj") << ")";
+
+      if (!poly)
+        os << public_name (m) << "_traits::update (esc, obj);";
+      else
+      {
+        // If this is a readonly section, then there may not be any
+        // overrides.
+        //
+        os << "{"
+           << "info_type::section_update su (" <<
+          "pi->find_section_update (" << i->index << "UL));";
+
+        if (i->update_empty ())
+        {
+          // For optimistic section, also check that we are not the
+          // final override, since otherwise we will increment the
+          // version without updating anything.
+          //
+          if (i->optimistic ())
+            os << "if (su != 0 && su != info.sections[" << i->index <<
+              "UL].update)" << endl;
+          else
+            os << "if (su != 0)" << endl;
+        }
+
+        os << "su (conn, obj);"
+           << "}";
+      }
+    }
+
+    os << "else" << endl
+       << "return false;"
+       << endl
+       << "return true;"
+       << "}";
+  }
+
   // find_ ()
   //
   if (id != 0)
@@ -2319,8 +3013,10 @@ traverse_object (type& c)
        << "{"
        << "bind (idb.bind, i);"
        << "sts.id_image_version (i.version);"
-       << "idb.version++;"
-       << "}";
+       << "idb.version++;";
+    if (opt != 0)
+      os << "sts.optimistic_id_image_binding ().version++;";
+    os << "}";
 
     if (poly_derived && !abst)
       os << "}";
@@ -2411,38 +3107,113 @@ traverse_object (type& c)
     os << "}";
   }
 
-  // load_() containers
+  // load_()
+  //
+  // Load containers, reset/reload sections.
   //
-  if (containers || poly_derived)
+  if (poly_derived ||
+      load_containers ||
+      uss.count (user_sections::count_new  |
+                 user_sections::count_load |
+                 (poly ? user_sections::count_load_empty : 0)) != 0)
   {
     os << "void " << traits << "::" << endl
        << "load_ (";
 
     if (poly && !poly_derived)
-      os << "base_statements_type& sts, ";
+      os << "base_statements_type& sts," << endl;
     else
-      os << "statements_type& sts, ";
+      os << "statements_type& sts," << endl;
 
-    os << "object_type& obj";
+    os << "object_type& obj," << endl
+       << "bool reload";
 
     if (poly_derived)
-      os << ", std::size_t d";
+      os << "," << endl
+         << "std::size_t d";
 
     os << ")"
-       << "{";
+       << "{"
+       << "ODB_POTENTIALLY_UNUSED (reload);"
+       << endl;
 
     if (poly_derived)
       os << "if (--d != 0)" << endl
-         << "base_traits::load_ (sts.base_statements (), obj" <<
+         << "base_traits::load_ (sts.base_statements (), obj, reload" <<
         (poly_base != poly_root ? ", d" : "") << ");"
          << endl;
 
-    if (containers)
+    if (load_containers ||
+        (!poly && uss.count (user_sections::count_new |
+                             user_sections::count_load) != 0))
+      os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());"
+         << endl;
+
+    if (load_containers)
     {
-      instance t (container_calls::load_call);
+      instance t (container_calls::load_call, &main_section);
       t->traverse (c);
     }
 
+    for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i)
+    {
+      // Skip special sections.
+      //
+      if (i->special == user_section::special_version)
+        continue;
+
+      // Skip overridden sections; they are handled by the base.
+      //
+      if (i->base != 0 && poly_derived)
+        continue;
+
+      data_member& m (*i->member);
+
+      // Section access is always by reference.
+      //
+      member_access& ma (m.get ("get"));
+      if (!ma.synthesized)
+        os << "// From " << location_string (ma.loc, true) << endl;
+
+      if (i->load == user_section::load_eager)
+      {
+        // Mark an eager section as loaded.
+        //
+        os << ma.translate ("obj") << ".reset (true, false);"
+           << endl;
+        continue;
+      }
+
+      os << "if (reload)"
+         << "{"
+        // Reload sections that have been loaded, clear change state.
+        //
+         << "if (" << ma.translate ("obj") << ".loaded ())"
+         << "{";
+
+      if (!poly)
+        os << public_name (m) << "_traits::load (esc, obj);";
+      else
+      {
+        os << "info_type::section_load sl (" << endl
+           << "root_traits::map->find (typeid (obj)).find_section_load (" <<
+          i->index << "UL));";
+
+        if (i->load_empty ())
+          os << "if (sl != 0)" << endl;
+
+        os << "sl (sts.connection (), obj, true);";
+      }
+
+      os << ma.translate ("obj") << ".reset (true, false);"
+         << "}"
+         << "}"
+         << "else" << endl
+        // Reset to unloaded, unchanged state.
+         << ma.translate ("obj") << ".reset ();"
+         << endl;
+    }
+
     os << "}";
   }
 
@@ -2494,7 +3265,7 @@ traverse_object (type& c)
     if (empty_depth != 0)
       os << "}";
 
-    os << "load_ (sts, obj, d);"
+    os << "load_ (sts, obj, false, d);"
        << "}";
   }
 
@@ -2959,6 +3730,20 @@ traverse_object (type& c)
 
       os << "," << endl
          << "&" << traits << "::erase";
+
+      // Sections.
+      //
+      if (uss.count (user_sections::count_total |
+                     user_sections::count_load  |
+                     (poly ? user_sections::count_load_empty : 0)) != 0)
+        os << "," << endl
+           << "&" << traits << "::load";
+
+      if (uss.count (user_sections::count_total  |
+                     user_sections::count_update |
+                     (poly ? user_sections::count_update_empty : 0)) != 0)
+        os << "," << endl
+           << "&" << traits << "::update";
     }
 
     if (options.generate_query ())
diff --git a/odb/relational/source.hxx b/odb/relational/source.hxx
index d27110e..9dbb4e9 100644
--- a/odb/relational/source.hxx
+++ b/odb/relational/source.hxx
@@ -79,8 +79,9 @@ namespace relational
 
       object_columns (statement_kind sk,
                       statement_columns& sc,
-                      query_parameters* param = 0)
-          : object_columns_base (true, true),
+                      query_parameters* param = 0,
+                      object_section* section = 0)
+          : object_columns_base (true, true, section),
             sk_ (sk), sc_ (sc), param_ (param), depth_ (1)
       {
       }
@@ -88,8 +89,9 @@ namespace relational
       object_columns (std::string const& table_qname,
                       statement_kind sk,
                       statement_columns& sc,
-                      size_t depth = 1)
-          : object_columns_base (true, true),
+                      size_t depth = 1,
+                      object_section* section = 0)
+          : object_columns_base (true, true, section),
             sk_ (sk),
             sc_ (sc),
             param_ (0),
@@ -98,6 +100,24 @@ namespace relational
       {
       }
 
+      virtual bool
+      section_test (data_member_path const& mp)
+      {
+        object_section& s (section (mp));
+
+        // Include eager loaded members into the main section for
+        // SELECT statements. Also include optimistic version into
+        // section's SELECT and UPDATE statements.
+        //
+        return section_ == 0 ||
+          *section_ == s ||
+          (sk_ == statement_select &&
+           *section_ == main_section &&
+           !s.separate_load ()) ||
+          (version (mp) &&
+           (sk_ == statement_update || sk_ == statement_select));
+      }
+
       virtual void
       traverse_object (semantics::class_& c)
       {
@@ -477,56 +497,101 @@ namespace relational
           : object_columns_base (true, true),
             obj_ (obj),
             depth_ (depth),
+            section_ (0),
             alias_ (alias),
             prefix_ (prefix),
             suffix_ (suffix)
       {
+        init ();
+      }
+
+      polymorphic_object_joins (semantics::class_& obj,
+                                size_t depth,
+                                user_section& section)
+          : object_columns_base (true, true),
+            obj_ (obj),
+            depth_ (depth),
+            section_ (§ion),
+            suffix_ ("\n")
+      {
+        init ();
+      }
+
+      void
+      init ()
+      {
         // Get the table and id columns.
         //
         table_ = alias_.empty ()
-          ? table_qname (obj)
-          : quote_id (alias_ + "_" + table_name (obj).uname ());
+          ? table_qname (obj_)
+          : quote_id (alias_ + "_" + table_name (obj_).uname ());
 
-        cols_->traverse (*id_member (obj));
+        cols_->traverse (*id_member (obj_));
       }
 
       virtual void
       traverse_object (semantics::class_& c)
       {
-        std::ostringstream cond;
+        // If section is specified, skip bases that don't add anything
+        // to load.
+        //
+        bool skip (false), stop (false);
+        if (section_ != 0)
+        {
+          skip = true;
+
+          if (section_->object == &c)
+          {
+            user_section& s (*section_);
 
-        qname table (table_name (c));
-        string alias (alias_.empty ()
-                      ? quote_id (table)
-                      : quote_id (alias_ + "_" + table.uname ()));
+            if (s.total != 0 || s.optimistic ())
+              skip = false;
 
-        for (object_columns_list::iterator b (cols_->begin ()), i (b);
-             i != cols_->end ();
-             ++i)
-        {
-          if (i != b)
-            cond << " AND ";
+            section_ = s.base; // Move to the next base.
 
-          string qn (quote_id (i->name));
-          cond << alias << '.' << qn << '=' << table_ << '.' << qn;
+            if (section_ == 0)
+              stop = true; // Stop at this base if there are no more overrides.
+          }
         }
 
-        string line (" LEFT JOIN " + quote_id (table));
+        if (!skip)
+        {
+          std::ostringstream cond;
+
+          qname table (table_name (c));
+          string alias (alias_.empty ()
+                        ? quote_id (table)
+                        : quote_id (alias_ + "_" + table.uname ()));
+
+          for (object_columns_list::iterator b (cols_->begin ()), i (b);
+               i != cols_->end ();
+               ++i)
+          {
+            if (i != b)
+              cond << " AND ";
+
+            string qn (quote_id (i->name));
+            cond << alias << '.' << qn << '=' << table_ << '.' << qn;
+          }
+
+          string line (" LEFT JOIN " + quote_id (table));
 
-        if (!alias_.empty ())
-          line += (need_alias_as ? " AS " : " ") + alias;
+          if (!alias_.empty ())
+            line += (need_alias_as ? " AS " : " ") + alias;
 
-        line += " ON " + cond.str ();
+          line += " ON " + cond.str ();
 
-        os << prefix_ << strlit (line) << suffix_;
+          os << prefix_ << strlit (line) << suffix_;
+        }
 
-        if (--depth_ != 0)
+        if (!stop && --depth_ != 0)
           inherits (c);
       }
 
     private:
       semantics::class_& obj_;
       size_t depth_;
+      user_section* section_;
       string alias_;
       string prefix_;
       string suffix_;
@@ -540,8 +605,11 @@ namespace relational
 
       //@@ context::{cur,top}_object; might have to be created every time.
       //
-      object_joins (semantics::class_& scope, bool query, size_t depth = 1)
-          : object_columns_base (true, true),
+      object_joins (semantics::class_& scope,
+                    bool query,
+                    size_t depth,
+                    object_section* section = 0)
+          : object_columns_base (true, true, section),
             query_ (query),
             depth_ (depth),
             table_ (table_qname (scope)),
@@ -550,6 +618,18 @@ namespace relational
         id_cols_->traverse (id_);
       }
 
+      virtual bool
+      section_test (data_member_path const& mp)
+      {
+        object_section& s (section (mp));
+
+        // Include eager loaded members into the main section.
+        //
+        return section_ == 0 ||
+          *section_ == s ||
+          (*section_ == main_section && !s.separate_load ());
+      }
+
       virtual void
       traverse_object (semantics::class_& c)
       {
@@ -816,9 +896,12 @@ namespace relational
     {
       typedef bind_member base;
 
+      // NULL section means we are generating object bind().
+      //
       bind_member (string const& var = string (),
-                   string const& arg = string ())
-          : member_base (var, 0, string (), string ()),
+                   string const& arg = string (),
+                   object_section* section = 0)
+          : member_base (var, 0, string (), string (), section),
             arg_override_ (arg)
       {
       }
@@ -855,6 +938,11 @@ namespace relational
         if (container (mi))
           return false;
 
+        // Treat version as present in every section.
+        //
+        if (section_ != 0 && !version (mi.m) && *section_ != section (mi.m))
+          return false;
+
         // Ignore polymorphic id references; they are bound in a special
         // way.
         //
@@ -869,12 +957,20 @@ namespace relational
 
         if (var_override_.empty ())
         {
+          if (section_ == 0 && separate_load (mi.m) && inverse (mi.m))
+            return false;
+
           os << "// " << mi.m.name () << endl
              << "//" << endl;
 
+          // Order of these tests is important.
+          //
           if (!insert_send_auto_id && id (mi.m) && auto_ (mi.m))
             os << "if (sk != statement_insert && sk != statement_update)"
                << "{";
+          else if (section_ == 0 && separate_load (mi.m))
+            os << "if (sk == statement_insert)"
+               << "{";
           else if (inverse (mi.m, key_prefix_) || version (mi.m))
             os << "if (sk == statement_select)"
                << "{";
@@ -887,7 +983,8 @@ namespace relational
 
             if (id (mi.m) ||
                 readonly (mi.m) ||
-                ((c = composite (mi.t)) && readonly (*c)))
+                ((c = composite (mi.t)) && readonly (*c)) ||
+                (section_ == 0 && separate_update (mi.m)))
               os << "if (sk != statement_update)"
                  << "{";
           }
@@ -946,6 +1043,8 @@ namespace relational
           //
           if (!insert_send_auto_id && id (mi.m) && auto_ (mi.m))
             block = true;
+          else if (section_ == 0 && separate_load (mi.m))
+            block = true;
           else if (inverse (mi.m, key_prefix_) || version (mi.m))
             block = true;
           else if (!readonly (*context::top_object))
@@ -954,7 +1053,8 @@ namespace relational
 
             if (id (mi.m) ||
                 readonly (mi.m) ||
-                ((c = composite (mi.t)) && readonly (*c)))
+                ((c = composite (mi.t)) && readonly (*c)) ||
+                (section_ == 0 && separate_update (mi.m)))
               block = true;
           }
 
@@ -1014,42 +1114,33 @@ namespace relational
 
         column_count_type const& cc (column_count (c));
 
-        os << "n += " << cc.total << "UL";
+        os << "n += ";
 
-        // select = total
-        // insert = total - inverse - optimistic_managed
-        // update = total - inverse - optimistic_managed - id - readonly
+        // select = total - separate_load
+        // insert = total - inverse - optimistic_managed - id(auto & !sending)
+        // update = total - inverse - optimistic_managed - id - readonly -
+        //  separate_update
         //
-        if (cc.inverse != 0 ||
-            cc.optimistic_managed != 0 ||
-            (!ro && (cc.id != 0 || cc.readonly != 0)))
-        {
-          os << " - (" << endl
-             << "sk == statement_select ? 0 : ";
-
-          if (cc.inverse != 0 || cc.optimistic_managed != 0)
-            os << (cc.inverse + cc.optimistic_managed) << "UL";
-
-          if (!ro && (cc.id != 0 || cc.readonly != 0))
-          {
-            if (cc.inverse != 0 || cc.optimistic_managed != 0)
-              os << " + ";
-
-            os << "(" << endl
-               << "sk == statement_insert ? ";
-
-            if (insert_send_auto_id || !auto_ (*id_member (c)))
-              os << "0";
-            else
-              os << cc.id << "UL";
-
-            os << " : " << cc.id + cc.readonly << "UL)";
-          }
-
-          os << ")";
-        }
-
-        os << ";";
+        size_t select (cc.total - cc.separate_load);
+        size_t insert (cc.total - cc.inverse - cc.optimistic_managed);
+        size_t update (insert - cc.id - cc.readonly - cc.separate_update);
+
+        semantics::data_member* id;
+        if (!insert_send_auto_id && (id = id_member (c)) != 0 && auto_ (*id))
+          insert -= cc.id;
+
+        if (select == insert && insert == update)
+          os << select << "UL;";
+        else if (select != insert && insert == update)
+          os << "sk == statement_select ? " << select << "UL : " <<
+            insert << "UL;";
+        else if (select == insert && insert != update)
+          os << "sk == statement_update ? " << update << "UL : " <<
+            select << "UL;";
+        else
+          os << "sk == statement_select ? " << select << "UL : " <<
+            "sk == statement_insert ? " << insert << "UL : " <<
+            update << "UL;";
 
         if (check)
           os << "}";
@@ -1066,8 +1157,11 @@ namespace relational
     {
       typedef grow_member base;
 
-      grow_member (size_t& index, string const& var = string ())
-          : member_base (var, 0, string (), string ()), index_ (index)
+      grow_member (size_t& index,
+                   string const& var = string (),
+                   user_section* section = 0)
+          : member_base (var, 0, string (), string (), section),
+            index_ (index)
       {
       }
 
@@ -1131,8 +1225,9 @@ namespace relational
       typedef init_image_member base;
 
       init_image_member (string const& var = string (),
-                         string const& member = string ())
-          : member_base (var, 0, string (), string ()),
+                         string const& member = string (),
+                         user_section* section = 0)
+          : member_base (var, 0, string (), string (), section),
             member_override_ (member)
       {
       }
@@ -1182,6 +1277,9 @@ namespace relational
         if (container (mi) || inverse (mi.m, key_prefix_))
           return false;
 
+        if (section_ != 0 && *section_ != section (mi.m))
+          return false;
+
         // Ignore polymorphic id references; they are initialized in a
         // special way.
         //
@@ -1221,8 +1319,15 @@ namespace relational
 
             if (id (mi.m) ||
                 readonly (mi.m) ||
+                (section_ == 0 && separate_update (mi.m)) ||
                 ((c = composite (mi.t)) && readonly (*c))) // Can't be id.
-              os << "if (sk == statement_insert)";
+            {
+              // If we are generating section init(), then sk can only be
+              // statement_update.
+              //
+              if (section_ == 0)
+                os << "if (sk == statement_insert)";
+            }
           }
 
           os << "{";
@@ -1478,8 +1583,9 @@ namespace relational
 
       init_value_member (string const& member = string (),
                          string const& var = string (),
-                         bool ignore_implicit_discriminator = true)
-          : member_base (var, 0, string (), string ()),
+                         bool ignore_implicit_discriminator = true,
+                         user_section* section = 0)
+          : member_base (var, 0, string (), string (), section),
             member_override_ (member),
             ignore_implicit_discriminator_ (ignore_implicit_discriminator)
       {
@@ -1529,6 +1635,9 @@ namespace relational
         if (container (mi))
           return false;
 
+        if (section_ != 0 && *section_ != section (mi.m))
+          return false;
+
         // Ignore polymorphic id references; they are initialized in a
         // special way.
         //
@@ -1549,6 +1658,11 @@ namespace relational
         }
         else
         {
+          // Ignore separately loaded members.
+          //
+          if (section_ == 0 && separate_load (mi.m))
+            return false;
+
           os << "// " << mi.m.name () << endl
              << "//" << endl
              << "{";
@@ -2335,7 +2449,7 @@ namespace relational
              << "//" << endl
              << "if (id != 0)" << endl
              << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));"
-             << "n += id_size;"
+             << "n += id_size;" // Not in if for "id unchanged" optimization.
              << endl;
 
           // We don't need to update the bind index since this is the
@@ -2403,7 +2517,7 @@ namespace relational
              << "//" << endl
              << "if (id != 0)" << endl
              << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));"
-             << "n += id_size;"
+             << "n += id_size;" // Not in if for "id unchanged" optimization.
              << endl;
 
           switch (ck)
@@ -2491,7 +2605,7 @@ namespace relational
              << "//" << endl
              << "if (id != 0)" << endl
              << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));"
-             << "n += id_size;"
+             << "n += id_size;" // Not in if for "id unchanged" optimization.
              << endl;
 
           // We don't need to update the bind index since this is the
@@ -3256,7 +3370,7 @@ namespace relational
       semantics::class_& c_;
     };
 
-    // Container statement cache members.
+    // Extra statement cache members for containers.
     //
     struct container_cache_members: object_members_base, virtual context
     {
@@ -3316,6 +3430,54 @@ namespace relational
       bool first_;
     };
 
+    // Extra statement cache members for sections.
+    //
+    struct section_cache_members: virtual context
+    {
+      typedef section_cache_members base;
+
+      virtual void
+      traverse (user_section& s)
+      {
+        string traits (public_name (*s.member) + "_traits");
+
+        os << db << "::" << "section_statements< " <<
+          class_fq_name (*s.object) << ", " << traits << " > " <<
+          s.member->name () << ";";
+      }
+    };
+
+    struct section_cache_init_members: virtual context
+    {
+      typedef section_cache_init_members base;
+
+      section_cache_init_members (bool first): first_ (first) {}
+
+      virtual void
+      traverse (user_section& s)
+      {
+        if (first_)
+        {
+          os << endl
+             << ": ";
+          first_ = false;
+        }
+        else
+          os << "," << endl
+             << "  ";
+
+        os << s.member->name () << " (c, im, id, idv";
+        extra_members ();
+        os << ")";
+      }
+
+      virtual void
+      extra_members () {}
+
+    protected:
+      bool first_;
+    };
+
     // Calls for container members.
     //
     struct container_calls: object_members_base, virtual context
@@ -3328,17 +3490,33 @@ namespace relational
         load_call,
         update_call,
         erase_obj_call,
-        erase_id_call
+        erase_id_call,
+        section_call
       };
 
-      container_calls (call_type call)
-          : object_members_base (true, false, true),
+      container_calls (call_type call, object_section* section = 0)
+          : object_members_base (true, false, true, false, section),
             call_ (call),
             obj_prefix_ ("obj"),
             modifier_ (0)
       {
       }
 
+      virtual bool
+      section_test (data_member_path const& mp)
+      {
+        object_section& s (section (mp));
+
+        // Include eager loaded members into the main section for
+        // load calls.
+        //
+        return section_ == 0 ||
+          *section_ == s ||
+          (call_ == load_call &&
+           *section_ == main_section &&
+           !s.separate_load ());
+      }
+
       virtual void
       traverse_composite_wrapper (semantics::data_member* m,
                                   semantics::class_& c,
@@ -3428,6 +3606,7 @@ namespace relational
         // In certain cases we don't need to do anything.
         //
         if ((call_ != load_call && inverse) ||
+            (call_ == section_call && !smart) ||
             (call_ == update_call && readonly (member_path_, member_scope_)))
           return;
 
@@ -3522,21 +3701,21 @@ namespace relational
           {
             os << traits << "::persist (" << endl
                << var << "," << endl
-               << "sts.container_statment_cache ()." << sts_name << ");";
+               << "esc." << sts_name << ");";
             break;
           }
         case load_call:
           {
             os << traits << "::load (" << endl
                << var << "," << endl
-               << "sts.container_statment_cache ()." << sts_name << ");";
+               << "esc." << sts_name << ");";
             break;
           }
         case update_call:
           {
             os << traits << "::update (" << endl
                << var << "," << endl
-               << "sts.container_statment_cache ()." << sts_name << ");";
+               << "esc." << sts_name << ");";
             break;
           }
         case erase_obj_call:
@@ -3546,7 +3725,7 @@ namespace relational
             if (smart)
               os << "&" << var << "," << endl;
 
-            os << "sts.container_statment_cache ()." << sts_name << ");"
+            os << "esc." << sts_name << ");"
                << endl;
             break;
           }
@@ -3557,10 +3736,17 @@ namespace relational
             if (smart)
               os << "0," << endl;
 
-            os << "sts.container_statment_cache ()." << sts_name << ");"
+            os << "esc." << sts_name << ");"
                << endl;
             break;
           }
+        case section_call:
+          {
+            os << "if (" << traits << "::container_traits_type::changed (" <<
+              var << "))" << endl
+               << "s.reset (true, true);"; // loaded, changed
+            break;
+          }
         }
 
         if (call_ != erase_id_call && (call_ != erase_obj_call || smart))
@@ -3592,155 +3778,1093 @@ namespace relational
       member_access* modifier_;
     };
 
-    // Output a list of parameters for the persist statement.
     //
-    struct persist_statement_params: object_columns_base, virtual context
+    //
+    struct section_traits: traversal::class_, virtual context
     {
-      typedef persist_statement_params base;
+      typedef section_traits base;
 
-      persist_statement_params (string& params, query_parameters& qp)
-          : params_ (params), qp_ (qp)
+      section_traits (semantics::class_& c)
+          : c_ (c),
+            scope_ ("access::object_traits_impl< " + class_fq_name (c) +
+                    ", id_" + db.string () + " >")
       {
       }
 
+      // Additional code that need to be executed following the call to
+      // init_value().
+      //
       virtual void
-      traverse_pointer (semantics::data_member& m, semantics::class_& c)
+      init_value_extra ()
       {
-        if (!inverse (m, key_prefix_))
-          object_columns_base::traverse_pointer (m, c);
       }
 
-      virtual bool
-      traverse_column (semantics::data_member& m, string const&, bool first)
+      virtual void
+      process_statement_columns (statement_columns&, statement_kind)
       {
-        string p;
-
-        if (version (m))
-          p = version_value (m);
-        else if (context::id (m) && auto_ (m)) // Only simple id can be auto.
-          p = qp_.auto_id ();
-        else
-          p = qp_.next ();
-
-        if (!p.empty ())
-        {
-          if (!first)
-            params_ += ',';
-
-          params_ += (p != "DEFAULT" ? convert_to (p, column_type (), m) : p);
-        }
-
-        return !p.empty ();
       }
 
-      virtual string
-      version_value (semantics::data_member&)
+      virtual void
+      section_extra (user_section&)
       {
-        return "1";
       }
 
-    private:
-      string& params_;
-      query_parameters& qp_;
-    };
-
-    //
-    //
-    struct class_: traversal::class_, virtual context
-    {
-      typedef class_ base;
-
-      class_ ()
-          : query_columns_type_ (false, false, false),
-            view_query_columns_type_ (false),
-            grow_base_ (index_),
-            grow_member_ (index_),
-            grow_version_member_ (index_, "version_"),
-            grow_discriminator_member_ (index_, "discriminator_"),
-            bind_id_member_ ("id_"),
-            bind_version_member_ ("version_"),
-            bind_discriminator_member_ ("discriminator_"),
-            init_id_image_member_ ("id_", "id"),
-            init_version_image_member_ ("version_", "(*v)"),
-            init_id_value_member_ ("id"),
-            init_version_value_member_ ("v"),
-            init_named_version_value_member_ ("v", "version_"),
-            init_discriminator_value_member_ ("d", "", false),
-            init_named_discriminator_value_member_ (
-              "d", "discriminator_", false)
+      // Returning "1" means increment by one.
+      //
+      virtual string
+      optimistic_version_increment (semantics::data_member&)
       {
-        init ();
+        return "1";
       }
 
-      class_ (class_ const&)
-          : root_context (), //@@ -Wextra
-            context (),
-            query_columns_type_ (false, false, false),
-            view_query_columns_type_ (false),
-            grow_base_ (index_),
-            grow_member_ (index_),
-            grow_version_member_ (index_, "version_"),
-            grow_discriminator_member_ (index_, "discriminator_"),
-            bind_id_member_ ("id_"),
-            bind_version_member_ ("version_"),
-            bind_discriminator_member_ ("discriminator_"),
-            init_id_image_member_ ("id_", "id"),
-            init_version_image_member_ ("version_", "(*v)"),
-            init_id_value_member_ ("id"),
-            init_version_value_member_ ("v"),
-            init_named_version_value_member_ ("v", "version_"),
-            init_discriminator_value_member_ ("d", "", false),
-            init_named_discriminator_value_member_ (
-              "d", "discriminator_", false)
+      virtual void
+      update_statement_extra (user_section&)
       {
-        init ();
       }
 
-      void
-      init ()
+      virtual void
+      traverse (user_section& s)
       {
-        if (generate_grow)
-        {
-          grow_base_inherits_ >> grow_base_;
-          grow_member_names_ >> grow_member_;
-        }
+        using semantics::class_;
+        using semantics::data_member;
 
-        bind_base_inherits_ >> bind_base_;
-        bind_member_names_ >> bind_member_;
+        data_member& m (*s.member);
 
-        init_image_base_inherits_ >> init_image_base_;
-        init_image_member_names_ >> init_image_member_;
+        class_* poly_root (polymorphic (c_));
+        bool poly (poly_root != 0);
+        bool poly_derived (poly && poly_root != &c_);
+        class_* poly_base (poly_derived ? &polymorphic_base (c_) : 0);
 
-        init_value_base_inherits_ >> init_value_base_;
-        init_value_member_names_ >> init_value_member_;
-      }
+        data_member* opt (optimistic (c_));
 
-      virtual void
-      init_auto_id (semantics::data_member&, // id member
-                    string const&)           // image variable prefix
-      {
-        if (insert_send_auto_id)
-          assert (false);
-      }
+        // Treat the special version update sections as abstract in reuse
+        // inheritance.
+        //
+        bool reuse_abst (!poly &&
+                         (abstract (c_) ||
+                          s.special == user_section::special_version));
 
-      virtual void
-      init_image_pre (type&)
-      {
-      }
+        bool load (s.total != 0 && s.separate_load ());
+        bool load_con (s.containers && s.separate_load ());
+        bool load_opt (s.optimistic () && s.separate_load ());
 
-      virtual void
-      init_value_extra ()
-      {
-      }
+        bool update (s.total != s.inverse + s.readonly); // Always separate.
+        bool update_con (s.readwrite_containers);
+        bool update_opt (s.optimistic () && (s.readwrite_containers || poly));
 
-      virtual void
-      traverse (type& c)
-      {
-        if (!options.at_once () && class_file (c) != unit.file ())
+        // Don't generate anything for empty sections.
+        //
+        if (!(load || load_con || load_opt ||
+              update || update_con || update_opt))
           return;
 
-        context::top_object = context::cur_object = &c;
-
+        // If we are adding a new section to a derived class in an optimistic
+        // polymorphic hierarchy, then pretend it inherits from the special
+        // version update section.
+        //
+        user_section* rs (0);
+        if (opt != 0)
+        {
+          // Skip overrides and get to the new section if polymorphic.
+          //
+          for (rs = &s; poly && rs->base != 0; rs = rs->base) ;
+
+          if (rs != 0)
+          {
+            if (rs->object != &opt->scope ())
+              rs->base = &(poly ? poly_root : &opt->scope ())->
+                get ("user-sections").back ();
+            else
+              rs = 0;
+          }
+        }
+
+        string name (public_name (m) + "_traits");
+        string scope (scope_ + "::" + name);
+
+        os << "// " << m.name () << endl
+           << "//" << endl
+           << endl;
+
+        // bind (id, image_type)
+        //
+        if (load || load_opt || update || update_opt)
+        {
+          os << "std::size_t " << scope << "::" << endl
+             << "bind (" << bind_vector << " b," << endl
+             << "const " << bind_vector << (reuse_abst ? "," : " id,") << endl
+             << "std::size_t" << (reuse_abst ? "," : " id_size,") << endl
+             << "image_type& i," << endl
+             << db << "::statement_kind sk)"
+             << "{"
+             << "ODB_POTENTIALLY_UNUSED (sk);"
+             << endl
+             << "using namespace " << db << ";"
+             << endl
+             << "std::size_t n (0);"
+             << endl;
+
+          // Bind reuse base. It is always first and we never ask it
+          // to bind id(+ver).
+          //
+          if (s.base != 0 && !poly_derived)
+          {
+            user_section& b (*s.base);
+
+            bool load (b.total != 0 && b.separate_load ());
+            bool load_opt (b.optimistic () && b.separate_load ());
+
+            bool update (b.total != b.inverse + b.readonly);
+
+            if (load || load_opt || update)
+              os << "// " << class_name (*b.object) << endl
+                 << "//" << endl
+                 << "n += object_traits_impl< " << class_fq_name (*b.object) <<
+                ", id_" << db << " >::" << public_name (*b.member) <<
+                "_traits::bind (" << endl
+                 << "b, 0, 0, i, sk);"
+                 << endl;
+          }
+
+          // Bind members.
+          //
+          {
+            instance bm ("", "", &s);
+            traversal::names n (*bm);
+            names (c_, n);
+          }
+
+          // Bind polymorphic image chain for the select statement.
+          //
+          if (s.base != 0 && poly_derived && s.separate_load ())
+          {
+            // Find the next base that has something to load, if any.
+            //
+            user_section* b (s.base);
+            string acc (".base");
+            for (class_* bo (poly_base);; bo = &polymorphic_base (*bo))
+            {
+              if (b->object == bo)
+              {
+                if (b->total != 0 || b->optimistic ())
+                  break;
+
+                b = b->base;
+                if (b == 0 || !polymorphic (*b->object))
+                {
+                  b = 0;
+                  break;
+                }
+              }
+              acc += "->base";
+            }
+
+            if (b != 0)
+              os << "// " << class_name (*b->object) << endl
+                 << "//" << endl
+                 << "if (sk == statement_select)" << endl
+                 << "n += object_traits_impl< " << class_fq_name (*b->object) <<
+                ", id_" << db << " >::" << public_name (*b->member) <<
+                "_traits::bind (" << endl
+                 << "b + n, 0, 0, *i" << acc << ", sk);"
+                 << endl;
+          }
+
+          if (!reuse_abst)
+            os << "// object_id" << endl
+               << "//" << endl
+               << "if (id != 0)" << endl
+               << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));"
+               << "n += id_size;" // Not in if for "id unchanged" optimization.
+               << endl;
+
+          os << "return n;"
+             << "}";
+        }
+
+        // grow ()
+        //
+        if (generate_grow && (load || load_opt))
+        {
+          os << "bool " << scope << "::" << endl
+             << "grow (image_type& i, " << truncated_vector << " t)"
+             << "{"
+             << "ODB_POTENTIALLY_UNUSED (i);"
+             << "ODB_POTENTIALLY_UNUSED (t);"
+             << endl
+             << "bool grew (false);"
+             << endl;
+
+          size_t index (0);
+
+          if (s.base != 0 && !poly_derived)
+          {
+            user_section& b (*s.base);
+
+            bool load (b.total != 0);
+            bool load_opt (b.optimistic ());
+
+            if (load || load_opt)
+            {
+              os << "// " << class_name (*b.object) << endl
+                 << "//" << endl
+                 << "grew = object_traits_impl< " << class_fq_name (*b.object) <<
+                ", id_" << db << " >::" << public_name (*b.member) <<
+                "_traits::grow (i, t);"
+                 << endl;
+
+              index += b.total + (load_opt ? 1 : 0);
+            }
+          }
+
+          {
+            user_section* ps (&s);
+            instance gm (index, "", ps);
+            traversal::names n (*gm);
+            names (c_, n);
+          }
+
+          // Grow polymorphic image chain.
+          //
+          if (s.base != 0 && poly_derived)
+          {
+            // Find the next base that has something to load, if any.
+            //
+            user_section* b (s.base);
+            string acc (".base");
+            size_t cols;
+            for (class_* bo (poly_base);; bo = &polymorphic_base (*bo))
+            {
+              if (b->object == bo)
+              {
+                cols = b->total + (b->optimistic () ? 1 : 0);
+                if (cols != 0)
+                  break;
+
+                b = b->base;
+                if (b == 0 || !polymorphic (*b->object))
+                {
+                  b = 0;
+                  break;
+                }
+              }
+              acc += "->base";
+            }
+
+            if (b != 0)
+              os << "// " << class_name (*b->object) << endl
+                 << "//" << endl
+                 << "if (object_traits_impl< " << class_fq_name (*b->object) <<
+                ", id_" << db << " >::" << public_name (*b->member) <<
+                "_traits::grow (" << endl
+                 << "*i" << acc << ", t + " << cols << "UL))" << endl
+                 << "i" << acc << "->version++;"
+                 << endl;
+          }
+
+          os << "return grew;" << endl
+             << "}";
+        }
+
+        // init (object, image)
+        //
+        if (load)
+        {
+          os << "void " << scope << "::" << endl
+             << "init (object_type& o, const image_type& i, database* db)"
+             << "{"
+             << "ODB_POTENTIALLY_UNUSED (db);"
+             << endl;
+
+          if (s.base != 0)
+          {
+            if (!poly_derived)
+            {
+              user_section& b (*s.base);
+
+              bool load (b.total != 0);
+
+              if (load)
+                os << "// " << class_name (*b.object) << endl
+                   << "//" << endl
+                   << "object_traits_impl< " << class_fq_name (*b.object) <<
+                  ", id_" << db << " >::" << public_name (*b.member) <<
+                  "_traits::init (o, i, db);"
+                   << endl;
+            }
+            else
+            {
+              // Find the next base that has something to load, if any.
+              //
+              user_section* b (s.base);
+              string acc (".base");
+              for (class_* bo (poly_base);; bo = &polymorphic_base (*bo))
+              {
+                if (b->object == bo)
+                {
+                  if (b->total != 0)
+                    break;
+
+                  b = b->base;
+                  if (b == 0 || !polymorphic (*b->object))
+                  {
+                    b = 0;
+                    break;
+                  }
+                }
+                acc += "->base";
+              }
+
+              if (b != 0)
+                os << "// " << class_name (*b->object) << endl
+                   << "//" << endl
+                   << "object_traits_impl< " << class_fq_name (*b->object) <<
+                  ", id_" << db << " >::" << public_name (*b->member) <<
+                  "_traits::init (" << endl
+                   << "o, *i" << acc << ", db);"
+                   << endl;
+            }
+          }
+
+          {
+            instance iv ("", "", true, &s);
+            traversal::names n (*iv);
+            names (c_, n);
+          }
+
+          os << "}";
+        }
+
+        // init (image, object)
+        //
+        if (update)
+        {
+          os << (generate_grow ? "bool " : "void ") << scope << "::" << endl
+             << "init (image_type& i, const object_type& o)"
+             << "{"
+             << "using namespace " << db << ";"
+             << endl
+             << "statement_kind sk (statement_insert);"
+             << "ODB_POTENTIALLY_UNUSED (sk);"
+             << endl;
+
+          // There is no call to init_image_pre() here (which calls the
+          // copy callback for some databases) since we are not going to
+          // touch any of the members that were loaded by query.
+
+          if (generate_grow)
+            os << "bool grew (false);"
+               << endl;
+
+          if (s.base != 0 && !poly_derived)
+          {
+            user_section& b (*s.base);
+
+            bool update (b.total != b.inverse + b.readonly);
+
+            if (update)
+              os << "// " << class_name (*b.object) << endl
+                 << "//" << endl
+                 << (generate_grow ? "grew = " : "") <<
+                "object_traits_impl< " << class_fq_name (*b.object) <<
+                ", id_" << db << " >::" << public_name (*b.member) <<
+                "_traits::init (i, o);"
+                 << endl;
+          }
+
+          {
+            instance ii ("", "", &s);
+            traversal::names n (*ii);
+            names (c_, n);
+          }
+
+          if (generate_grow)
+            os << "return grew;";
+
+          os << "}";
+        }
+
+        // The rest does not apply to reuse-abstract sections.
+        //
+        if (reuse_abst)
+        {
+          section_extra (s);
+          return;
+        }
+
+        // Statements.
+        //
+        qname table (table_name (c_));
+        string qtable (quote_id (table));
+
+        instance id_cols;
+        id_cols->traverse (*id_member (c_));
+
+        // select_statement
+        //
+        if (load || load_opt)
+        {
+          size_t depth (poly_derived ? polymorphic_depth (c_) : 1);
+
+          statement_columns sc;
+          {
+            statement_kind sk (statement_select); // Imperfect forwarding.
+            object_section* ps (&s);              // Imperfect forwarding.
+            instance t (qtable, sk, sc, depth, ps);
+            t->traverse (c_);
+            process_statement_columns (sc, statement_select);
+          }
+
+          os << "const char " << scope << "::" << endl
+             << "select_statement[] =" << endl
+             << strlit ("SELECT ") << endl;
+
+          for (statement_columns::const_iterator i (sc.begin ()),
+                 e (sc.end ()); i != e;)
+          {
+            string const& c (i->column);
+            os << strlit (c + (++i != e ? "," : "")) << endl;
+          }
+
+          os << strlit (" FROM " + qtable) << endl;
+
+          // Join polymorphic bases.
+          //
+          if (depth != 1 && s.base != 0)
+          {
+            size_t d (depth - 1);       //@@ (im)perfect forward.
+            user_section& bs (*s.base); //@@ (im)perfect forward.
+            instance j (c_, d, bs);
+            j->traverse (*poly_base);
+          }
+
+          // Join tables of inverse members belonging to this section.
+          //
+          {
+            bool f (false);          // @@ (im)perfect forwarding
+            object_section* ps (&s); // @@ (im)perfect forwarding
+            instance j (c_, f, depth, ps);
+            j->traverse (c_);
+          }
+
+          instance qp (table);
+          for (object_columns_list::iterator b (id_cols->begin ()), i (b);
+               i != id_cols->end (); ++i)
+          {
+            if (i != b)
+              os << endl;
+
+            os << strlit ((i == b ? " WHERE " : " AND ") +
+                          qtable + "." + quote_id (i->name) + "=" +
+                          convert_to (qp->next (), i->type, *i->member));
+          }
+
+          os << ";"
+             << endl;
+        }
+
+        // update_statement
+        //
+        if (update || update_opt)
+        {
+          instance qp (table);
+
+          statement_columns sc;
+          {
+            query_parameters* p (qp.get ());      // Imperfect forwarding.
+            statement_kind sk (statement_update); // Imperfect forwarding.
+            object_section* ps (&s);              // Imperfect forwarding.
+            instance t (sk, sc, p, ps);
+            t->traverse (c_);
+            process_statement_columns (sc, statement_update);
+          }
+
+          os << "const char " << scope << "::" << endl
+             << "update_statement[] =" << endl
+             << strlit ("UPDATE " + qtable + " SET ") << endl;
+
+          for (statement_columns::const_iterator i (sc.begin ()),
+                   e (sc.end ()); i != e;)
+          {
+            string const& c (i->column);
+            os << strlit (c + (++i != e ? "," : "")) << endl;
+          }
+
+          // This didn't work out: cannot change the identity column.
+          //
+          //if (sc.empty ())
+          //{
+          //  // We can end up with nothing to set if we need to "touch" a row
+          //  // in order to increment its optimistic concurrency version. In
+          //  // this case just do a dummy assignment based on the id column.
+          //  //
+          //  string const& c (quote_id (id_cols->begin ()->name));
+          //  os << strlit (c + "=" + c) << endl;
+          //}
+
+          update_statement_extra (s);
+
+          for (object_columns_list::iterator b (id_cols->begin ()), i (b);
+               i != id_cols->end (); ++i)
+          {
+            if (i != b)
+              os << endl;
+
+            os << strlit ((i == b ? " WHERE " : " AND ") +
+                          quote_id (i->name) + "=" +
+                          convert_to (qp->next (), i->type, *i->member));
+          }
+
+          if (s.optimistic ()) // Note: not update_opt.
+          {
+            os << endl
+               << strlit (" AND " + column_qname (*opt, column_prefix ()) +
+                          "=" + convert_to (qp->next (), *opt));
+          }
+
+          os << ";"
+             << endl;
+        }
+
+        // load ()
+        //
+        if (load || load_opt || load_con)
+        {
+          os << "void " << scope << "::" << endl
+             << "load (extra_statement_cache_type& esc, object_type& obj" <<
+            (poly ? ", bool top" : "") << ")"
+             << "{";
+
+          if (poly)
+            os << "ODB_POTENTIALLY_UNUSED (top);"
+               << endl;
+
+          // Load values, if any.
+          //
+          if (load || load_opt)
+          {
+            // The SELECT statement for the top override loads all the
+            // values.
+            //
+            if (poly)
+              os << "if (top)"
+                 << "{";
+
+            // Note that we don't use delayed load machinery here. While
+            // a section can definitely contain self-referencing pointers,
+            // loading such a pointer won't mess up the data members in the
+            // image that we care about. It also holds true for streaming
+            // result, since the bindings are different.
+
+            os << "using namespace " << db << ";"
+               << "using " << db << "::select_statement;" // Conflicts.
+               << endl
+               << "statements_type& sts (esc." << m.name () << ");"
+               << endl
+               << "image_type& im (sts.image ());"
+               << "binding& imb (sts.select_image_binding ());"
+               << endl;
+
+            // For the polymorphic case, instead of storing an array of
+            // versions as we do for objects, we will add all the versions
+            // up and use that as a cumulative image chain version. If you
+            // meditate a bit on that, you will realize that it will work
+            // (hint: versions can only increase).
+            //
+            string ver;
+            string ver_decl;
+
+            if (s.base != 0 && poly_derived)
+            {
+              ver = "imv";
+              ver_decl = "std::size_t imv (im.version";
+
+              user_section* b (s.base);
+              string acc ("im.base");
+              for (class_* bo (poly_base);; bo = &polymorphic_base (*bo))
+              {
+                if (b->object == bo)
+                {
+                  if (b->total != 0 || b->optimistic ())
+                    ver_decl += " +\n" + acc + "->version";
+
+                  b = b->base;
+                  if (b == 0 || !polymorphic (*b->object))
+                  {
+                    b = 0;
+                    break;
+                  }
+                }
+                acc += "->base";
+              }
+
+              ver_decl += ")";
+
+              os << ver_decl << ";"
+                 << endl;
+            }
+            else
+              ver = "im.version";
+
+            os << "if (" << ver << " != sts.select_image_version () ||" << endl
+               << "imb.version == 0)"
+               << "{"
+               << "bind (imb.bind, 0, 0, im, statement_select);"
+               << "sts.select_image_version (" << ver << ");"
+               << "imb.version++;"
+               << "}";
+
+            // Id binding is assumed initialized and bound.
+            //
+            os << "select_statement& st (sts.select_statement ());"
+               << "st.execute ();"
+               << "auto_result ar (st);"
+               << "select_statement::result r (st.fetch ());"
+               << endl;
+
+            os << "if (r == select_statement::no_data)" << endl
+               << "throw object_not_persistent ();"
+               << endl;
+
+            if (grow (c_, &s))
+            {
+              os << "if (r == select_statement::truncated)"
+                 << "{"
+                 << "if (grow (im, sts.select_image_truncated ()))" << endl
+                 << "im.version++;"
+                 << endl;
+
+              // The same logic as above.
+              //
+              if (s.base != 0 && poly_derived)
+                os << ver_decl << ";"
+                   << endl;
+
+              os << "if (" << ver << " != sts.select_image_version ())"
+                 << "{"
+                 << "bind (imb.bind, 0, 0, im, statement_select);"
+                 << "sts.select_image_version (" << ver << ");"
+                 << "imb.version++;"
+                 << "st.refetch ();"
+                 << "}"
+                 << "}";
+            }
+
+            if (opt != 0) // Not load_opt, we do it in poly-derived as well.
+            {
+              member_access& ma (opt->get ("get"));
+
+              if (!ma.synthesized)
+                os << "// From " << location_string (ma.loc, true) << endl;
+
+              os << "if (";
+
+              if (poly_derived)
+              {
+                os << "root_traits::version (*im.base";
+                for (class_* b (poly_base);
+                     b != poly_root;
+                     b = &polymorphic_base (*b))
+                  os << "->base";
+                os << ")";
+              }
+              else
+                os << "version (im)";
+
+              os << " != " << ma.translate ("obj") << ")" << endl
+                 << "throw object_changed ();"
+                 << endl;
+            }
+
+            if (load)
+            {
+              os << "init (obj, im, &sts.connection ().database ());";
+              init_value_extra (); // Stream results, etc.
+              os << endl;
+            }
+
+            if (poly)
+              os << "}"; // if (top)
+          }
+
+          // Call base to load its containers, if this is an override.
+          //
+          if (poly_derived && s.base != 0)
+          {
+            user_section* b (s.base);
+            for (class_* bo (poly_base);; bo = &polymorphic_base (*bo))
+            {
+              if (b->object == bo)
+              {
+                // If we don't have any values of our own but out base
+                // does, then allow it to load them.
+                //
+                if (b->containers ||
+                    (!load && (b->total != 0 || b->optimistic ())))
+                  break;
+
+                b = b->base;
+                if (b == 0 || !polymorphic (*b->object))
+                {
+                  b = 0;
+                  break;
+                }
+              }
+            }
+
+            // This one is tricky: ideally we would do a direct call to
+            // the base's load() (which may not be our immediate base,
+            // BTW) but there is no easy way to resolve base's extra
+            // statements from ours. So, instead, we are going to go
+            // via the dispatch machinery which requires a connection
+            // rather than statements. Not the most efficient way but
+            // simple.
+
+            // Find the "previous" override by starting the search from
+            // our base.
+            //
+            if (b != 0)
+            {
+              // Note that here we are using the base section index to
+              // handle the special version update base.
+              //
+              os << "info.base->find_section_load (" << b->index << "UL) (" <<
+                "esc." << m.name () << ".connection (), obj, " <<
+                // If we don't have any values of our own, then allow the
+                // base load its.
+                //
+                (load ? "false" : "top") << ");"
+                 << endl;
+            }
+          }
+
+          // Load our containers, if any.
+          //
+          if (s.containers)
+          {
+            instance t (container_calls::load_call, &s);
+            t->traverse (c_);
+          }
+
+          os << "}";
+        }
+
+        // update ()
+        //
+        if (update || update_opt || update_con)
+        {
+          os << "void " << scope << "::" << endl
+             << "update (extra_statement_cache_type& esc, " <<
+            "const object_type& obj" <<
+            (poly_derived && s.base != 0 ? ", bool base" : "") << ")"
+             << "{";
+
+          // Call base if this is an override.
+          //
+          if (poly_derived && s.base != 0)
+          {
+            user_section* b (s.base);
+            for (class_* bo (poly_base);; bo = &polymorphic_base (*bo))
+            {
+              if (b->object == bo)
+              {
+                if (b->total != b->inverse + b->readonly ||
+                    b->readwrite_containers ||
+                    (poly && b->optimistic ()))
+                  break;
+
+                b = b->base;
+                if (b == 0 || !polymorphic (*b->object))
+                {
+                  b = 0;
+                  break;
+                }
+              }
+            }
+
+            // The same (tricky) logic as in load(). Note that here we are
+            // using the base section index to handle the special version
+            // update base.
+            //
+            if (b != 0)
+              os << "if (base)" << endl
+                 << "info.base->find_section_update (" << b->index <<
+                "UL) (esc." << m.name () << ".connection (), obj);"
+                 << endl;
+            else
+              os << "ODB_POTENTIALLY_UNUSED (base);"
+                 << endl;
+          }
+
+          // Update values, if any.
+          //
+          if (update || update_opt)
+          {
+            os << "using namespace " << db << ";"
+               << endl
+               << "statements_type& sts (esc." << m.name () << ");"
+               << endl
+               << "image_type& im (sts.image ());"
+               << "const binding& id (sts.idv_binding ());" // id+version
+               << "binding& imb (sts.update_image_binding ());"
+               << endl;
+
+            if (update)
+            {
+              if (generate_grow)
+                os << "if (";
+
+              os << "init (im, obj)";
+
+              if (generate_grow)
+                os << ")" << endl
+                   << "im.version++";
+
+              os << ";"
+                 << endl;
+            }
+
+            os << "if (im.version != sts.update_image_version () ||" << endl
+               << "id.version != sts.update_id_binding_version () ||" << endl
+               << "imb.version == 0)"
+               << "{"
+               << "bind (imb.bind, id.bind, id.count, im, statement_update);"
+               << "sts.update_image_version (im.version);"
+               << "sts.update_id_binding_version (id.version);"
+               << "imb.version++;"
+               << "}";
+
+            os << "if (sts.update_statement ().execute () == 0)" << endl;
+
+            if (opt == 0)
+              os << "throw object_not_persistent ();";
+            else
+              os << "throw object_changed ();";
+
+            os << endl;
+          }
+
+          // Update readwrite containers if any.
+          //
+          if (s.readwrite_containers)
+          {
+            instance t (container_calls::update_call, &s);
+            t->traverse (c_);
+          }
+
+          // Update the optimistic concurrency version in the object member.
+          // Very similar code to object.
+          //
+          if (s.optimistic ()) // Note: not update_opt.
+          {
+            member_access& ma_get (opt->get ("get"));
+            member_access& ma_set (opt->get ("set"));
+
+            // Object is passed as const reference so we need to cast away
+            // constness.
+            //
+            string obj ("const_cast< object_type& > (obj)");
+            string inc (optimistic_version_increment (*opt));
+
+            if (!ma_set.synthesized)
+              os << "// From " << location_string (ma_set.loc, true) << endl;
+
+            if (ma_set.placeholder ())
+            {
+              if (!ma_get.synthesized)
+                os << "// From " << location_string (ma_get.loc, true) << endl;
+
+              if (inc == "1")
+                os << ma_set.translate (
+                  obj, ma_get.translate ("obj") + " + 1") << ";";
+              else
+                os << ma_set.translate (obj, inc) << ";";
+            }
+            else
+            {
+              // If this member is const and we have a synthesized direct
+              // access, then cast away constness. Otherwise, we assume
+              // that the user-provided expression handles this.
+              //
+              bool cast (ma_set.direct () && const_type (opt->type ()));
+              if (cast)
+                os << "const_cast< version_type& > (" << endl;
+
+              os << ma_set.translate (obj);
+
+              if (cast)
+                os << ")";
+
+              if (inc == "1")
+                os << "++;";
+              else
+                os << " = " << inc << ";";
+            }
+          }
+
+          os << "}";
+        }
+
+        section_extra (s);
+
+        if (rs != 0)
+          rs->base = 0;
+      }
+
+    protected:
+      semantics::class_& c_;
+      string scope_;
+    };
+
+    // Output a list of parameters for the persist statement.
+    //
+    struct persist_statement_params: object_columns_base, virtual context
+    {
+      typedef persist_statement_params base;
+
+      persist_statement_params (string& params, query_parameters& qp)
+          : params_ (params), qp_ (qp)
+      {
+      }
+
+      virtual void
+      traverse_pointer (semantics::data_member& m, semantics::class_& c)
+      {
+        if (!inverse (m, key_prefix_))
+          object_columns_base::traverse_pointer (m, c);
+      }
+
+      virtual bool
+      traverse_column (semantics::data_member& m, string const&, bool first)
+      {
+        string p;
+
+        if (version (m))
+          p = version_value (m);
+        else if (context::id (m) && auto_ (m)) // Only simple id can be auto.
+          p = qp_.auto_id ();
+        else
+          p = qp_.next ();
+
+        if (!p.empty ())
+        {
+          if (!first)
+            params_ += ',';
+
+          params_ += (p != "DEFAULT" ? convert_to (p, column_type (), m) : p);
+        }
+
+        return !p.empty ();
+      }
+
+      virtual string
+      version_value (semantics::data_member&)
+      {
+        return "1";
+      }
+
+    private:
+      string& params_;
+      query_parameters& qp_;
+    };
+
+    //
+    //
+    struct class_: traversal::class_, virtual context
+    {
+      typedef class_ base;
+
+      class_ ()
+          : query_columns_type_ (false, false, false),
+            view_query_columns_type_ (false),
+            grow_base_ (index_),
+            grow_member_ (index_),
+            grow_version_member_ (index_, "version_"),
+            grow_discriminator_member_ (index_, "discriminator_"),
+            bind_id_member_ ("id_"),
+            bind_version_member_ ("version_"),
+            bind_discriminator_member_ ("discriminator_"),
+            init_id_image_member_ ("id_", "id"),
+            init_version_image_member_ ("version_", "(*v)"),
+            init_id_value_member_ ("id"),
+            init_version_value_member_ ("v"),
+            init_named_version_value_member_ ("v", "version_"),
+            init_discriminator_value_member_ ("d", "", false),
+            init_named_discriminator_value_member_ (
+              "d", "discriminator_", false)
+      {
+        init ();
+      }
+
+      class_ (class_ const&)
+          : root_context (), //@@ -Wextra
+            context (),
+            query_columns_type_ (false, false, false),
+            view_query_columns_type_ (false),
+            grow_base_ (index_),
+            grow_member_ (index_),
+            grow_version_member_ (index_, "version_"),
+            grow_discriminator_member_ (index_, "discriminator_"),
+            bind_id_member_ ("id_"),
+            bind_version_member_ ("version_"),
+            bind_discriminator_member_ ("discriminator_"),
+            init_id_image_member_ ("id_", "id"),
+            init_version_image_member_ ("version_", "(*v)"),
+            init_id_value_member_ ("id"),
+            init_version_value_member_ ("v"),
+            init_named_version_value_member_ ("v", "version_"),
+            init_discriminator_value_member_ ("d", "", false),
+            init_named_discriminator_value_member_ (
+              "d", "discriminator_", false)
+      {
+        init ();
+      }
+
+      void
+      init ()
+      {
+        if (generate_grow)
+        {
+          grow_base_inherits_ >> grow_base_;
+          grow_member_names_ >> grow_member_;
+        }
+
+        bind_base_inherits_ >> bind_base_;
+        bind_member_names_ >> bind_member_;
+
+        init_image_base_inherits_ >> init_image_base_;
+        init_image_member_names_ >> init_image_member_;
+
+        init_value_base_inherits_ >> init_value_base_;
+        init_value_member_names_ >> init_value_member_;
+      }
+
+      virtual void
+      init_auto_id (semantics::data_member&, // id member
+                    string const&)           // image variable prefix
+      {
+        if (insert_send_auto_id)
+          assert (false);
+      }
+
+      virtual void
+      init_image_pre (type&)
+      {
+      }
+
+      virtual void
+      init_value_extra ()
+      {
+      }
+
+      virtual void
+      traverse (type& c)
+      {
+        if (!options.at_once () && class_file (c) != unit.file ())
+          return;
+
+        context::top_object = context::cur_object = &c;
+
         if (object (c))
           traverse_object (c);
         else if (view (c))
@@ -3793,7 +4917,8 @@ namespace relational
       object_extra (type&) {}
 
       virtual void
-      container_cache_extra_args (bool /*used*/) {}
+      extra_statement_cache_extra_args (bool /*containers*/,
+                                        bool /*sections*/) {}
 
       virtual void
       object_query_statement_ctor_args (type&,
@@ -3815,15 +4940,15 @@ namespace relational
       }
 
       virtual string
-      optimimistic_version_init (semantics::data_member&)
+      optimistic_version_init (semantics::data_member&)
       {
         return "1";
       }
 
-      // Returning "1" means incremenet by one.
+      // Returning "1" means increment by one.
       //
       virtual string
-      optimimistic_version_increment (semantics::data_member&)
+      optimistic_version_increment (semantics::data_member&)
       {
         return "1";
       }
@@ -4078,6 +5203,9 @@ namespace relational
         if (features.view)
           os << "#include " << endl;
 
+        if (features.section)
+          os << "#include " << endl;
+
         os << "#include " << endl
            << "#include " << endl;
 
diff --git a/odb/relational/sqlite/context.cxx b/odb/relational/sqlite/context.cxx
index 35ec91b..664af35 100644
--- a/odb/relational/sqlite/context.cxx
+++ b/odb/relational/sqlite/context.cxx
@@ -123,8 +123,8 @@ namespace relational
     {
       struct has_grow: traversal::class_
       {
-        has_grow (bool& r)
-            : r_ (r)
+        has_grow (bool& r, user_section* s)
+            : r_ (r), section_ (s)
         {
           *this >> inherits_ >> *this;
         }
@@ -137,7 +137,7 @@ namespace relational
           if (!(context::object (c) || context::composite (c)))
             return;
 
-          if (c.count ("sqlite-grow"))
+          if (section_ == 0 && c.count ("sqlite-grow"))
             r_ = c.get ("sqlite-grow");
           else
           {
@@ -148,29 +148,41 @@ namespace relational
             if (!r_)
               names (c);
 
-            c.set ("sqlite-grow", r_);
+            if (section_ == 0)
+              c.set ("sqlite-grow", r_);
           }
         }
 
       private:
         bool& r_;
+        user_section* section_;
         traversal::inherits inherits_;
       };
 
       struct has_grow_member: member_base
       {
         has_grow_member (bool& r,
+                         user_section* section = 0,
                          semantics::type* type = 0,
                          string const& key_prefix = string ())
-            : relational::member_base (type, string (), key_prefix),
+            : relational::member_base (type, string (), key_prefix, section),
               r_ (r)
         {
         }
 
+        virtual bool
+        pre (member_info& mi)
+        {
+          return (section_ == 0 && !separate_load (mi.m)) ||
+            (section_ != 0 && *section_ == section (mi.m));
+        }
+
         virtual void
         traverse_composite (member_info& mi)
         {
           // By calling grow() instead of recursing, we reset any overrides.
+          // We also don't pass section since they don't apply inside
+          // composites.
           //
           r_ = r_ || context::grow (dynamic_cast (mi.t));
         }
@@ -187,14 +199,14 @@ namespace relational
     }
 
     bool context::
-    grow_impl (semantics::class_& c)
+    grow_impl (semantics::class_& c, user_section* section)
     {
-      if (c.count ("sqlite-grow"))
+      if (section == 0 && c.count ("sqlite-grow"))
         return c.get ("sqlite-grow");
 
       bool r (false);
-      has_grow ct (r);
-      has_grow_member mt  (r);
+      has_grow ct (r, section);
+      has_grow_member mt  (r, section);
       traversal::names names;
       ct >> names >> mt;
       ct.traverse (c);
@@ -214,7 +226,7 @@ namespace relational
     grow_impl (semantics::data_member& m, semantics::type& t, string const& kp)
     {
       bool r (false);
-      has_grow_member mt  (r, &t, kp);
+      has_grow_member mt  (r, 0, &t, kp);
       mt.traverse (m);
       return r;
     }
diff --git a/odb/relational/sqlite/context.hxx b/odb/relational/sqlite/context.hxx
index a3721df..95e4ae6 100644
--- a/odb/relational/sqlite/context.hxx
+++ b/odb/relational/sqlite/context.hxx
@@ -67,7 +67,7 @@ namespace relational
       convert_expr (string const&, semantics::data_member&, bool);
 
       virtual bool
-      grow_impl (semantics::class_&);
+      grow_impl (semantics::class_&, user_section*);
 
       virtual bool
       grow_impl (semantics::data_member&);
diff --git a/odb/validator.cxx b/odb/validator.cxx
index efe9236..080aaa4 100644
--- a/odb/validator.cxx
+++ b/odb/validator.cxx
@@ -4,11 +4,13 @@
 
 #include 
 
+#include 
 #include 
 
 #include 
 #include 
 #include 
+#include 
 #include 
 
 #include 
@@ -110,6 +112,50 @@ namespace
         }
       }
 
+      // Make sure a member of a section is an immediate member of an object.
+      // The same for the section member itself.
+      //
+      if (!object (c))
+      {
+        if (m.count ("section-member"))
+        {
+          os << m.file () << ":" << m.line () << ":" << m.column () << ": " <<
+            "error: data member belonging to a section can only be a " <<
+            "direct member of a persistent class" << endl;
+          valid_ = false;
+        }
+
+        if (t.fq_name () == "::odb::section")
+        {
+          os << m.file () << ":" << m.line () << ":" << m.column () << ": " <<
+            "error: section data member can only be a direct member of a " <<
+            "persistent class" << endl;
+          valid_ = false;
+        }
+      }
+
+      // Make sure the load and update pragmas are only specified on
+      // section members.
+      //
+      if (t.fq_name () != "::odb::section")
+      {
+        if (m.count ("section-load"))
+        {
+          location_t loc (m.get ("section-load-location"));
+          error (loc) << "'#pragma db load' can only be specified for "
+            "a section data member" << endl;
+          valid_ = false;
+        }
+
+        if (m.count ("section-update"))
+        {
+          location_t loc (m.get ("section-update-location"));
+          error (loc) << "'#pragma db update' can only be specified for "
+            "a section data member" << endl;
+          valid_ = false;
+        }
+      }
+
       // Resolve null overrides.
       //
       override_null (m);
@@ -438,9 +484,16 @@ namespace
         if (id->count ("default"))
         {
           os << id->file () << ":" << id->line () << ":" << id->column ()
-             << ": error: object id member cannot have default value"
-             << endl;
+             << ": error: object id member cannot have default value" << endl;
+          valid_ = false;
+        }
 
+        // Complain if an id member is in a section.
+        //
+        if (id->count ("section-member"))
+        {
+          os << id->file () << ":" << id->line () << ":" << id->column ()
+             << ": error: object id member cannot be in a section" << endl;
           valid_ = false;
         }
 
@@ -464,7 +517,7 @@ namespace
 
         // Make sure we have the class declared optimistic.
         //
-        if (&optimistic->scope () == &c && !c.count ("optimistic"))
+        if (&m.scope () == &c && !c.count ("optimistic"))
         {
           os << m.file () << ":" << m.line () << ":" << m.column () << ":"
              << " error: version data member in a class not declared "
@@ -490,7 +543,7 @@ namespace
         // Make sure id and version members are in the same class. The
         // current architecture relies on that.
         //
-        if (id != 0 && &id->scope () != &optimistic->scope ())
+        if (id != 0 && &id->scope () != &m.scope ())
         {
           os << c.file () << ":" << c.line () << ":" << c.column () << ":"
              << " error: object id and version members are in different "
@@ -519,6 +572,15 @@ namespace
           valid_ = false;
         }
 
+        // Complain if the version member is in a section.
+        //
+        if (m.count ("section-member"))
+        {
+          os << m.file () << ":" << m.line () << ":" << m.column ()
+             << ": error: version member cannot be in a section" << endl;
+          valid_ = false;
+        }
+
         // This takes care of also marking derived classes as optimistic.
         //
         c.set ("optimistic-member", optimistic);
@@ -571,6 +633,28 @@ namespace
       if (poly_root != 0)
         c.set ("polymorphic-root", poly_root);
 
+      // Sectionable objects.
+      //
+      if (c.count ("sectionable"))
+      {
+        if (optimistic == 0)
+        {
+          location_t l (c.get ("sectionable-location"));
+          error (l) << "only optimistic class can be sectionable" << endl;
+          valid_ = false;
+        }
+        else if (&optimistic->scope () != &c && poly_root != &c)
+        {
+          location l (c.get ("sectionable-location"));
+          error (l) << "only optimistic class that declares the version " <<
+            "data member or that is a root of a polymorphic hierarchy can " <<
+            "be sectionable" << endl;
+          info (optimistic->location ()) << "version member is declared " <<
+            "here" << endl;
+          valid_ = false;
+        }
+      }
+
       // Update features set based on this object.
       //
       if (options.at_once () || class_file (c) == unit.file ())
@@ -878,6 +962,56 @@ namespace
     virtual void
     traverse_object (type& c)
     {
+      bool poly (polymorphic (c));
+
+      // Make sure we have no empty or pointless sections unless we
+      // are reuse-abstract or polymorphic.
+      //
+      if (!poly && !abstract (c))
+      {
+        user_sections& uss (c.get ("user-sections"));
+
+        for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i)
+        {
+          user_section& s (*i);
+
+          // Skip the special version update section (we always treat it
+          // as abstract in reuse inheritance).
+          //
+          if (s.special == user_section::special_version)
+            continue;
+
+          semantics::data_member& m (*s.member);
+          location const& l (m.location ());
+
+          if (s.total == 0 && !s.containers)
+          {
+            error (l) << "empty section" << endl;
+
+            if (&m.scope () != &c)
+              info (c.location ()) << "as seen in this non-abstract " <<
+                "persistent class" << endl;
+
+            valid_ = false;
+            continue;
+          }
+
+          // Eager-loaded section with readonly members.
+          //
+          if (s.load == user_section::load_eager && s.update_empty ())
+          {
+            error (l) << "eager-loaded section with readonly members is " <<
+              "pointless" << endl;
+
+            if (&m.scope () != &c)
+              info (c.location ()) << "as seen in this non-abstract " <<
+                "persistent class" << endl;
+
+            valid_ = false;
+          }
+        }
+      }
+
       if (semantics::data_member* id = id_member (c))
       {
         semantics::type& t (utype (*id));
@@ -937,6 +1071,20 @@ namespace
           }
         }
       }
+      else
+      {
+        // Make sure an object without id has no sections.
+        //
+        user_sections& uss (c.get ("user-sections"));
+
+        if (!uss.empty ())
+        {
+          semantics::data_member& m (*uss.front ().member);
+          os << m.file () << ":" << m.line () << ":" << m.column ()
+             << ": error: object without id cannot have sections" << endl;
+          valid_ = false;
+        }
+      }
     }
 
     virtual void
-- 
cgit v1.1