From d455b7c1c09d0b40d71b06d1d3eb5dcad97c81ee Mon Sep 17 00:00:00 2001
From: Boris Kolpackov Next, we create three An object consists of data members which are either values, pointers
- to other objects (Chapter Y, "Relationships"), or
- containers of values or pointers to other objects (Chapter
- X, "Containers"). Pointers to other objects and containers can
+ to other objects (Chapter 6, "Relationships"), or
+ containers of values or pointers to other objects (Chapter
+ 5, "Containers"). Pointers to other objects and containers can
be viewed as special kinds of values since they also can only
be stored in the database as part of an object. These two pragmas are the minimum required to declare a
persistent class. Other pragmas can be used to fine-tune
the database-related properties of a class and its
- members (see Chapter 5, "ODB Pragma Language"). Normally, an object class should define the default constructor. The
generated database support code uses this constructor when
@@ -1682,8 +1735,8 @@ private:
is unknown to the ODB compiler then we will need to provide the
mapping to the database system type and, possibly, the code to
convert between the two. For more information on how to achieve
- this refer to the Composite values are discussed in greater detail in Chapter
- Z, "Composite Value Types". Composite values are discussed in greater detail in Chapter
+ 7, "Composite Value Types". Normally, you would use object types to model real-world entities,
things that have their own identity. For example, in the
@@ -1750,8 +1803,8 @@ class name
create dynamically allocated instances of persistent classes and
return pointer to these instances. As we will see in later chapters,
pointers are also used to establish relationships between objects
- (Chapter Y, Realtionships) as well as to cache
- persistent object in a session (Chapter Q, Session). By default, all these mechanisms use raw pointers to return,
pass, and cache objects. This is normally sufficient for applications
@@ -1795,7 +1848,7 @@ class person
};
- Refer to Section 5.1.2, " Refer to Section 9.1.2, " Built-in support, provided by the ODB runtime, library allows us to use
@@ -1804,7 +1857,7 @@ class person
commonly used frameworks and libraries (such as Boost and Qt),
provide support for smart pointers found in these frameworks and
libraries. It is also easy to add support for our own smart pointers,
- as described in Section Y.4, "Using Custom Smart
+ as described in Section 6.4, "Using Custom Smart
Pointers". The first The second and third The The next three exceptions ( The The The The ODB runtime library provides built-in persistence support for
- all commonly used standard C++ containers, namely,
- We don't need to do anything special to declare a member of a
- container type in a persistent class. For example: A data member in a persistent class that is of a container type
- behaves like a value type. That is, when an object is made persistent,
- the elements of the container are store in the database. Similarly,
- when a persistent object is loaded from the database, the contents
- of the container are automatically loaded as well. While an ordinary member is mapped to one or more columns in the
- object's table, a member of a container type is mapped to a seperate
- table. The exact schema of such a table depends on the kind of
- a container. ODB defines the following container kinds: ordered,
- set, multiset, map, and multimap. The container kinds and the
- contents of the tables to which they are mapped are discussed
- in detail in the following sections. Containers in ODB can contain simple value types, composite value
- types (see @@), and pointers to objects (see @@). Containers of
- containers, either directly or indirectly via a composite value
- type, are not allowed. A key in map and multimap containers can
- be a simple or composite value type but not a pointer to an object.
- An index in the ordered container should be a simple integer type. If we don't know the identifiers of the objects that we are looking
+ for, we can use queries to search the database for objects matching
+ certain criteria. The ODB query facility is optional and we need to
+ explicitly request the generation of the necessary database support
+ code with the The value type in the ordered, set, and map containers as well as
- the key type in the map containers should be default-constructible.
- The default constructor in these types can be made private in which
- case the ODB provides a flexible query API that offers two distinct levels of
+ abstraction from the database system query language such as SQL.
+ At the high level we are presented with an easy to use yet powerful
+ object-oriented query language, called ODB Query Language. This
+ query language is modeled after and is integrated into C++ allowing
+ us to write expressive and safe queries that look and feel like
+ ordinary C++. We have already seen examples of these queries in the
+ introductory chapters. Below is another, more interesting, example: At the low level, queries can be written as predicates using
+ the database system-native query language such as the
+ In ODB an ordered container is any container that maintains (explicitly
- or implicitly) an order of its elements in the form of an integer index.
- Standard C++ containers that are ordered include Note that at this level we lose the static typing of
+ query expressions. For example, if we wrote something like this: The database table for an ordered container consists of at least
- three columns. The first column contains the object id of a
- persistent class instance of which the container is a member.
- The second column contains the element index within a container.
- And the last column contains the element value. If the object
- id or element value are composite, then instead of a single
- column they can occupy multiple columns. Consider the following persistent object as an example: We would get two errors during the C++ compilation. The first would
+ indicate that we cannot compare The resulting database table (called It would compile fine and would trigger an error only when executed
+ by the database system. A number of ODB pragmas allow us to customize the table name,
- column names, and native database types for the container both on
- the per-container and per-member basis. For more information on
- these pragmas, refer to Chapter 5, "ODB Pragma
- Language". The following example shows some of the possible
- customizations: We can also combine the two query languages in a single query, for
+ example: While the C++ container used in the persistent class may be ordered,
- sometimes we may wish to store such a container in the database without
- the order information. In the example above, for instance, the order
- of person's nicknames is probably not important. To instruct the ODB
- compiler to ignore the order in ordered containers we can use the
- An ODB query is an expression that tells the database system whether
+ any given object matches the desired criteria. As such, a query expression
+ always evaluates as The table for the ordered container that is marked unordered will
- miss the index column and the order in which elements are retrieved
- from the database may not be the same as the order in which they
- were stored. At the core of every query expression lie simple expressions which
+ involve one or more object members, values, or parameters. To
+ refer to an object member we use an expression such as
+ In a simple expression an object member can be compared to a value,
+ parameter, or another member using a number of predefined operators
+ and functions. The following table gives an overview of the available
+ expressions: In ODB set and multiset containers (referred to as just set
- containers) are associative containers that contain elements
- based on some relationship between them. A set container may
- or may not guarantee a particular order of the elements that
- it stores. Standard C++ containers that are considered set
- containers for the purpose of persistence include
- The database table for a set container consists of at least
- two columns. The first column contains the object id of a
- persistent class instance of which the container is a member.
- And the second column contains the element value. If the object
- id or element value are composite, then instead of a single
- column they can occupy multiple columns. Consider the following persistent object as an example: The resulting database table (called A number of ODB pragmas allow us to customize the table name,
- column names, and native database types for the container both on
- the per-container and per-member basis. For more information on
- these pragmas, refer to Chapter 5, "ODB Pragma
- Language". The following example shows some of the possible
- customizations: In ODB map and multimap containers (referred to as just set
- containers) are associative containers that contain key-value
- elemenst based on some relationship between keys. A map container
- may or may not guarantee a particular order of the elements that
- it stores. Standard C++ containers that are considered map
- containers for the purpose of persistence include
- The database table for a map container consists of at least
- three columns. The first column contains the object id of a
- persistent class instance of which the container is a member.
- The second column contains the element key. And the last column
- contains the element value. If the object id, element key, or
- element value are composite, then instead of a single column
- they can occupy multiple columns. Consider the following persistent object as an example: The The resulting database table (called A number of ODB pragmas allow us to customize the table name,
- column names, and native database types for the container both on
- the per-container and per-member basis. For more information on
- these pragmas, refer to Chapter 5, "ODB Pragma
- Language". The following example shows some of the possible
- customizations: While the ODB runtime and profile libraries provide support for
- a wide range of containers, it is also easy to persist custom
- container types. To achieve this you will need to implement the
- Once the container traits specialization is ready for your container,
- you will need to include it into the ODB compilation process using
- the Now, whenever we compile a header file that uses the hashtable
- container, we can pass the following option to make sure it
- is recognized by the ODB compiler as a container and the traits
- file is included in the generated code: The operator precedence in the query expressions are the same
+ as for equivalent C++ operators. We can use parentheses to
+ make sure the expression is evaluated in the desired order.
+ For example: Relationships between persistent objects are expressed with pointers or
- containers of pointers. The ODB runtime library provides built-in
- support for the TR1 When an object containing a pointer to another object is loaded,
- the pointed-to object is loaded as well. In some situations this
- eager loading of the relationships is undesirable since it
- can lead to a large number of otherwise unused objects being
- instantiated from the database. To support finer control
- over relationships loading, the ODB runtime and profile
- libraries provide the so-called lazy versions of
- the supported pointers. An object pointed-to by a lazy pointer
- is not loaded automatically when the containing object is loaded.
- Instead, we have to explicitly request the instantiation of the
- pointed-to object. Lazy pointers are discussed in
- detail in Section Y.3, "Lazy Pointers". An instance of the As a simple example, consider the following employee-employer
- relationship. Code examples presented in this chapter
- will use the If a parameter is bound by value, then the value for this parameter
+ is copied from the C++ variable to the query instance at the query
+ construction time. On the other hand, if a parameter is bound by
+ reference, then the query instance stores a reference to the
+ bound variable. The actual value of the parameter is only extracted
+ at the query execution time. Consider, for example, the following
+ two queries: By default, an object pointer can be The In this case, if we perform a database operation on the
- We don't need to do anything special to establish or navigate a
- relationship between two persistent objects, as shown in the
- following code fragment: A query that only has by-value parameters does not depend on any
+ other variables and is self-sufficient once constructed. A query
+ that has one or more by-reference parameters depends on the
+ bound variables until the query is executed. If one such variable
+ goes out of scope and we execute the query, the behavior is
+ undefined. Once we have the query instance ready and by-reference parameters
+ initialized, we can execute the query using the
+ The only notable line in the above code is the creation of a
- session before the second transaction starts. As discussed in
- Chapter @@ ref, a session acts as a cache of persistent objects.
- By creating a session before loading the As a general guideline, you should use a session when loading
- objects that have pointers to other persistent objects. A
- session makes sure that for a given object id, a single instance
- is shared among all other objects that relate to it. The first We can also use the data members from the pointed-to
- objects in database queries (Chapter 4, "Querying the
- Database"). For each pointer in a persistent class, the query
- class defines a nested scope containing members corresponding
- to the data members in the pointed-to object. For example, the
- query class for the When calling the Note that it is not required to explicitly create a named
+ query variable before executing it. For example, the following
+ two queries are equivalent: Normally we would create a named query instance if we are
+ planning to run the same query multiple times and would use the
+ in-line version for those that are executed only once. An important concept to keep in mind when working with object
- relationships is the independence of persistent objects. In particular,
- when an object containing a pointer to another object is made persistent
- or is updated, the pointed-to object is not automatically persisted
- or updated. Rather, only a reference to the object (in the form of the
- object id) is stored for the pointed-to object in the database.
- The pointed-to object itself is a separate entity and should
- be made persistent or updated independently. It is also possible to create queries from other queries by
+ combining them using logical operators. For example: When persisting or updating an object containing a pointer to another
- object, the pointed-to object must have a valid object id. This,
- however, may not always be easy to achieve in complex relationships that
- involve objects with automatically assigned identifiers. In such
- cases it may be necessary to first persist an object with a pointer
- set to Persistent object relationships can be divided into two groups:
- unidirectional and bidirectional. Each group in turn contains
- several configurations that vary depending on the cardinality
- of the sides of the relationship. All possible unidirectional
- and bidirectional configurations are discussed in the following
- sections. In unidirectional relationships we are only interested in navigating
- from object to object in one direction. Because there is no interest
- in navigating in the opposite direction, the cardinality of the other
- end of the relationship is unimportant. As a result, there are only
- two possible unidirectional relationships: to-one and to-many. Each
- of these relationships is described in the following sections. For
- sample code that shows how to work with these relationships, refer
- to the The result of executing a query is zero, one, or more objects
+ matching the query criteria. The result is returned as an instance
+ of the An example of a unidirectional to-one relationship is the
- employee-employer relationship (an employee has one employer).
- The following persistent C++ classes model this relationship: It is best to view an instance of The The corresponding database tables look like this: The default constructor creates an empty result set. The
+ An example of a unidirectional to-many relationship is the
- employee-project relationship (an employee can be involved
- in multiple projects). The following persistent C++ classes
- model this relationship: If the result is cached, the database state of all the returned
+ objects is stored in the application's memory. Note that
+ the actual objects are still only instantiated on demand
+ during result iteration. It is the raw database state that
+ is cached in memory. In contrast, for uncached results
+ the object's state is sent by the database system one object
+ at a time as the iteration progresses. Uncached results can improve the performance of both the application
+ and the database system in situations where we have a large
+ number of objects in the result or if we will only examine
+ a small portion of the returned objects. However, uncached
+ results have a number of limitations. There can only be one
+ uncached result in a transaction. Creating another result
+ (cached or uncached) by calling The To iterate over the objects in a result we use the
+ The corresponding database tables look like this: The result iterator is an input iterator which means that the
+ only two position operations that it supports are to move to the
+ next object and to determine whether the end of the result stream
+ has been reached. In fact, the result iterator can only be in two
+ states: the current position and the end position. If we have
+ two iterators pointing to the current position and then we
+ advance one of them, the other will advance as well. This,
+ for example, means that it doesn't make sense to store an
+ iterator that points to some object of interest in the result
+ stream with the intent of dereferencing it after the iteration
+ is over. Instead, we would need to store the object itself. The result iterator has the following dereference functions
+ that can be used to access the pointed-to object: To obtain a more canonical database schema, the names of tables
- and columns above can be customized using ODB pragmas
- (Chapter 5, "ODB Pragma Language"). For example: When we call the The resulting The overloaded Note, however, that because of this optimization, a subsequent
+ to In bidirectional relationships we are interested in navigating
- from object to object in both directions. As a result, each
- object class in a relationship contains a pointer to the other
- object. If smart pointers are used, then a weak pointer should
- be used as one of the pointers to avoid ownership cycles. For
- example: The second The ODB runtime library provides built-in persistence support for
+ all commonly used standard C++ containers, namely,
+ Note that when we establish a bidirectional relationship, we
- have to set both pointers consistently. One way to make sure
- that a relationship is always in a consistent state is to
- provide a single function that updates both pointers at the
- same time. For example: We don't need to do anything special to declare a member of a
+ container type in a persistent class. For example: A data member in a persistent class that is of a container type
+ behaves like a value type. That is, when an object is made persistent,
+ the elements of the container are store in the database. Similarly,
+ when a persistent object is loaded from the database, the contents
+ of the container are automatically loaded as well. Above, to model a bidirectional relationship in persistent classes,
- we used two pointers, one in each object. While this is a natural
- representation in C++, it does not translate to a canonical
- relational model. Consider the database schema generated for
- the above two classes: While an ordinary member is mapped to one or more columns in the
+ object's table, a member of a container type is mapped to a seperate
+ table. The exact schema of such a table depends on the kind of
+ a container. ODB defines the following container kinds: ordered,
+ set, multiset, map, and multimap. The container kinds and the
+ contents of the tables to which they are mapped are discussed
+ in detail in the following sections. While this database schema is valid, it is unconventional. We have
- a reference from a row in the Containers in ODB can contain simple value types, composite value
+ types (see @@), and pointers to objects (see @@). Containers of
+ containers, either directly or indirectly via a composite value
+ type, are not allowed. A key in map and multimap containers can
+ be a simple or composite value type but not a pointer to an object.
+ An index in the ordered container should be a simple integer type. To eliminate redundant database schema references we can use the
- The value type in the ordered, set, and map containers as well as
+ the key type in the map containers should be default-constructible.
+ The default constructor in these types can be made private in which
+ case the The resulting database schema looks like this: As you can see, an inverse member does not have a corresponding
- column (or table, in case of an inverse container of pointers)
- and, from the point of view of database operations, is effectively
- read-only. The only way to change a bidirectional relationship
- with an inverse side is to set its direct (non-inverse)
- pointer. Also note that an ordered container (Section
- X.1, "Ordered Containers") of pointers that is an inverse side
- of a bidirectional relationship is always treated as unordered
- (Section 5.3.8, " There are three distinct bidirectional relationships that we
- will cover in the following sections: one-to-one, one-to-many,
- and many-to-many. We will only talk about bidirectional
- relationships with inverse sides since they result in canonical
- database schemas. For sample code that shows how to work with
- these relationships, refer to the In ODB an ordered container is any container that maintains (explicitly
+ or implicitly) an order of its elements in the form of an integer index.
+ Standard C++ containers that are ordered include The database table for an ordered container consists of at least
+ three columns. The first column contains the object id of a
+ persistent class instance of which the container is a member.
+ The second column contains the element index within a container.
+ And the last column contains the element value. If the object
+ id or element value are composite, then instead of a single
+ column they can occupy multiple columns. An example of a bidirectional one-to-one relationship is the
- presented above employee-position relationship (an employee
- fills one position and a position is filled by one employee).
- The following persistent C++ classes model this relationship: Consider the following persistent object as an example: The resulting database table (called A number of ODB pragmas allow us to customize the table name,
+ column names, and native database types for the container both on
+ the per-container and per-member basis. For more information on
+ these pragmas, refer to Chapter 9, "ODB Pragma
+ Language". The following example shows some of the possible
+ customizations: The corresponding database tables look like this: While the C++ container used in the persistent class may be ordered,
+ sometimes we may wish to store such a container in the database without
+ the order information. In the example above, for instance, the order
+ of person's nicknames is probably not important. To instruct the ODB
+ compiler to ignore the order in ordered containers we can use the
+ If instead the other side of this relationship is made inverse,
- then the database tables will change as follows: The table for the ordered container that is marked unordered will
+ miss the index column and the order in which elements are retrieved
+ from the database may not be the same as the order in which they
+ were stored. In ODB set and multiset containers (referred to as just set
+ containers) are associative containers that contain elements
+ based on some relationship between them. A set container may
+ or may not guarantee a particular order of the elements that
+ it stores. Standard C++ containers that are considered set
+ containers for the purpose of persistence include
+ The database table for a set container consists of at least
+ two columns. The first column contains the object id of a
+ persistent class instance of which the container is a member.
+ And the second column contains the element value. If the object
+ id or element value are composite, then instead of a single
+ column they can occupy multiple columns. An example of a bidirectional one-to-many relationship is the
- employer-employee relationship (an employer has multiple
- employees and an employee is employed by one employer).
- The following persistent C++ classes model this relationship: Consider the following persistent object as an example: The resulting database table (called A number of ODB pragmas allow us to customize the table name,
+ column names, and native database types for the container both on
+ the per-container and per-member basis. For more information on
+ these pragmas, refer to Chapter 9, "ODB Pragma
+ Language". The following example shows some of the possible
+ customizations: The corresponding database tables differ significantly depending
- on which side of the relationship is made inverse. If the one
- side ( If instead the many side ( In ODB map and multimap containers (referred to as just set
+ containers) are associative containers that contain key-value
+ elemenst based on some relationship between keys. A map container
+ may or may not guarantee a particular order of the elements that
+ it stores. Standard C++ containers that are considered map
+ containers for the purpose of persistence include
+ The database table for a map container consists of at least
+ three columns. The first column contains the object id of a
+ persistent class instance of which the container is a member.
+ The second column contains the element key. And the last column
+ contains the element value. If the object id, element key, or
+ element value are composite, then instead of a single column
+ they can occupy multiple columns. An example of a bidirectional many-to-many relationship is the
- employee-project relationship (an employee can work on multiple
- projects and a project can have multiple participating employees).
- The following persistent C++ classes model this relationship: Consider the following persistent object as an example: The resulting database table (called A number of ODB pragmas allow us to customize the table name,
+ column names, and native database types for the container both on
+ the per-container and per-member basis. For more information on
+ these pragmas, refer to Chapter 9, "ODB Pragma
+ Language". The following example shows some of the possible
+ customizations: The corresponding database tables look like this: While the ODB runtime and profile libraries provide support for
+ a wide range of containers, it is also easy to persist custom
+ container types. To achieve this you will need to implement the
+ Once the container traits specialization is ready for your container,
+ you will need to include it into the ODB compilation process using
+ the If instead the other side of this relationship is made inverse,
- then the database tables will change as follows: Now, whenever we compile a header file that uses the hashtable
+ container, we can pass the following option to make sure it
+ is recognized by the ODB compiler as a container and the traits
+ file is included in the generated code: Relationships between persistent objects are expressed with pointers or
+ containers of pointers. The ODB runtime library provides built-in
+ support for the TR1 When an object containing a pointer to another object is loaded,
+ the pointed-to object is loaded as well. In some situations this
+ eager loading of the relationships is undesirable since it
+ can lead to a large number of otherwise unused objects being
+ instantiated from the database. To support finer control
+ over relationships loading, the ODB runtime and profile
+ libraries provide the so-called lazy versions of
+ the supported pointers. An object pointed-to by a lazy pointer
+ is not loaded automatically when the containing object is loaded.
+ Instead, we have to explicitly request the instantiation of the
+ pointed-to object. Lazy pointers are discussed in
+ detail in Section 6.3, "Lazy Pointers". Consider again the bidirectional, one-to-many employer-employee
- relationship that was presented earlier in this chapter: As a simple example, consider the following employee-employer
+ relationship. Code examples presented in this chapter
+ will use the Consider also the following transaction which obtains the employer
- name given the employee id: By default, an object pointer can be While this transaction looks very simple, it actually does a lot more
- than what meets the eye and is necessary. Consider what happens when
- we load the In this case, if we perform a database operation on the
+ To overcome this problem ODB offers finer grained control over
- the relationship loading in the form of lazy pointers. A lazy
- pointer does not automatically load the pointed-to object
- when the containing object is loaded. Instead, we have to
- explicitly load the pointed-to object if and when we need to
- access it. We don't need to do anything special to establish or navigate a
+ relationship between two persistent objects, as shown in the
+ following code fragment: The ODB runtime library provides lazy counterparts for all the
- supported pointers, namely: While we will discuss the interface of lazy pointers in more detail
- shortly, the most commonly used extra function provided by these
- pointers is The following example shows how we can change our employer-employee
- relationship to use lazy pointers. Here we choose to use lazy pointers
- for both sides of the relationship. And the transaction is changed like this: The only notable line in the above code is the creation of a
+ session before the second transaction starts. As discussed in
+ Chapter @@ ref, a session acts as a cache of persistent objects.
+ By creating a session before loading the As a general guideline, you should use a session when loading
+ objects that have pointers to other persistent objects. A
+ session makes sure that for a given object id, a single instance
+ is shared among all other objects that relate to it. We can also use the data members from the pointed-to
+ objects in database queries (Chapter 4, "Querying the
+ Database"). For each pointer in a persistent class, the query
+ class defines a nested scope containing members corresponding
+ to the data members in the pointed-to object. For example, the
+ query class for the As a general guideline we recommend that you make at least one side
- of a bidirectional relationship lazy, especially for relationships
- with a many side. An important concept to keep in mind when working with object
+ relationships is the independence of persistent objects. In particular,
+ when an object containing a pointer to another object is made persistent
+ or is updated, the pointed-to object is not automatically persisted
+ or updated. Rather, only a reference to the object (in the form of the
+ object id) is stored for the pointed-to object in the database.
+ The pointed-to object itself is a separate entity and should
+ be made persistent or updated independently. A lazy pointer implementation mimics the interface of its eager
- counterpart which can be used once the pointer is loaded. It also
- adds a number of additional functions that are specific to the
- lazy loading functionality. Overall, the interface of a lazy
- pointer follows this general outline: When persisting or updating an object containing a pointer to another
+ object, the pointed-to object must have a valid object id. This,
+ however, may not always be easy to achieve in complex relationships that
+ involve objects with automatically assigned identifiers. In such
+ cases it may be necessary to first persist an object with a pointer
+ set to Persistent object relationships can be divided into two groups:
+ unidirectional and bidirectional. Each group in turn contains
+ several configurations that vary depending on the cardinality
+ of the sides of the relationship. All possible unidirectional
+ and bidirectional configurations are discussed in the following
+ sections. In unidirectional relationships we are only interested in navigating
+ from object to object in one direction. Because there is no interest
+ in navigating in the opposite direction, the cardinality of the other
+ end of the relationship is unimportant. As a result, there are only
+ two possible unidirectional relationships: to-one and to-many. Each
+ of these relationships is described in the following sections. For
+ sample code that shows how to work with these relationships, refer
+ to the An example of a unidirectional to-one relationship is the
+ employee-employer relationship (an employee has one employer).
+ The following persistent C++ classes model this relationship: In a lazy weak pointer interface, the The corresponding database tables look like this: An example of a unidirectional to-many relationship is the
+ employee-project relationship (an employee can be involved
+ in multiple projects). The following persistent C++ classes
+ model this relationship: Notice that inside the for-loop we use a reference to the lazy
- weak pointer instead of making a copy. This is not merely to
- avoid a copy. When a lazy pointer is loaded, all other lazy
- pointers that point to the same object do not automatically
- become loaded (though an attempt to load such copies will
- result in them pointing to the same object, provided the
- same session is still in effect). By using a reference
- in the above transaction we make sure that we load the
- pointer that is contained in the The corresponding database tables look like this: As another example, suppose we want to add an employee
- to Example Inc. The straightforward implementation of this
- transaction is presented below: To obtain a more canonical database schema, the names of tables
+ and columns above can be customized using ODB pragmas
+ (Chapter 9, "ODB Pragma Language"). For example: The resulting Notice here that we didn't have to update the employer object
- in the database since the A faster implementation of this transaction, that avoids loading
- the employer object, relies on the ability to initialize an
- unloaded lazy pointer with the database where the object
- is stored as well as its identifier: In bidirectional relationships we are interested in navigating
+ from object to object in both directions. As a result, each
+ object class in a relationship contains a pointer to the other
+ object. If smart pointers are used, then a weak pointer should
+ be used as one of the pointers to avoid ownership cycles. For
+ example: While the ODB runtime and profile libraries provide support for
- the majority of widely-used pointers, it is also easy to add
- support for a custom smart pointer. To achieve this you will need to implement the
- Note that when we establish a bidirectional relationship, we
+ have to set both pointers consistently. One way to make sure
+ that a relationship is always in a consistent state is to
+ provide a single function that updates both pointers at the
+ same time. For example: Once you have determined the pointer kind for your smart pointer,
- use a specialization for one of the standard pointers found in
- the common ODB runtime library ( Once the pointer traits specialization is ready, you will need to
- include it into the ODB compilation process using the
- Now, whenever we compile a header file that uses It is also possible to implement a lazy counterpart for your
- smart pointer. The ODB runtime library provides a class template
- that encapsulates the object id management and loading
- functionality that is needed to implement a lazy pointer. All
- you need to do is wrap it with an interface that mimics
- your smart pointer. Using one of the existing lazy pointer
- implementations (either from the ODB runtime library or one
- of the profile libraries) as a base for your implementation
- is the easiest way to get started. Above, to model a bidirectional relationship in persistent classes,
+ we used two pointers, one in each object. While this is a natural
+ representation in C++, it does not translate to a canonical
+ relational model. Consider the database schema generated for
+ the above two classes: Composite value type is a While this database schema is valid, it is unconventional. We have
+ a reference from a row in the To eliminate redundant database schema references we can use the
+ A composite value type does not have to define a default constructor,
- unless it is used as an element of a container, in which case the
- default constructor can be made private. If a composite value type
- has private or protected non-transient data members or if its
- default constructor is not public and the value type is used as
- an element in a container, then the The complete versions of the above code fragment as well as other code
- samples presented in this chapter can found in the The resulting database schema looks like this: The members of a composite value can be other value types (either
- simple or composite), containers (@@ ref), and object pointers (@@ ref).
- Similarly, a composite value type can be used in object members,
- as an element in a container, and as a base for another composite
- value type. In particular, composite value types can be used as
- element types in set containers (@@ ref) and as key types in map
- containers (@@ ref). A composite value type that is used as an
- element of a container cannot contain other containers since
- containers of containers are not allowed. The following example
- illustrates some of the possible use cases: As you can see, an inverse member does not have a corresponding
+ column (or table, in case of an inverse container of pointers)
+ and, from the point of view of database operations, is effectively
+ read-only. The only way to change a bidirectional relationship
+ with an inverse side is to set its direct (non-inverse)
+ pointer. Also note that an ordered container (Section
+ 5.1, "Ordered Containers") of pointers that is an inverse side
+ of a bidirectional relationship is always treated as unordered
+ (Section 9.3.8, " There are three distinct bidirectional relationships that we
+ will cover in the following sections: one-to-one, one-to-many,
+ and many-to-many. We will only talk about bidirectional
+ relationships with inverse sides since they result in canonical
+ database schemas. For sample code that shows how to work with
+ these relationships, refer to the An example of a bidirectional one-to-one relationship is the
+ presented above employee-position relationship (an employee
+ fills one position and a position is filled by one employee).
+ The following persistent C++ classes model this relationship: We can also use the data members from the composite value types
- in database queries (Chapter 4, "Querying the
- Database"). For each composite value in a persistent class,
- the query class defines a nested scope containing members corresponding
- to the data members in the value type. For example, the
- query class for the The corresponding database tables look like this: If instead the other side of this relationship is made inverse,
+ then the database tables will change as follows: Customizing a column name for a data member of a simple value
- type is straightforward: we simply specify the desired name with
- the An example of a bidirectional one-to-many relationship is the
+ employer-employee relationship (an employer has multiple
+ employees and an employee is employed by one employer).
+ The following persistent C++ classes model this relationship: The column names for the The corresponding database tables differ significantly depending
+ on which side of the relationship is made inverse. If the one
+ side ( We can customize both the prefix and the suffix using the
- If instead the many side ( An example of a bidirectional many-to-many relationship is the
+ employee-project relationship (an employee can work on multiple
+ projects and a project can have multiple participating employees).
+ The following persistent C++ classes model this relationship: The database schema changes as follows: The corresponding database tables look like this: We can also make the column prefix empty, for example: This will result in the following schema: If instead the other side of this relationship is made inverse,
+ then the database tables will change as follows: The same principal applies when a composite value type is used
- as an element of a container, except that instead of
- When a composite value type contains a container, an extra table
- is used to store the elements (@@ ref Chapter X, "Containers").
- The names of such tables are constructed in a way similar to the
- column names, except that by default both the object name and the
- member name are used as a prefix. For example: Consider again the bidirectional, one-to-many employer-employee
+ relationship that was presented earlier in this chapter: The corresponding database schema will look like this: Consider also the following transaction which obtains the employer
+ name given the employee id: To customize the container table name we can use the
- While this transaction looks very simple, it actually does a lot more
+ than what meets the eye and is necessary. Consider what happens when
+ we load the To overcome this problem ODB offers finer grained control over
+ the relationship loading in the form of lazy pointers. A lazy
+ pointer does not automatically load the pointed-to object
+ when the containing object is loaded. Instead, we have to
+ explicitly load the pointed-to object if and when we need to
+ access it. The ODB runtime library provides lazy counterparts for all the
+ supported pointers, namely: While we will discuss the interface of lazy pointers in more detail
+ shortly, the most commonly used extra function provided by these
+ pointers is The following example shows how we can change our employer-employee
+ relationship to use lazy pointers. Here we choose to use lazy pointers
+ for both sides of the relationship. This will result in the following schema changes: And the transaction is changed like this: Similar to columns, we can make the table prefix empty. As a general guideline we recommend that you make at least one side
+ of a bidirectional relationship lazy, especially for relationships
+ with a many side. A lazy pointer implementation mimics the interface of its eager
+ counterpart which can be used once the pointer is loaded. It also
+ adds a number of additional functions that are specific to the
+ lazy loading functionality. Overall, the interface of a lazy
+ pointer follows this general outline: A session is an application's unit of work that may encompass several
- database transactions (@@ ref Transaction). In this version of ODB a
- session is just an object cache. In the future versions it will provide
- additional functionality, such as automatic object state change flushing
- and optimistic concurrency. Each thread of execution in an application can have only one active
- session at a time. A session is started by creating an instance of
- the In a lazy weak pointer interface, the The Notice that inside the for-loop we use a reference to the lazy
+ weak pointer instead of making a copy. This is not merely to
+ avoid a copy. When a lazy pointer is loaded, all other lazy
+ pointers that point to the same object do not automatically
+ become loaded (though an attempt to load such copies will
+ result in them pointing to the same object, provided the
+ same session is still in effect). By using a reference
+ in the above transaction we make sure that we load the
+ pointer that is contained in the As another example, suppose we want to add an employee
+ to Example Inc. The straightforward implementation of this
+ transaction is presented below: Notice here that we didn't have to update the employer object
+ in the database since the A faster implementation of this transaction, that avoids loading
+ the employer object, relies on the ability to initialize an
+ unloaded lazy pointer with the database where the object
+ is stored as well as its identifier: The session constructor creates a new session and sets it as a
- current session for this thread. If we try to create a session
- while there is already a current session in effect, this constructor
- throws the The static The static We normally don't use the object cache interface directly. However,
- it could be useful in some cases, for example, to find out whether
- an object has already been loaded. While the ODB runtime and profile libraries provide support for
+ the majority of widely-used pointers, it is also easy to add
+ support for a custom smart pointer. To achieve this you will need to implement the
+ A session is an object cache. Every time an object is made persistent
- by calling the Once you have determined the pointer kind for your smart pointer,
+ use a specialization for one of the standard pointers found in
+ the common ODB runtime library ( Once the pointer traits specialization is ready, you will need to
+ include it into the ODB compilation process using the
+ Now, whenever we compile a header file that uses It is also possible to implement a lazy counterpart for your
+ smart pointer. The ODB runtime library provides a class template
+ that encapsulates the object id management and loading
+ functionality that is needed to implement a lazy pointer. All
+ you need to do is wrap it with an interface that mimics
+ your smart pointer. Using one of the existing lazy pointer
+ implementations (either from the ODB runtime library or one
+ of the profile libraries) as a base for your implementation
+ is the easiest way to get started. The per-object caching policies depend on the object pointer kind
- (@@ ref). Objects with a unique pointer, such as Finally, the session caches both constant and non-constant objects,
- depending on whether a constant reference or constant pointer was
- passed to the Composite value type is a A composite value type does not have to define a default constructor,
+ unless it is used as an element of a container, in which case the
+ default constructor can be made private. If a composite value type
+ has private or protected non-transient data members or if its
+ default constructor is not public and the value type is used as
+ an element in a container, then the If we don't know the identifiers of the objects that we are looking
- for, we can use queries to search the database for objects matching
- certain criteria. The ODB query facility is optional and we need to
- explicitly request the generation of the necessary database support
- code with the ODB provides a flexible query API that offers two distinct levels of
- abstraction from the database system query language such as SQL.
- At the high level we are presented with an easy to use yet powerful
- object-oriented query language, called ODB Query Language. This
- query language is modeled after and is integrated into C++ allowing
- us to write expressive and safe queries that look and feel like
- ordinary C++. We have already seen examples of these queries in the
- introductory chapters. Below is another, more interesting, example: The complete versions of the above code fragment as well as other code
+ samples presented in this chapter can found in the The members of a composite value can be other value types (either
+ simple or composite), containers (@@ ref), and object pointers (@@ ref).
+ Similarly, a composite value type can be used in object members,
+ as an element in a container, and as a base for another composite
+ value type. In particular, composite value types can be used as
+ element types in set containers (@@ ref) and as key types in map
+ containers (@@ ref). A composite value type that is used as an
+ element of a container cannot contain other containers since
+ containers of containers are not allowed. The following example
+ illustrates some of the possible use cases: At the low level, queries can be written as predicates using
- the database system-native query language such as the
- Note that at this level we lose the static typing of
- query expressions. For example, if we wrote something like this: We would get two errors during the C++ compilation. The first would
- indicate that we cannot compare It would compile fine and would trigger an error only when executed
- by the database system. We can also combine the two query languages in a single query, for
- example: An ODB query is an expression that tells the database system whether
- any given object matches the desired criteria. As such, a query expression
- always evaluates as We can also use the data members from the composite value types
+ in database queries (Chapter 4, "Querying the
+ Database"). For each composite value in a persistent class,
+ the query class defines a nested scope containing members corresponding
+ to the data members in the value type. For example, the
+ query class for the At the core of every query expression lie simple expressions which
- involve one or more object members, values, or parameters. To
- refer to an object member we use an expression such as
- In a simple expression an object member can be compared to a value,
- parameter, or another member using a number of predefined operators
- and functions. The following table gives an overview of the available
- expressions: Customizing a column name for a data member of a simple value
+ type is straightforward: we simply specify the desired name with
+ the The column names for the The We can customize both the prefix and the suffix using the
+ The operator precedence in the query expressions are the same
- as for equivalent C++ operators. We can use parentheses to
- make sure the expression is evaluated in the desired order.
- For example: The database schema changes as follows: We can also make the column prefix empty, for example: An instance of the This will result in the following schema: If a parameter is bound by value, then the value for this parameter
- is copied from the C++ variable to the query instance at the query
- construction time. On the other hand, if a parameter is bound by
- reference, then the query instance stores a reference to the
- bound variable. The actual value of the parameter is only extracted
- at the query execution time. Consider, for example, the following
- two queries: The same principal applies when a composite value type is used
+ as an element of a container, except that instead of
+ When a composite value type contains a container, an extra table
+ is used to store the elements (@@ ref Chapter X, "Containers").
+ The names of such tables are constructed in a way similar to the
+ column names, except that by default both the object name and the
+ member name are used as a prefix. For example: The The corresponding database schema will look like this: A query that only has by-value parameters does not depend on any
- other variables and is self-sufficient once constructed. A query
- that has one or more by-reference parameters depends on the
- bound variables until the query is executed. If one such variable
- goes out of scope and we execute the query, the behavior is
- undefined. To customize the container table name we can use the
+ Once we have the query instance ready and by-reference parameters
- initialized, we can execute the query using the
- The first This will result in the following schema changes: When calling the Similar to columns, we can make the table prefix empty. Note that it is not required to explicitly create a named
- query variable before executing it. For example, the following
- two queries are equivalent: Normally we would create a named query instance if we are
- planning to run the same query multiple times and would use the
- in-line version for those that are executed only once. A session is an application's unit of work that may encompass several
+ database transactions (@@ ref Transaction). In this version of ODB a
+ session is just an object cache. In the future versions it will provide
+ additional functionality, such as automatic object state change flushing
+ and optimistic concurrency. It is also possible to create queries from other queries by
- combining them using logical operators. For example: Each thread of execution in an application can have only one active
+ session at a time. A session is started by creating an instance of
+ the The result of executing a query is zero, one, or more objects
- matching the query criteria. The result is returned as an instance
- of the It is best to view an instance of The The The default constructor creates an empty result set. The
- If the result is cached, the database state of all the returned
- objects is stored in the application's memory. Note that
- the actual objects are still only instantiated on demand
- during result iteration. It is the raw database state that
- is cached in memory. In contrast, for uncached results
- the object's state is sent by the database system one object
- at a time as the iteration progresses. Uncached results can improve the performance of both the application
- and the database system in situations where we have a large
- number of objects in the result or if we will only examine
- a small portion of the returned objects. However, uncached
- results have a number of limitations. There can only be one
- uncached result in a transaction. Creating another result
- (cached or uncached) by calling The The session constructor creates a new session and sets it as a
+ current session for this thread. If we try to create a session
+ while there is already a current session in effect, this constructor
+ throws the To iterate over the objects in a result we use the
- The static The static We normally don't use the object cache interface directly. However,
+ it could be useful in some cases, for example, to find out whether
+ an object has already been loaded. The result iterator is an input iterator which means that the
- only two position operations that it supports are to move to the
- next object and to determine whether the end of the result stream
- has been reached. In fact, the result iterator can only be in two
- states: the current position and the end position. If we have
- two iterators pointing to the current position and then we
- advance one of them, the other will advance as well. This,
- for example, means that it doesn't make sense to store an
- iterator that points to some object of interest in the result
- stream with the intent of dereferencing it after the iteration
- is over. Instead, we would need to store the object itself. The result iterator has the following dereference functions
- that can be used to access the pointed-to object: A session is an object cache. Every time an object is made persistent
+ by calling the When we call the The per-object caching policies depend on the object pointer kind
+ (@@ ref). Objects with a unique pointer, such as The overloaded Finally, the session caches both constant and non-constant objects,
+ depending on whether a constant reference or constant pointer was
+ passed to the Note, however, that because of this optimization, a subsequent
- to The second As we have already seen in previous chapters, ODB uses a pragma-based
language to capture database-specific information about C++ types.
@@ -5034,10 +5086,10 @@ private:
the C++ compiler to build our application. Some C++ compilers
issue warnings about pragmas that they do not recognize. There
are several ways to deal with this problem which are covered
- at the end of this chapter in Section 5.4,
+ at the end of this chapter in Section 9.4,
"C++ Compiler Warnings". A pragma with the The If the table name is not specified, the class name is used as the
table name. The For additional information on object pointers, refer to
Section @@, "". A pragma with the Many of the value type specifiers have corresponding member type
- specifiers with the same names (see Section 5.3,
+ specifiers with the same names (see Section 9.3,
"Data Member Pragmas"). The behavior of such specifiers
for members is similar to that for value types. The only difference
is the scope. A particular value type specifier applies to all
@@ -5219,7 +5271,7 @@ class person
to a single member. In other words, member specifiers take precedence
over values specified with value specifiers. The In the above example we changed the mapping for the The The The The The The If the column name is not specified, then The If the column name is not specified, then The If the column name is not specified, then The A pragma with the Many of the member specifiers have corresponding value type
- specifiers with the same names (see Section 5.2,
+ specifiers with the same names (see Section 9.2,
"Value Type Pragmas"). The behavior of such specifiers
for members is similar to that for value types. The only difference
is the scope. A particular value type specifier applies to all
@@ -5533,7 +5585,7 @@ typedef std::map<unsigned short, float> age_weight_map;
to a single member. In other words, member specifiers take precedence
over values specified with value specifiers. The In a relational database, an identifier member is mapped to a
primary key. The For additional information on the automatic identifier assignment,
refer to Section 3.5, "Making Objects Persistent". The The For a member of a composite value type, the If the column name is not specified, it is derived from the member
name by removing the common member name decorations, such as leading
and trailing underscores, the The
-
+
+ 5 ODB Pragma Language
+ 5 Containers
+
+
+
+
+ 5.1 Ordered Containers
+ 5.2 Set and Multiset Containers
+ 5.3 Map and Multimap Containers
+ 5.4 Using Custom Containers
+
+
+ 6 Relationships
+
+
+
+
+
+ 6.1 Unidirectional Relationships
+
+
+
+
+ 6.1.1 To-One Relationships
+ 6.1.2 To-Many Relationships
+
+ 6.2 Bidirectional Relationships
+
+
+
+
+ 6.2.1 One-to-One Relationships
+ 6.2.2 One-to-Many Relationships
+ 6.2.3 Many-to-Many Relationships
+ 6.3 Lazy Pointers
+ 6.4 Using Custom Smart Pointers
+
+
+ 7 Composite Value Types
+
+
+
+
+ 7.1 Composite Value Column and Table Names
+
+
+ 8 Session
+
+
+
+
+ 8.1 Object Cache
+ 9 ODB Pragma Language
-
5.1 Object Type Pragmas
+ 9.1 Object Type Pragmas
-
- 5.1.1 table
+ 5.1.2 pointer
+ 9.1.1 table
9.1.2 pointer
-
5.2 Value Type Pragmas
+ 9.2 Value Type Pragmas
-
- 5.2.1 type
- 5.2.2 not_null
- 5.2.3 unordered
- 5.2.4 index_type
- 5.2.5 key_type
- 5.2.6 value_type
- 5.2.7 id_column
- 5.2.8 index_column
- 5.2.9 key_column
+ 5.2.10 value_column
+ 9.2.1 type
+ 9.2.2 not_null
+ 9.2.3 unordered
+ 9.2.4 index_type
+ 9.2.5 key_type
+ 9.2.6 value_type
+ 9.2.7 id_column
+ 9.2.8 index_column
+ 9.2.9 key_column
9.2.10 value_column
-
5.3 Data Member Pragmas
+ 9.3 Data Member Pragmas
-
- 5.3.1 id
- 5.3.2 auto
- 5.3.3 type
- 5.3.4 column
- 5.3.5 transient
- 5.3.6 not_null
- 5.3.7 inverse
- 5.3.8 unordered
- 5.3.9 table
- 5.3.10 index_type
- 5.3.11 key_type
- 5.3.12 value_type
- 5.3.13 id_column
- 5.3.14 index_column
- 5.3.15 key_column
+ 5.3.16 value_column
+ 9.3.1 id
+ 9.3.2 auto
+ 9.3.3 type
+ 9.3.4 column
+ 9.3.5 transient
+ 9.3.6 not_null
+ 9.3.7 inverse
+ 9.3.8 unordered
+ 9.3.9 table
+ 9.3.10 index_type
+ 9.3.11 key_type
+ 9.3.12 value_type
+ 9.3.13 id_column
+ 9.3.14 index_column
+ 9.3.15 key_column
9.3.16 value_column
-
@@ -389,15 +442,15 @@ for consistency.
5.4 C++ Compiler Warnings
+ 9.4 C++ Compiler Warnings
-
- 5.4.1 GNU C++
- 5.4.2 Visual C++
- 5.4.3 Sun C++
- 5.4.4 IBM XL C++
+ 5.4.5 HP aC++
+ 9.4.1 GNU C++
+ 9.4.2 Visual C++
+ 9.4.3 Sun C++
+ 9.4.4 IBM XL C++ 9.4.5 HP aC++
- 6 Database Systems
+ 10 Database Systems
-
-
@@ -1084,7 +1137,7 @@ main (int argc, char* argv[])
database name, etc., from the command line. In your own applications
you may prefer to use other 6.1 MySQL Database
+ 10.1 MySQL Database
-
- 6.1.1 MySQL Type Mapping
- 6.1.2 MySQL Database Class
- 6.1.3 Connection Factory
+ 6.1.4 MySQL Exceptions
+ 10.1.1 MySQL Type Mapping
+ 10.1.2 MySQL Database Class
+ 10.1.3 Connection Factory 10.1.4 MySQL Exceptions mysql::database
constructors which allow you to pass this information directly
- (see Section 6.1.2, "MySQL Database Class").
+ (see Section 10.1.2, "MySQL Database Class").
person
objects. Right now they are
transient objects, which means that if we terminate the application
@@ -1552,9 +1605,9 @@ Hello, Joe!
object and doesn't have its own unique identifier.db type
pragma (Section
- 5.2.1, "type
"). Similar to object types, composite
+ this refer to the db type
pragma (Section
+ 9.2.1, "type
"). Similar to object types, composite
value types have to be explicitly declared as persistent using the
db value
pragma, for example:pointer
"
+ pointer
"
for more information on this pragma.3.3 Database
@@ -1848,7 +1901,7 @@ auto_ptr<odb::database> db (
as well as the next chapter which is dedicated to the topic of
querying the database for persistent objects. For details on the
system-specific database
classes, refer to
- Chapter 6, "Database Systems".
+ Chapter 10, "Database Systems".
3.4 Transactions
@@ -2094,7 +2147,7 @@ update_age (database& db, person& p)
persist()
function expects a constant reference
to an instance being persisted. The second function expects a constant
object pointer. Both of these functions can only be used on objects with
- application-assigned object ids (Section 5.3.2,
+ application-assigned object ids (Section 9.3.2,
"auto
").persist()
versions are similar to the
@@ -2460,7 +2513,7 @@ namespace odb
null_pointer
exception is thrown when a
pointer to a persistent object declared non-NULL
with the db not_null
pragma has the NULL
- value. See Chapter Y, "Relationships" for details.already_in_transaction
,
not_in_transaction
,
@@ -2472,7 +2525,7 @@ namespace odb
not_in_session
, and
const_object
) are thrown by the
odb::session
class and are discussed
- in Chapter Q, "Session".deadlock
exception is thrown when a transaction
deadlock is detected by the database system. It can thrown by any
@@ -2497,8 +2550,8 @@ namespace odb
database_exception
is a base class for all
database system-specific exceptions that are thrown by the
- database system-specific runtime library. See Chapter
- 6, "Database Systems" for more information.odb::exception
class is defined in the
<odb/exception.hxx>
header file. All the
@@ -2514,1020 +2567,930 @@ namespace odb
- X Containers
-
- std::vector
, std::list
, std::set
,
- std::multiset
, std::map
, and
- std::multimap
. Plus, ODB profile libraries, that are
- available for commonly used frameworks and libraries (such as Boost
- and Qt), provide persistence support for containers found in these
- frameworks and libraries. It is also easy to persist custom
- container types as discussed later in Section X.4,
- "Using Custom Containers".
-#pragma db object
-class person
-{
- ...
-private:
- std::vector<std::name> nicknames_;
- ...
-};
-
-
- 4 Querying the Database
- --generate-query
ODB compiler option.odb::access
class should be made a friend of
- the value or key type. For example:
-#pragma db value
-class name
-{
-public:
- name (const std::string&, const std::string&);
- ...
-private:
- friend class odb::access;
- name ();
- ...
-};
+ typedef odb::query<person> query;
+ typedef odb::result<person> result;
-#pragma db object
-class person
-{
- ...
-private:
- #pragma db id auto
- unsigned long id_;
+ unsigned short age;
+ query q (query::first == "John" && query::age < query::_ref (age));
- std::vector<name> aliases_;
- ...
-};
+ for (age = 10; age < 100; age += 10)
+ {
+ result r (db.query<person> (q));
+ ...
+ }
+ WHERE
predicate from the SQL SELECT
+ statement. This language will be referred to as native query
+ language. At this level ODB still takes care of converting
+ query parameters from C++ to the database system format. Below
+ is the re-implementation of the above example using SQL as
+ the native query language:X.1 Ordered Containers
+
+ query q ("first = 'John' AND age = " + query::_ref (age));
+
- std::vector
- and std::list
. While elements in std::set
- are also kept in a specific order, this order is not based on an
- integer index but rather on the relationship between elements. As
- a result, std::set
is not considered an ordered
- container for the purpose of persistence.
+ query q (query::first == 123 && query::agee < query::_ref (age));
+
- query::first
to an
+ integer and the second would pick the misspelling in
+ query::agee
. On the other hand, if we wrote something
+ like this:
-#pragma db object
-class person
-{
- ...
-private:
- #pragma db id auto
- unsigned long id_;
-
- std::vector<std::string> nicknames_;
- ...
-};
+ query q ("first = 123 AND agee = " + query::_ref (age));
- person_nicknames
) will
- contain the object id column of type unsigned long
- (called object_id
), the index column of an integer type
- (called index
), and the value column of type
- std::string
(called value
).
-#pragma db object
-class person
-{
- ...
-private:
- #pragma db id auto
- unsigned long id_;
-
- #pragma db table("nicknames") \
- id_column ("person_id") \
- index_type ("SMALLINT UNSIGNED NOT NULL") \
- index_column ("nickname_number") \
- value_type ("VARCHAR(255) NOT NULL") \
- value_column ("nickname")
- std::vector<std::string> nicknames_;
- ...
-};
+ query q ("first = 'John'" + (query::age < query::_ref (age)));
- unordered
pragma (see Chapter 5, "ODB
- Pragma Language" for details). For example:4.1 ODB Query Language
+
+ true
or false
. At
+ the higher level, an expression consists of other expressions
+ combined with logical operators such as &&
(AND),
+ ||
(OR), and !
(NOT). For example:
-#pragma db object
-class person
-{
- ...
-private:
- #pragma db id auto
- unsigned long id_;
+ typedef odb::query<person> query;
- #pragma db unordered
- std::vector<std::string> nicknames_;
- ...
-};
+ query q (query::first == "John" || query::age == 31);
- query::first
above. The names of members in the
+ query
class are derived from the names of data members
+ in the object class by removing the common member name decorations,
+ such as leading and trailing underscores, the m_
prefix,
+ etc.X.2 Set and Multiset Containers
+ std::set
and std::multiset
.
+
-
+
- Operator
+ Description
+ Example
+
+
-
+ ==
equal
+
+ query::age == 31
-#pragma db object
-class person
-{
- ...
-private:
- #pragma db id auto
- unsigned long id_;
+
+
+
- std::set<std::string> emails_;
- ...
-};
-
+ !=
unequal
+
+ query::age != 31
+
-
+ <
less than
+
+ query::age < 31
person_emails
) will
- contain the object id column of type unsigned long
- (called object_id
) and the value column of type
- std::string
(called value
).
+
-
+ >
greater than
+
+ query::age > 31
+
-
+ <=
less than or equal
+
+ query::age <= 31
-#pragma db object
-class person
-{
- ...
-private:
- #pragma db id auto
- unsigned long id_;
+
+
+
- #pragma db table("emails") \
- id_column ("person_id") \
- value_type ("VARCHAR(255) NOT NULL") \
- value_column ("email")
- std::set<std::string> emails_;
- ...
-};
-
+ >=
greater than or equal
+
+ query::age >= 31
+
-
+ in()
one of the values
+
+ query::age.in (30, 32, 34)
X.3 Map and Multimap Containers
+
+
-
+ in_range()
one of the values in range
+
+ query::age.in_range (begin, end)
std::map
and std::multimap
.
+
-
+ is_null()
value is NULL
+
+ query::age.is_null ()
+
+
+ is_not_null()
value is not NULL
+
+ query::age.is_not_null ()
in()
function accepts a maximum of five arguments.
+ Use the in_range()
function if you need to compare
+ to more than five values. This function accepts a pair of
+ standard C++ iterators and compares to all the values from
+ the begin
position inclusive and until and
+ excluding the end
position. The following
+ code fragment shows how we can use these functions:
-#pragma db object
-class person
-{
- ...
-private:
- #pragma db id auto
- unsigned long id_;
-
- std::map<unsigned short, float> age_weight_map_;
- ...
-};
-
-
- person_age_weight_map
)
- will contain the object id column of type unsigned long
- (called object_id
), the key column of type
- unsigned short
(called key
), and the value
- column of type std::string
(called value
).
-#pragma db object
-class person
-{
- ...
-private:
- #pragma db id auto
- unsigned long id_;
+ names.push_back ("John");
+ names.push_back ("Jack");
+ names.push_back ("Jane");
- #pragma db table("weight_map") \
- id_column ("person_id") \
- key_type ("INT UNSIGNED NOT NULL") \
- key_column ("age") \
- value_type ("DOUBLE NOT NULL") \
- value_column ("weight")
- std::map<unsigned short, float> age_weight_map_;
- ...
-};
+ query q1 (query::first.in ("John", "Jack", "Jane"));
+ query q2 (query::first.in_range (names.begin (), names.end ()));
- X.4 Using Custom Containers
-
- container_traits
class template specialization for
- your container. First determine the container kind (ordered, set,
- multiset, map, or multimap) for your container type. Then use a
- specialization for one of the standard C++ containers found in
- the common ODB runtime library (libodb
) as a base
- for your own implementation.--odb-epilogue
option and into the generated header
- file with the --hxx-prologue
option. As an example,
- suppose we have a hash table container for which we have the traits
- specialization implemented in the hashtable-traits.hxx
- file. Then, we can create an ODB compiler options file for this
- container and save it to hashtable.options
:
-# Options file for the hash table container.
-#
---odb-epilogue '#include "hashtable-traits.hxx"'
---hxx-prologue '#include "hashtable-traits.hxx"'
-
-
---options-file hashtable.options
+
+ query q ((query::first == "John" || query::first == "Jane") &&
+ query::age < 31);
-
-
-
- Y Relationships
-
- shared_ptr
/weak_ptr
,
- std::auto_ptr
, and raw pointers. Plus, ODB profile
- libraries, that available for commonly used frameworks and libraries
- (such as Boost and Qt), provide support for smart pointers found
- in these frameworks and libraries. It is also easy to add support
- for a custom smart pointer as discussed later in
- Section Y.4, "Using Custom Smart Pointers". Any supported
- smart pointer can be used in a data member as long as it can be
- explicitly constructed from the canonical object pointer (@@ ref).
- For example, we can use weak_ptr
if the object pointer
- is shared_ptr
.4.2 Parameter Binding
- odb::query
class encapsulates two
+ parts of information about the query: the query expression and
+ the query parameters. Parameters can be bound to C++ variables
+ either by value or by reference.shared_ptr
and weak_ptr
- smart pointers from the TR1 (std::tr1
) namespace.
-#pragma db object
-class employer
-{
- ...
-
- #pragma db id
- std::string name_;
-};
-
-#pragma db object
-class employee
-{
- ...
+ string name ("John");
- #pragma db id
- unsigned long id_;
+ query q1 (query::first == query::_val (name));
+ query q2 (query::first == query::_ref (name));
- std::string first_name_;
- std::string last_name_;
+ name = "Jane";
- shared_ptr<employer> employer_;
-};
+ db.query<person> (q1); // Find John.
+ db.query<person> (q2); // Find Jane.
- NULL
. To
- specify that a pointer always point to a valid object we can
- use the not_null
pragma, for example:odb::query
class provides two special functions,
+ _val()
and _ref()
, that allow us to
+ bind the parameter either by value or by reference, respectively.
+ In the ODB query language, if the binding is not specified
+ explicitly, the value semantic is used by default. In the
+ native query language, binding must always be specified
+ explicitly. For example:
-#pragma db object
-class employee
-{
- ...
+ query q1 (query::age < age); // By value.
+ query q2 (query::age < query::_val (age)); // By value.
+ query q3 (query::age < query::_ref (age)); // By reference.
- #pragma db not_null
- shared_ptr<employer> employer_;
-};
+ query q4 ("age < " + age); // Error.
+ query q5 ("age < " + query::_val (age)); // By value.
+ query q6 ("age < " + query::_ref (age)); // By reference.
- employee
object and the employer_
- pointer is NULL
, then the odb::null_pointer
- exception will be thrown.
-// Create an employer and a few employees.
-//
-unsigned long john_id, jane_id;
-{
- shared_ptr<employer> er (new employer ("Example Inc"));
- shared_ptr<employee> john (new employee ("John", "Doe"));
- shared_ptr<employee> jane (new employee ("Jane", "Doe"));
-
- john->employer_ = er;
- jane->employer_ = er;
-
- transaction t (db.begin ());
-
- db.persist (er);
- john_id = db.persist (john);
- jane_id = db.persist (jane);
-
- t.commit ();
-}
+
4.3 Executing a Query
- shared_ptr<employee> john (db.load<employee> (john_id));
- shared_ptr<employee> jane (db.load<employee> (jane_id));
+ database::query()
function template. It has two
+ overloaded versions:
+ template <typename T>
+ result<T>
+ query (bool cache = true);
- t.commit ();
-}
+ template <typename T>
+ result<T>
+ query (const odb::query<T>&, bool cache = true);
- employee
- objects we make sure that their employer_
pointers
- point to the same employer
object. Without a
- session, each employee
would have ended up pointing
- to its own, private instance of the Example Inc employer.query()
function is used to return all the
+ persistent objects of a given type stored in the database.
+ The second function uses the passed query instance to only return
+ objects matching the query criteria. The cache
argument
+ determines whether the objects' states should be cached in the
+ application's memory or if they should be returned by the database
+ system one by one as the iteration over the result progresses. The
+ result caching is discussed in detail in the next section.employee
object contains
- the employer
scope (derived from the employer_
- pointer) which in turn contains the name
member
- (derived from the employer::name_
data member of the
- pointed-to object). As a result, we can use the
- query::employer::name
expression while querying
- the database for the employee
objects. For example,
- the following transaction finds all the employees of Example Inc
- that have the Doe last name:query()
function, we have to
+ explicitly specify the object type we are querying. For example:
-typedef odb::query<employee> query;
-typedef odb::result<employee> result;
+ typedef odb::query<person> query;
+ typedef odb::result<person> result;
-session s;
-transaction t (db.begin ());
+ result all (db.query<person> ());
+ result johns (db.query<person> (query::first == "John"));
+
-result r (db->query<employee> (
- query::employer::name == "Example Inc" && query::last == "Doe"));
+
+ query q (query::first == "John");
-t.commit ();
+ result r1 (db.query<person> (q));
+ result r1 (db.query<person> (query::first == "John"));
+ NULL
and then, once the pointed-to object is
- made persistent and its identifier assigned, set the pointer
- to the correct value and update the object in the database.
+result
+find_minors (database& db, const query& name_query)
+{
+ return db.query<person> (name_query && query::age < 18);
+}
-
- Y.1 Unidirectional Relationships
+ 4.4 Query Result
- relationship
example in the odb-examples
- package.odb::result
class template, for example:Y.1.1 To-One Relationships
+
+ typedef odb::query<person> query;
+ typedef odb::result<person> result;
-
+
+ odb::result
+ as a handle to a stream, such as a file stream. While we can
+ make a copy of a result or assign one result to another, the
+ two instances will refer to the same result stream. Advancing
+ the current position in one instance will also advance it in
+ another. The result instance is only usable within the transaction
+ it was created in. Trying to manipulate the result after the
+ transaction has terminated leads to undefined behavior.odb::result
class template conforms to the
+ standard C++ sequence requirements and has the following
+ interface:
-#pragma db object
-class employer
+namespace odb
{
- ...
+ template <typename T>
+ class result
+ {
+ public:
+ typedef odb::result_iterator<T> iterator;
- #pragma db id
- std::string name_;
-};
+ public:
+ result ();
-#pragma db object
-class employee
-{
- ...
+ result (const result&);
- #pragma db id
- unsigned long id_;
+ result&
+ operator= (const result&);
- #pragma db not_null
- shared_ptr<employer> employer_;
-};
-
+ void
+ swap (result&)
-
-CREATE TABLE employer (
- name VARCHAR (255) NOT NULL PRIMARY KEY);
+ iterator
+ end ();
-CREATE TABLE employee (
- id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
- employer VARCHAR (255) NOT NULL REFERENCES employer (name));
+ public:
+ void
+ cache ();
+
+ bool
+ empty () const;
+
+ std::size_t
+ size () const;
+ };
+}
- Y.1.2 To-Many Relationships
+ cache()
function caches the returned objects'
+ state in the application's memory. We have already mentioned
+ result caching when we talked about query execution. As you
+ may remember the database::query()
function
+ caches the result unless instructed not to by the caller.
+ The cache()
function allows us to
+ cache the result at a later stage if it wasn't already
+ cached during query execution.
-#pragma db object
-class project
-{
- ...
+
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.empty()
function returns true
if
+ there are no objects in the result and false
otherwise.
+ The size()
function can only be called for cached results.
+ It returns the number of objects in the result. If we call this
+ function on an uncached result, the odb::result_not_cached
+ exception is thrown.begin()
and end()
functions
+ together with the odb::result<T>::iterator
+ type, for example:
+ result r (db.query<person> (query::first == "John"));
- #pragma db not_null unordered
- std::vector<shared_ptr<project> > projects_;
-};
+ for (result::iterator i (r.begin ()); i != r.end (); ++i)
+ {
+ ...
+ }
-
-CREATE TABLE project (
- name VARCHAR (255) NOT NULL PRIMARY KEY);
+
+
+namespace odb
+{
+ template <typename T>
+ class result_iterator
+ {
+ public:
+ T*
+ operator-> () const;
-CREATE TABLE employee_projects (
- object_id BIGINT UNSIGNED NOT NULL,
- value VARCHAR (255) NOT NULL REFERENCES project (name));
+ T&
+ operator* () const;
+
+ typename object_traits<T>::pointer_type
+ load ();
+
+ void
+ load (T& x);
+ };
+}
- *
or ->
operator,
+ the iterator will allocate a new instance of the object class
+ in the dynamic memory, load its state from the database
+ state, and return a reference or pointer to the new instance. The
+ iterator maintains the ownership of the returned object and will
+ return the same pointer for subsequent calls to either of these
+ operators until it is advanced to the next object or we call
+ the first load()
function (see below). For example:
-#pragma db object
-class employee
-{
- ...
+ result r (db.query<person> (query::first == "John"));
- #pragma db not_null unordered \
- id_column("employee_id") value_column("project_name")
- std::vector<shared_ptr<project> > projects_;
-};
+ for (result::iterator i (r.begin ()); i != r.end ();)
+ {
+ cout << i->last () << endl; // Create an object.
+ person& p (*i); // Reference to the same object.
+ cout << p.age () << endl;
+ ++i; // Free the object.
+ }
- employee_projects
table would then
- look like this:result_iterator::load()
functions are
+ similar to database::load()
. The first function
+ returns a dynamically allocated instance of the current
+ object. As an optimization, if the iterator already owns an object
+ as a result of an earlier
+ call to the *
or ->
operator, then it
+ relinquishes the ownership of this object and returns it instead.
+ This allows us to write code like this without worrying about
+ a double allocation:
-CREATE TABLE employee_projects (
- employee_id BIGINT UNSIGNED NOT NULL,
- project_name VARCHAR (255) NOT NULL REFERENCES project (name));
-
+
+ result r (db.query<person> (query::first == "John"));
+ for (result::iterator i (r.begin ()); i != r.end (); ++i)
+ {
+ if (i->last == "Doe")
+ {
+ auto_ptr p (i.load ());
+ ...
+ }
+ }
+
- Y.2 Bidirectional Relationships
+ load()
call to the *
or ->
+ operator results in the allocation of a new object.load()
function allows
+ us to load the current object's state into an existing instance.
+ For example:
-class employee;
-
-#pragma db object
-class position
-{
- ...
+ result r (db.query<person> (query::first == "John"));
- #pragma db id
- unsigned long id_;
+ person p;
+ for (result::iterator i (r.begin ()); i != r.end (); ++i)
+ {
+ i.load (p);
+ cout << p.last () << endl;
+ cout << i.age () << endl;
+ }
+
- weak_ptr<employee> employee_;
-};
-#pragma db object
-class employee
-{
- ...
+
- #pragma db id
- unsigned long id_;
+ 5 Containers
- #pragma db not_null
- shared_ptr<position> position_;
-};
- std::vector
, std::list
, std::set
,
+ std::multiset
, std::map
, and
+ std::multimap
. Plus, ODB profile libraries, that are
+ available for commonly used frameworks and libraries (such as Boost
+ and Qt), provide persistence support for containers found in these
+ frameworks and libraries. It is also easy to persist custom
+ container types as discussed later in Section 5.4,
+ "Using Custom Containers".
#pragma db object
-class position: public enable_shared_from_this<position>
+class person
{
...
-
- void
- fill (shared_ptr<employee> e)
- {
- employee_ = e;
- e->positions_ = shared_from_this ();
- }
-
private:
- weak_ptr<employee> employee_;
-};
-
-#pragma db object
-class employee
-{
+ std::vector<std::name> nicknames_;
...
-
-private:
- friend class position;
-
- #pragma db not_null
- shared_ptr<position> position_;
};
+
-CREATE TABLE position (
- id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
- employee BIGINT UNSIGNED REFERENCES employee (id));
-
-CREATE TABLE employee (
- id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
- position BIGINT UNSIGNED NOT NULL REFERENCES position (id));
-
+ position
table to a row
- in the employee
table. We also have a reference
- from this same row in the employee
table back to
- the row in the position
table. From the relational
- point of view, one of these references is redundant since
- in SQL we can easily navigate in both directions using just one
- of these references.inverse
pragma (Section 5.3.7,
- "inverse
") which tells the ODB compiler that
- a pointer is the inverse side of a bidirectional relationship.
- Either side of a relationship can be made inverse. For example:odb::access
class should be made a friend of
+ the value or key type. For example:
-#pragma db object
-class position
+#pragma db value
+class name
{
+public:
+ name (const std::string&, const std::string&);
+ ...
+private:
+ friend class odb::access;
+ name ();
...
-
- #pragma db inverse(position_)
- weak_ptr<employee> employee_;
};
#pragma db object
-class employee
+class person
{
...
+private:
+ #pragma db id auto
+ unsigned long id_;
- #pragma db not_null
- shared_ptr<position> position_;
+ std::vector<name> aliases_;
+ ...
};
-
-CREATE TABLE position (
- id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
-
-CREATE TABLE employee (
- id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
- position BIGINT UNSIGNED NOT NULL REFERENCES position (id));
-
- unordered
")
- because the contents of such a container are implicitly built from
- the direct side of the relationship which does not contain the
- element order (index).5.1 Ordered Containers
- inverse
example
- in the odb-examples
package.std::vector
+ and std::list
. While elements in std::set
+ are also kept in a specific order, this order is not based on an
+ integer index but rather on the relationship between elements. As
+ a result, std::set
is not considered an ordered
+ container for the purpose of persistence.Y.2.1 One-to-One Relationships
+
-class employee;
-
#pragma db object
-class position
+class person
{
...
-
- #pragma db id
+private:
+ #pragma db id auto
unsigned long id_;
- #pragma db inverse(position_)
- weak_ptr<employee> employee_;
+ std::vector<std::string> nicknames_;
+ ...
};
+
+
+ person_nicknames
) will
+ contain the object id column of type unsigned long
+ (called object_id
), the index column of an integer type
+ (called index
), and the value column of type
+ std::string
(called value
).
#pragma db object
-class employee
+class person
{
...
-
- #pragma db id
+private:
+ #pragma db id auto
unsigned long id_;
- #pragma db not_null
- shared_ptr<position> position_;
+ #pragma db table("nicknames") \
+ id_column ("person_id") \
+ index_type ("SMALLINT UNSIGNED NOT NULL") \
+ index_column ("nickname_number") \
+ value_type ("VARCHAR(255) NOT NULL") \
+ value_column ("nickname")
+ std::vector<std::string> nicknames_;
+ ...
};
- unordered
pragma (see Chapter 9, "ODB
+ Pragma Language" for details). For example:
-CREATE TABLE position (
- id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
+
+#pragma db object
+class person
+{
+ ...
+private:
+ #pragma db id auto
+ unsigned long id_;
-CREATE TABLE employee (
- id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
- position BIGINT UNSIGNED NOT NULL REFERENCES position (id));
+ #pragma db unordered
+ std::vector<std::string> nicknames_;
+ ...
+};
-
-CREATE TABLE position (
- id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
- employee BIGINT UNSIGNED REFERENCES employee (id));
+
+ 5.2 Set and Multiset Containers
-CREATE TABLE employee (
- id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
- std::set
and std::multiset
.Y.2.2 One-to-Many Relationships
+
-class employee;
-
#pragma db object
-class employer
+class person
{
...
+private:
+ #pragma db id auto
+ unsigned long id_;
- #pragma db id
- std::string name_;
-
- #pragma db not_null inverse(employer_)
- std::vector<weak_ptr<employee> > employees_
+ std::set<std::string> emails_;
+ ...
};
+
+
+ person_emails
) will
+ contain the object id column of type unsigned long
+ (called object_id
) and the value column of type
+ std::string
(called value
).
#pragma db object
-class employee
+class person
{
...
-
- #pragma db id
+private:
+ #pragma db id auto
unsigned long id_;
- #pragma db not_null
- shared_ptr<employer> employer_;
+ #pragma db table("emails") \
+ id_column ("person_id") \
+ value_type ("VARCHAR(255) NOT NULL") \
+ value_column ("email")
+ std::set<std::string> emails_;
+ ...
};
- employer
) is inverse as in the code
- above, then the resulting database schema looks like this:
-CREATE TABLE employer (
- name VARCHAR (255) NOT NULL PRIMARY KEY);
-
-CREATE TABLE employee (
- id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
- employer VARCHAR (255) NOT NULL REFERENCES employer (name));
-
-
- employee
) of this
- relationship is made inverse, then the database tables will change
- as follows:
-CREATE TABLE employer (
- name VARCHAR (255) NOT NULL PRIMARY KEY);
-
-CREATE TABLE employer_employees (
- object_id VARCHAR (255) NOT NULL,
- value BIGINT UNSIGNED NOT NULL REFERENCES employee (id));
+
+ 5.3 Map and Multimap Containers
-CREATE TABLE employee (
- id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
- std::map
and std::multimap
.Y.2.3 Many-to-Many Relationships
+
-class employee;
-
#pragma db object
-class project
+class person
{
...
+private:
+ #pragma db id auto
+ unsigned long id_;
- #pragma db id
- std::string name_;
-
- #pragma db not_null inverse(projects_)
- std::vector<weak_ptr<employee> > employees_;
+ std::map<unsigned short, float> age_weight_map_;
+ ...
};
+
+
+ person_age_weight_map
)
+ will contain the object id column of type unsigned long
+ (called object_id
), the key column of type
+ unsigned short
(called key
), and the value
+ column of type std::string
(called value
).
#pragma db object
-class employee
+class person
{
...
-
- #pragma db id
+private:
+ #pragma db id auto
unsigned long id_;
- #pragma db not_null unordered
- std::vector<shared_ptr<project> > projects_;
+ #pragma db table("weight_map") \
+ id_column ("person_id") \
+ key_type ("INT UNSIGNED NOT NULL") \
+ key_column ("age") \
+ value_type ("DOUBLE NOT NULL") \
+ value_column ("weight")
+ std::map<unsigned short, float> age_weight_map_;
+ ...
};
- 5.4 Using Custom Containers
-
-CREATE TABLE project (
- name VARCHAR (255) NOT NULL PRIMARY KEY);
+
container_traits
class template specialization for
+ your container. First determine the container kind (ordered, set,
+ multiset, map, or multimap) for your container type. Then use a
+ specialization for one of the standard C++ containers found in
+ the common ODB runtime library (libodb
) as a base
+ for your own implementation.--odb-epilogue
option and into the generated header
+ file with the --hxx-prologue
option. As an example,
+ suppose we have a hash table container for which we have the traits
+ specialization implemented in the hashtable-traits.hxx
+ file. Then, we can create an ODB compiler options file for this
+ container and save it to hashtable.options
:
+# Options file for the hash table container.
+#
+--odb-epilogue '#include "hashtable-traits.hxx"'
+--hxx-prologue '#include "hashtable-traits.hxx"'
-
+--options-file hashtable.options
+
+
+
+
-
-CREATE TABLE project (
- name VARCHAR (255) NOT NULL PRIMARY KEY);
-CREATE TABLE project_employees (
- object_id VARCHAR (255) NOT NULL,
- value BIGINT UNSIGNED NOT NULL REFERENCES employee (id));
+
+ 6 Relationships
-CREATE TABLE employee (
- id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
- shared_ptr
/weak_ptr
,
+ std::auto_ptr
, and raw pointers. Plus, ODB profile
+ libraries, that available for commonly used frameworks and libraries
+ (such as Boost and Qt), provide support for smart pointers found
+ in these frameworks and libraries. It is also easy to add support
+ for a custom smart pointer as discussed later in
+ Section 6.4, "Using Custom Smart Pointers". Any supported
+ smart pointer can be used in a data member as long as it can be
+ explicitly constructed from the canonical object pointer (@@ ref).
+ For example, we can use weak_ptr
if the object pointer
+ is shared_ptr
.Y.3 Lazy Pointers
+ shared_ptr
and weak_ptr
+ smart pointers from the TR1 (std::tr1
) namespace.
-class employee;
-
#pragma db object
class employer
{
@@ -3535,9 +3498,6 @@ class employer
#pragma db id
std::string name_;
-
- #pragma db not_null inverse(employer_)
- std::vector<weak_ptr<employee> > employees_;
};
#pragma db object
@@ -3548,1357 +3508,1449 @@ class employee
#pragma db id
unsigned long id_;
- #pragma db not_null
+ std::string first_name_;
+ std::string last_name_;
+
shared_ptr<employer> employer_;
};
- NULL
. To
+ specify that a pointer always point to a valid object we can
+ use the not_null
pragma, for example:
-unsigned long id = ...
-string name;
-
-session s;
-transaction t (db.begin ());
-
-shared_ptr<employee> e (db.load<employee> (id));
-name = e->employer_->name_;
+#pragma db object
+class employee
+{
+ ...
-t.commit ();
+ #pragma db not_null
+ shared_ptr<employer> employer_;
+};
- employee
object: the employer_
- pointer is also automatically loaded which means the employer
- object corresponding to this employee is also loaded. But the
- employer
object in turn contains the list of pointers
- to all the employees, which are also loaded. A a result, when object
- relationships are involved, a simple transaction like the above can
- load many more objects than is necessary.employee
object and the employer_
+ pointer is NULL
, then the odb::null_pointer
+ exception will be thrown.odb::lazy_shared_ptr
and
- odb::lazy_weak_ptr
for TR1 shared_ptr
and
- weak_ptr
, odb::lazy_auto_ptr
for
- std::auto_ptr
, and odb::lazy_ptr
for raw
- pointers. The ODB profile libraries provide lazy pointer
- implementations for smart pointers from popular frameworks and
- libraries.
+// Create an employer and a few employees.
+//
+unsigned long john_id, jane_id;
+{
+ shared_ptr<employer> er (new employer ("Example Inc"));
+ shared_ptr<employee> john (new employee ("John", "Doe"));
+ shared_ptr<employee> jane (new employee ("Jane", "Doe"));
-
load()
. This function loads the
- pointed-to object if it hasn't already been loaded. After
- the call to this function, the lazy pointer can be used
- in the the same way as its eager counterpart. The load()
- function also returns the eager pointer, in case you need to pass
- it around. For a lazy weak pointer, the
- load()
function also locks the pointer.
-class employee;
+ db.persist (er);
+ john_id = db.persist (john);
+ jane_id = db.persist (jane);
-#pragma db object
-class employer
+ t.commit ();
+}
+
+// Load a few employee objects and print their employer.
+//
{
- ...
+ session s;
+ transaction t (db.begin ());
- #pragma db not_null inverse(employer_)
- std::vector<lazy_weak_ptr<employee> > employees_;
-};
+ shared_ptr<employee> john (db.load<employee> (john_id));
+ shared_ptr<employee> jane (db.load<employee> (jane_id));
-#pragma db object
-class employee
-{
- ...
+ cout << john->employer_->name_ << endl;
+ cout << jane->employer_->name_ << endl;
- #pragma db not_null
- lazy_shared_ptr<employer> employer_;
-};
+ t.commit ();
+}
- employee
+ objects we make sure that their employer_
pointers
+ point to the same employer
object. Without a
+ session, each employee
would have ended up pointing
+ to its own, private instance of the Example Inc employer.employee
object contains
+ the employer
scope (derived from the employer_
+ pointer) which in turn contains the name
member
+ (derived from the employer::name_
data member of the
+ pointed-to object). As a result, we can use the
+ query::employer::name
expression while querying
+ the database for the employee
objects. For example,
+ the following transaction finds all the employees of Example Inc
+ that have the Doe last name:
-unsigned long id = ...
-string name;
+typedef odb::query<employee> query;
+typedef odb::result<employee> result;
session s;
transaction t (db.begin ());
-shared_ptr<employee> e (db.load<employee> (id));
-e->employer_.load ();
-name = e->employer_->name_;
+result r (db->query<employee> (
+ query::employer::name == "Example Inc" && query::last == "Doe"));
+
+for (result::iterator i (r.begin ()); i != r.end (); ++i)
+ cout << i->first_ << " " << i->last_ << endl;
t.commit ();
- NULL
and then, once the pointed-to object is
+ made persistent and its identifier assigned, set the pointer
+ to the correct value and update the object in the database.
-template <class T>
-class lazy_ptr
-{
-public:
- //
- // The eager pointer interface.
- //
+
6.1 Unidirectional Relationships
- // Lazy loading interface.
- //
-public:
- bool loaded () const;
- eager_ptr<T> load () const;
+ relationship
example in the odb-examples
+ package.6.1.1 To-One Relationships
- // Initialization with a persistent loaded object.
- //
- template <class Y> lazy_ptr (database&, Y*);
- template <class Y> lazy_ptr (database&, const eager_ptr<Y>&);
+
+#pragma db object
+class employer
+{
+ ...
- // Initialization with a persistent unloaded object.
- //
- template <class ID> lazy_ptr (database&, const ID&);
+ #pragma db id
+ std::string name_;
+};
- template <class ID> void reset (database&, const ID&);
+#pragma db object
+class employee
+{
+ ...
- // Query object id and database of a persistent object.
- //
- template <class O /* = T */>
- object_traits<O>::id_type object_id () const;
+ #pragma db id
+ unsigned long id_;
- odb::database& database () const;
+ #pragma db not_null
+ shared_ptr<employer> employer_;
};
- load()
function
- returns the strong (shared) eager pointer. The following
- transaction demonstrates the use of a lazy weak pointer based on
- the employer
and employee
classes
- presented earlier.
-typedef std::vector<lazy_weak_ptr<employee> > employees;
+
-
+CREATE TABLE employer (
+ name VARCHAR (255) NOT NULL PRIMARY KEY);
-session s;
-transaction t (db.begin ());
+CREATE TABLE employee (
+ id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+ employer VARCHAR (255) NOT NULL REFERENCES employer (name));
+
-shared_ptr<employer> er (db.load<employer> ("Example Inc"));
-employees& es (er->employees ());
+ 6.1.2 To-Many Relationships
-for (employees::iterator i (es.begin ()); i != es.end (); ++i)
+
+#pragma db object
+class project
{
- // We are only interested in employees with object id less than
- // 100.
- //
- lazy_weak_ptr<employee>& lwp (*i);
+ ...
- if (lwp.object_id<employee> () < 100)
- {
- shared_ptr<employee> e (lwp.load ()); // Load and lock.
- cout << e->first_ << " " << e->last_ << endl;
- }
-}
+ #pragma db id
+ std::string name_;
+};
-t.commit ();
+#pragma db object
+class employee
+{
+ ...
+
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db not_null unordered
+ std::vector<shared_ptr<project> > projects_;
+};
- employer
- object. This way, if we later need to re-examine this
- employee
object, the pointer will already
- be loaded.
+CREATE TABLE project (
+ name VARCHAR (255) NOT NULL PRIMARY KEY);
+
+CREATE TABLE employee (
+ id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
+
+CREATE TABLE employee_projects (
+ object_id BIGINT UNSIGNED NOT NULL,
+ value VARCHAR (255) NOT NULL REFERENCES project (name));
+
+
+
-session s;
-transaction t (db.begin ());
+#pragma db object
+class employee
+{
+ ...
-shared_ptr<employer> er (db.load<employer> ("Example Inc"));
-shared_ptr<employee> e (new employee ("John", "Doe"));
+ #pragma db not_null unordered \
+ id_column("employee_id") value_column("project_name")
+ std::vector<shared_ptr<project> > projects_;
+};
+
-e->employer_ = er;
-er->employees ().push_back (e);
+ employee_projects
table would then
+ look like this:
+CREATE TABLE employee_projects (
+ employee_id BIGINT UNSIGNED NOT NULL,
+ project_name VARCHAR (255) NOT NULL REFERENCES project (name));
- employees_
list of
- pointers is an inverse side of a bidirectional relationship
- and is effectively read-only, from the persistence point of
- view.6.2 Bidirectional Relationships
+
+
-lazy_shared_ptr<employer> er (db, std::string ("Example Inc"));
-shared_ptr<employee> e (new employee ("John", "Doe"));
+class employee;
-e->employer_ = er;
+#pragma db object
+class position
+{
+ ...
-session s;
-transaction t (db.begin ());
+ #pragma db id
+ unsigned long id_;
-db.persist (e);
+ weak_ptr<employee> employee_;
+};
-t.commit ();
-
+#pragma db object
+class employee
+{
+ ...
- Y.4 Using Custom Smart Pointers
+ #pragma db id
+ unsigned long id_;
- pointer_traits
class template specialization for
- your pointer. The first step is to determine the pointer kind
- since the interface of the pointer_traits
specialization
- varies depending on the pointer kind. The supported pointer kinds
- are: raw (raw pointer or equivalent, that is, unmanaged),
- unique (smart pointer that doesn't support sharing),
- shared (smart pointer that supports sharing), and
- weak (weak counterpart of the shared pointer). Any of
- these pointers can be lazy, which also affects the
- interface of the pointer_traits
specialization.libodb
) as a base
- for your own implementation.
+#pragma db object
+class position: public enable_shared_from_this<position>
+{
+ ...
-
-
+ --odb-epilogue
option and into the generated header
- file with the --hxx-prologue
option. As an example,
- suppose we have the smart_ptr
smart pointer for which
- we have the traits specialization implemented in the
- smart-ptr-traits.hxx
file. Then, we can create an ODB
- compiler options file for this pointer and save it to
- smart-ptr.options
:
-# Options file for smart_ptr.
-#
---odb-epilogue '#include "smart-ptr-traits.hxx"'
---hxx-prologue '#include "smart-ptr-traits.hxx"'
-
+private:
+ weak_ptr<employee> employee_;
+};
- smart_ptr
,
- we can pass the following option to make sure it is recognized by the
- ODB compiler as a smart pointer and the traits file is included in the
- generated code:
---options-file smart-ptr.options
-
+private:
+ friend class position;
-
+CREATE TABLE position (
+ id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+ employee BIGINT UNSIGNED REFERENCES employee (id));
-
- Z Composite Value Types
+CREATE TABLE employee (
+ id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+ position BIGINT UNSIGNED NOT NULL REFERENCES position (id));
+ class
or struct
- type that is mapped to more than one database column. To declare
- a composite value type we use the db value
pragma,
- for example:position
table to a row
+ in the employee
table. We also have a reference
+ from this same row in the employee
table back to
+ the row in the position
table. From the relational
+ point of view, one of these references is redundant since
+ in SQL we can easily navigate in both directions using just one
+ of these references.inverse
pragma (Section 9.3.7,
+ "inverse
") which tells the ODB compiler that
+ a pointer is the inverse side of a bidirectional relationship.
+ Either side of a relationship can be made inverse. For example:
-#pragma db value
-class basic_name
+#pragma db object
+class position
{
...
- std::string first_;
- std::string last_;
+ #pragma db inverse(position_)
+ weak_ptr<employee> employee_;
};
-
-
- odb::access
class
- should be declared a friend of this value type. For example:
-#pragma db value
-class basic_name
+#pragma db object
+class employee
{
-public:
- basic_name (const std::string& first, const std::string& last);
-
...
-private:
- friend class odb::access;
-
- basic_name () {} // Needed for storing basic_name in containers.
-
- std::string first_;
- std::string last_;
+ #pragma db not_null
+ shared_ptr<position> position_;
};
- composite
- example in the odb-examples
package.
+CREATE TABLE position (
+ id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
-
-
-#pragma db value
-class basic_name
-{
- ...
+CREATE TABLE employee (
+ id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+ position BIGINT UNSIGNED NOT NULL REFERENCES position (id));
+
- std::string first_;
- std::string last_;
-};
+ 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).inverse
example
+ in the odb-examples
package.6.2.1 One-to-One Relationships
- std::string nickname_;
- basic_names aliases_;
-};
+
+class employee;
+
+#pragma db object
+class position
{
...
- std::string title_;
- name_extras extras_;
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db inverse(position_)
+ weak_ptr<employee> employee_;
};
#pragma db object
-class person
+class employee
{
...
- #pragma db id auto
+ #pragma db id
unsigned long id_;
- name name_;
+ #pragma db not_null
+ shared_ptr<position> position_;
};
-
+ person
object presented above
- contains the name
scope (derived from the name_
- data member) which in turn contains the extras
member
- (derived from the name::extras_
data member of the
- composite value type). The process continues reqursively for nested
- composite value types and, as a result, we can use the
- query::name::extras::nickname
expression while querying
- the database for the person
objects. For example:
-typedef odb::query<person> query;
-typedef odb::result<person> result;
+
+CREATE TABLE position (
+ id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
-transaction t (db->begin ());
+CREATE TABLE employee (
+ id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+ position BIGINT UNSIGNED NOT NULL REFERENCES position (id));
+
-result r (db->query<person> (
- query::name::extras::nickname == "Squeaky"));
+
+CREATE TABLE position (
+ id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+ employee BIGINT UNSIGNED REFERENCES employee (id));
-t.commit ();
+CREATE TABLE employee (
+ id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
- Z.1 Composite Value Column and Table Names
+ 6.2.2 One-to-Many Relationships
- db column
pragma (@@ ref). For composite value
- types things are slightly more complicated since it is mapped to
- multiple columns. Consider the following example:
-#pragma db value
-class name
+
-
+class employee;
+
+#pragma db object
+class employer
{
...
- std::string first_;
- std::string last_;
+ #pragma db id
+ std::string name_;
+
+ #pragma db not_null inverse(employer_)
+ std::vector<weak_ptr<employee> > employees_
};
#pragma db object
-class person
+class employee
{
...
- #pragma db id auto
+ #pragma db id
unsigned long id_;
- name name_;
+ #pragma db not_null
+ shared_ptr<employer> employer_;
};
-
+ first_
and last_
- members are constructed by using the sanitized name of the
- person::name_
member as a prefix and the names of the
- members in the value type (first_
and last_
)
- as suffixes. As a result, the database schema for the above classes
- will look like this:employer
) is inverse as in the code
+ above, then the resulting database schema looks like this:
-CREATE TABLE person (
+CREATE TABLE employer (
+ name VARCHAR (255) NOT NULL PRIMARY KEY);
+
+CREATE TABLE employee (
id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
- name_first TEXT NOT NULL,
- name_last TEXT NOT NULL);
+ employer VARCHAR (255) NOT NULL REFERENCES employer (name));
- db column
pragma as shown in the following
- example:employee
) of this
+ relationship is made inverse, then the database tables will change
+ as follows:
+CREATE TABLE employer (
+ name VARCHAR (255) NOT NULL PRIMARY KEY);
+
+CREATE TABLE employer_employees (
+ object_id VARCHAR (255) NOT NULL,
+ value BIGINT UNSIGNED NOT NULL REFERENCES employee (id));
+
+CREATE TABLE employee (
+ id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
+
+
+ 6.2.3 Many-to-Many Relationships
+
+
-#pragma db value
-class name
+class employee;
+
+#pragma db object
+class project
{
...
- #pragma db column("first_name")
- std::string first_;
+ #pragma db id
+ std::string name_;
- #pragma db column("last_name")
- std::string last_;
+ #pragma db not_null inverse(projects_)
+ std::vector<weak_ptr<employee> > employees_;
};
#pragma db object
-class person
+class employee
{
...
- #pragma db column("person_")
- name name_;
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db not_null unordered
+ std::vector<shared_ptr<project> > projects_;
};
-
-CREATE TABLE person (
- id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
- person_first_name TEXT NOT NULL,
- person_last_name TEXT NOT NULL);
-
-
-
-#pragma db object
-class person
-{
- ...
+CREATE TABLE employee (
+ id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
- #pragma db column("")
- name name_;
-};
+CREATE TABLE employee_projects (
+ object_id BIGINT UNSIGNED NOT NULL,
+ value VARCHAR (255) NOT NULL REFERENCES project (name));
-
-CREATE TABLE person (
- id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
- first_name TEXT NOT NULL,
- last_name TEXT NOT NULL);
+CREATE TABLE project (
+ name VARCHAR (255) NOT NULL PRIMARY KEY);
+
+CREATE TABLE project_employees (
+ object_id VARCHAR (255) NOT NULL,
+ value BIGINT UNSIGNED NOT NULL REFERENCES employee (id));
+
+CREATE TABLE employee (
+ id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
- db column
either the db value_column
- (@@ ref) or db key_column
(@@ ref) pragmas are used to
- specify the column prefix.6.3 Lazy Pointers
-
-#pragma db value
-class name
+class employee;
+
+#pragma db object
+class employer
{
...
- std::string first_;
- std::string last_;
- std::vector<std::string> nicknames_;
+ #pragma db id
+ std::string name_;
+
+ #pragma db not_null inverse(employer_)
+ std::vector<weak_ptr<employee> > employees_;
};
#pragma db object
-class person
+class employee
{
...
- name name_;
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db not_null
+ shared_ptr<employer> employer_;
};
-
-CREATE TABLE `person_name_nicknames` (
- `object_id` BIGINT UNSIGNED NOT NULL,
- `index` BIGINT UNSIGNED NOT NULL,
- `value` TEXT NOT NULL)
+
+unsigned long id = ...
+string name;
-CREATE TABLE person (
- id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
- name_first TEXT NOT NULL,
- name_last TEXT NOT NULL);
+session s;
+transaction t (db.begin ());
+
+shared_ptr<employee> e (db.load<employee> (id));
+name = e->employer_->name_;
+
+t.commit ();
- db table
(@@ ref) pragma, for example:employee
object: the employer_
+ pointer is also automatically loaded which means the employer
+ object corresponding to this employee is also loaded. But the
+ employer
object in turn contains the list of pointers
+ to all the employees, which are also loaded. A a result, when object
+ relationships are involved, a simple transaction like the above can
+ load many more objects than is necessary.odb::lazy_shared_ptr
and
+ odb::lazy_weak_ptr
for TR1 shared_ptr
and
+ weak_ptr
, odb::lazy_auto_ptr
for
+ std::auto_ptr
, and odb::lazy_ptr
for raw
+ pointers. The ODB profile libraries provide lazy pointer
+ implementations for smart pointers from popular frameworks and
+ libraries.load()
. This function loads the
+ pointed-to object if it hasn't already been loaded. After
+ the call to this function, the lazy pointer can be used
+ in the the same way as its eager counterpart. The load()
+ function also returns the eager pointer, in case you need to pass
+ it around. For a lazy weak pointer, the
+ load()
function also locks the pointer.
-#pragma db value
-class name
+class employee;
+
+#pragma db object
+class employer
{
...
- #pragma db table("nickname")
- std::vector<std::string> nicknames_;
+ #pragma db not_null inverse(employer_)
+ std::vector<lazy_weak_ptr<employee> > employees_;
};
#pragma db object
-class person
+class employee
{
...
- #pragma db table("person_")
- name name_;
+ #pragma db not_null
+ lazy_shared_ptr<employer> employer_;
};
-
-CREATE TABLE `person_nickname` (
- `object_id` BIGINT UNSIGNED NOT NULL,
- `index` BIGINT UNSIGNED NOT NULL,
- `value` TEXT NOT NULL)
+
+unsigned long id = ...
+string name;
+
+session s;
+transaction t (db.begin ());
+
+shared_ptr<employee> e (db.load<employee> (id));
+e->employer_.load ();
+name = e->employer_->name_;
+
+t.commit ();
-
+template <class T>
+class lazy_ptr
+{
+public:
+ //
+ // The eager pointer interface.
+ //
-
- Q Session
+ // Initialization/assignment from an eager pointer.
+ //
+public:
+ template <class Y> lazy_ptr (const eager_ptr<Y>&);
+ template <class Y> lazy_ptr& operator= (const eager_ptr<Y>&);
- odb::session
class and is automatically terminated
- when this instance is destroyed. You will need to include the
- <odb/session.hxx>
header file to make this class
- available in your application. For example:load()
function
+ returns the strong (shared) eager pointer. The following
+ transaction demonstrates the use of a lazy weak pointer based on
+ the employer
and employee
classes
+ presented earlier.
-#include <odb/database.hxx>
-#include <odb/session.hxx>
-#include <odb/transaction.hxx>
+typedef std::vector<lazy_weak_ptr<employee> > employees;
-using namespace odb;
+session s;
+transaction t (db.begin ());
-{
- session s;
+shared_ptr<employer> er (db.load<employer> ("Example Inc"));
+employees& es (er->employees ());
- // First transaction.
+for (employees::iterator i (es.begin ()); i != es.end (); ++i)
+{
+ // We are only interested in employees with object id less than
+ // 100.
//
- {
- transaction t (db.begin ());
- ...
- t.commit ();
- }
+ lazy_weak_ptr<employee>& lwp (*i);
- // Second transaction.
- //
+ if (lwp.object_id<employee> () < 100)
{
- transaction t (db.begin ());
- ...
- t.commit ();
+ shared_ptr<employee> e (lwp.load ()); // Load and lock.
+ cout << e->first_ << " " << e->last_ << endl;
}
-
- // Session 's' is terminated here.
}
+
+t.commit ();
- odb::session
class has the following interface:employer
+ object. This way, if we later need to re-examine this
+ employee
object, the pointer will already
+ be loaded.
-namespace odb
-{
- class session
- {
- public:
- session ();
- ~session ();
+
-
+session s;
+transaction t (db.begin ());
- // Current session interface.
- //
- public:
- static session&
- current ();
+shared_ptr<employer> er (db.load<employer> ("Example Inc"));
+shared_ptr<employee> e (new employee ("John", "Doe"));
- static bool
- has_current ();
+e->employer_ = er;
+er->employees ().push_back (e);
- static void
- current (session&);
+db.persist (e);
+t.commit ();
+
- static void
- reset_current ();
+ employees_
list of
+ pointers is an inverse side of a bidirectional relationship
+ and is effectively read-only, from the persistence point of
+ view.
+lazy_shared_ptr<employer> er (db, std::string ("Example Inc"));
+shared_ptr<employee> e (new employee ("John", "Doe"));
- template <typename T>
- object_traits<T>::pointer_type
- find (database_type&, const object_traits<T>::id_type&) const;
+e->employer_ = er;
- template <typename T>
- void
- erase (database_type&, const object_traits<T>::id_type&);
- };
-}
-
+session s;
+transaction t (db.begin ());
- odb::already_in_session
exception. The
- destructor clears the current session for this thread if this
- session is the current one.current()
accessor returns the currently active
- session for this thread. If there is no active session, this function
- throws the odb::not_in_session
exception. We can check
- whether there is a session in effect in this thread using the
- has_current()
static function.current()
modifier allows us to set the
- current session for this thread. The reset_current
- static function clears the current session. These two functions
- allow for more advanced use cases, such as multiplexing
- between two or more sessions in the same thread.6.4 Using Custom Smart Pointers
- Q.1 Object Cache
+ pointer_traits
class template specialization for
+ your pointer. The first step is to determine the pointer kind
+ since the interface of the pointer_traits
specialization
+ varies depending on the pointer kind. The supported pointer kinds
+ are: raw (raw pointer or equivalent, that is, unmanaged),
+ unique (smart pointer that doesn't support sharing),
+ shared (smart pointer that supports sharing), and
+ weak (weak counterpart of the shared pointer). Any of
+ these pointers can be lazy, which also affects the
+ interface of the pointer_traits
specialization.database::persist()
function (@@ ref), loaded
- by calling the database::load()
or database::find()
- function (@@ ref), or loaded by iterating over a query result (@@ ref),
- the pointer to the persistent object, in the form of the canonical object
- pouinter (@@ ref), is stored in the session. For as long as the
- session is in effect, any subsequent calls to load the same object will
- return the cached instance. When an object's state is deleted from the
- database with the database::erase()
function (@@ ref), the
- cached object pointer is removed from the session. For example:libodb
) as a base
+ for your own implementation.
-shared_ptr<person> p (new person ("John", "Doe"));
+
--odb-epilogue
option and into the generated header
+ file with the --hxx-prologue
option. As an example,
+ suppose we have the smart_ptr
smart pointer for which
+ we have the traits specialization implemented in the
+ smart-ptr-traits.hxx
file. Then, we can create an ODB
+ compiler options file for this pointer and save it to
+ smart-ptr.options
:
+# Options file for smart_ptr.
+#
+--odb-epilogue '#include "smart-ptr-traits.hxx"'
+--hxx-prologue '#include "smart-ptr-traits.hxx"'
+
-unsigned long id (db.persist (p)); // p is cached in s.
-shared_ptr<person> p1 (db.load<person> (id)); // p1 same as p.
+ smart_ptr
,
+ we can pass the following option to make sure it is recognized by the
+ ODB compiler as a smart pointer and the traits file is included in the
+ generated code:
+--options-file smart-ptr.options
+ std::auto_ptr
,
- as an object pointer are never cached since it is not possible to have
- two such pointers pointing to the same object. When an object is
- persisted via a pointer or loaded as a dynamically allocated instance,
- objects with both raw and shared pointers as object pointers are
- cached. If an object is persisted as a reference or loaded into
- a pre-allocated instance, the object is only cached if its object
- pointer is a raw pointer.database::persist()
function (in contrast,
- when loaded, objects are always created and cached as non-constant).
- If we try to load an object as non-constant that was previously
- persisted and cached as constant, the odb::const_object
- exception is thrown. The following transaction illustartes the
- situation where this would happen:
-shared_ptr<const person> p (new person ("John", "Doe"));
-session s;
-transaction t (db.begin ());
+
+ 7 Composite Value Types
-unsigned long id (db.persist (p));
-shared_ptr<const person> p1 (db.load<const person> (id)); // Ok.
-shared_ptr<person> p2 (db.load<person> (id)); // Exception.
+ class
or struct
+ type that is mapped to more than one database column. To declare
+ a composite value type we use the db value
pragma,
+ for example:
+#pragma db value
+class basic_name
+{
+ ...
+ std::string first_;
+ std::string last_;
+};
+
-
+ odb::access
class
+ should be declared a friend of this value type. For example:
+#pragma db value
+class basic_name
+{
+public:
+ basic_name (const std::string& first, const std::string& last);
-
+ 4 Querying the Database
+ ...
- --generate-query
ODB compiler option.
- typedef odb::query<person> query;
- typedef odb::result<person> result;
+ std::string first_;
+ std::string last_;
+};
+
- unsigned short age;
- query q (query::first == "John" && query::age < query::_ref (age));
+ composite
+ example in the odb-examples
package.WHERE
predicate from the SQL SELECT
- statement. This language will be referred to as native query
- language. At this level ODB still takes care of converting
- query parameters from C++ to the database system format. Below
- is the re-implementation of the above example using SQL as
- the native query language:
+#pragma db value
+class basic_name
+{
+ ...
-
-
- query q ("first = 'John' AND age = " + query::_ref (age));
-
+ std::string first_;
+ std::string last_;
+};
-
- query q (query::first == 123 && query::agee < query::_ref (age));
-
+#pragma db value
+class name_extras
+{
+ ...
- query::first
to an
- integer and the second would pick the misspelling in
- query::agee
. On the other hand, if we wrote something
- like this:
- query q ("first = 123 AND agee = " + query::_ref (age));
-
+#pragma db value
+class name: public basic_name
+{
+ ...
-
- query q ("first = 'John'" + (query::age < query::_ref (age)));
-
+ #pragma db id auto
+ unsigned long id_;
- 4.1 ODB Query Language
+ name name_;
+};
+true
or false
. At
- the higher level, an expression consists of other expressions
- combined with logical operators such as &&
(AND),
- ||
(OR), and !
(NOT). For example:person
object presented above
+ contains the name
scope (derived from the name_
+ data member) which in turn contains the extras
member
+ (derived from the name::extras_
data member of the
+ composite value type). The process continues reqursively for nested
+ composite value types and, as a result, we can use the
+ query::name::extras::nickname
expression while querying
+ the database for the person
objects. For example:
- typedef odb::query<person> query;
-
- query q (query::first == "John" || query::age == 31);
-
+typedef odb::query<person> query;
+typedef odb::result<person> result;
- query::first
above. The names of members in the
- query
class are derived from the names of data members
- in the object class by removing the common member name decorations,
- such as leading and trailing underscores, the m_
prefix,
- etc.
-
+
-
+...
- Operator
- Description
- Example
-
-
+t.commit ();
+
-
- ==
equal
-
- query::age == 31
-
+
- !=
unequal
-
- query::age != 31
7.1 Composite Value Column and Table Names
-
-
+
- <
less than
-
- query::age < 31
db column
pragma (@@ ref). For composite value
+ types things are slightly more complicated since it is mapped to
+ multiple columns. Consider the following example:
-
+
- >
greater than
-
- query::age > 31
+#pragma db value
+class name
+{
+ ...
-
-
-
+ std::string first_;
+ std::string last_;
+};
-
- <=
less than or equal
-
- query::age <= 31
-
+#pragma db object
+class person
+{
+ ...
-
- >=
greater than or equal
-
- query::age >= 31
-
+ #pragma db id auto
+ unsigned long id_;
-
- in()
one of the values
-
- query::age.in (30, 32, 34)
-
+ name name_;
+};
+
- in_range()
one of the values in range
-
- query::age.in_range (begin, end)
-
+
- is_null()
value is NULL
-
- query::age.is_null ()
first_
and last_
+ members are constructed by using the sanitized name of the
+ person::name_
member as a prefix and the names of the
+ members in the value type (first_
and last_
)
+ as suffixes. As a result, the database schema for the above classes
+ will look like this:
-
-
- is_not_null()
value is not NULL
-
- query::age.is_not_null ()
+CREATE TABLE person (
+ id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+ name_first TEXT NOT NULL,
+ name_last TEXT NOT NULL);
+
- in()
function accepts a maximum of five arguments.
- Use the in_range()
function if you need to compare
- to more than five values. This function accepts a pair of
- standard C++ iterators and compares to all the values from
- the begin
position inclusive and until and
- excluding the end
position. The following
- code fragment shows how we can use these functions:db column
pragma as shown in the following
+ example:
- std::vector<string> names;
+#pragma db value
+class name
+{
+ ...
- names.push_back ("John");
- names.push_back ("Jack");
- names.push_back ("Jane");
+ #pragma db column("first_name")
+ std::string first_;
- query q1 (query::first.in ("John", "Jack", "Jane"));
- query q2 (query::first.in_range (names.begin (), names.end ()));
-
+ #pragma db column("last_name")
+ std::string last_;
+};
+#pragma db object
+class person
+{
+ ...
+ #pragma db column("person_")
+ name name_;
+};
+
-
- query q ((query::first == "John" || query::first == "Jane") &&
- query::age < 31);
+
+CREATE TABLE person (
+ id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+ person_first_name TEXT NOT NULL,
+ person_last_name TEXT NOT NULL);
+
+#pragma db object
+class person
+{
+ ...
-
- 4.2 Parameter Binding
+ #pragma db column("")
+ name name_;
+};
+ odb::query
class encapsulates two
- parts of information about the query: the query expression and
- the query parameters. Parameters can be bound to C++ variables
- either by value or by reference.
+CREATE TABLE person (
+ id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+ first_name TEXT NOT NULL,
+ last_name TEXT NOT NULL);
+
+
+ db column
either the db value_column
+ (@@ ref) or db key_column
(@@ ref) pragmas are used to
+ specify the column prefix.
- string name ("John");
+#pragma db value
+class name
+{
+ ...
- query q1 (query::first == query::_val (name));
- query q2 (query::first == query::_ref (name));
+ std::string first_;
+ std::string last_;
+ std::vector<std::string> nicknames_;
+};
- name = "Jane";
+#pragma db object
+class person
+{
+ ...
- db.query<person> (q1); // Find John.
- db.query<person> (q2); // Find Jane.
+ name name_;
+};
- odb::query
class provides two special functions,
- _val()
and _ref()
, that allow us to
- bind the parameter either by value or by reference, respectively.
- In the ODB query language, if the binding is not specified
- explicitly, the value semantic is used by default. In the
- native query language, binding must always be specified
- explicitly. For example:
- query q1 (query::age < age); // By value.
- query q2 (query::age < query::_val (age)); // By value.
- query q3 (query::age < query::_ref (age)); // By reference.
+
+CREATE TABLE `person_name_nicknames` (
+ `object_id` BIGINT UNSIGNED NOT NULL,
+ `index` BIGINT UNSIGNED NOT NULL,
+ `value` TEXT NOT NULL)
- query q4 ("age < " + age); // Error.
- query q5 ("age < " + query::_val (age)); // By value.
- query q6 ("age < " + query::_ref (age)); // By reference.
+CREATE TABLE person (
+ id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+ name_first TEXT NOT NULL,
+ name_last TEXT NOT NULL);
- db table
(@@ ref) pragma, for example:4.3 Executing a Query
+
+#pragma db value
+class name
+{
+ ...
-
- database::query()
function template. It has two
- overloaded versions:
- template <typename T>
- result<T>
- query (bool cache = true);
+#pragma db object
+class person
+{
+ ...
- template <typename T>
- result<T>
- query (const odb::query<T>&, bool cache = true);
+ #pragma db table("person_")
+ name name_;
+};
- query()
function is used to return all the
- persistent objects of a given type stored in the database.
- The second function uses the passed query instance to only return
- objects matching the query criteria. The cache
argument
- determines whether the objects' states should be cached in the
- application's memory or if they should be returned by the database
- system one by one as the iteration over the result progresses. The
- result caching is discussed in detail in the next section.query()
function, we have to
- explicitly specify the object type we are querying. For example:
+CREATE TABLE `person_nickname` (
+ `object_id` BIGINT UNSIGNED NOT NULL,
+ `index` BIGINT UNSIGNED NOT NULL,
+ `value` TEXT NOT NULL)
+
-
- typedef odb::query<person> query;
- typedef odb::result<person> result;
+
-
- query q (query::first == "John");
- result r1 (db.query<person> (q));
- result r1 (db.query<person> (query::first == "John"));
-
+ 8 Session
- odb::session
class and is automatically terminated
+ when this instance is destroyed. You will need to include the
+ <odb/session.hxx>
header file to make this class
+ available in your application. For example:
-result
-find_minors (database& db, const query& name_query)
-{
- return db.query<person> (name_query && query::age < 18);
-}
-
-result r (find_underage (db, query::first == "John"));
-
+#include <odb/database.hxx>
+#include <odb/session.hxx>
+#include <odb/transaction.hxx>
- 4.4 Query Result
+using namespace odb;
- odb::result
class template, for example:
- typedef odb::query<person> query;
- typedef odb::result<person> result;
+ // First transaction.
+ //
+ {
+ transaction t (db.begin ());
+ ...
+ t.commit ();
+ }
- result johns (db.query<person> (query::first == "John"));
-
+ // Second transaction.
+ //
+ {
+ transaction t (db.begin ());
+ ...
+ t.commit ();
+ }
- odb::result
- as a handle to a stream, such as a file stream. While we can
- make a copy of a result or assign one result to another, the
- two instances will refer to the same result stream. Advancing
- the current position in one instance will also advance it in
- another. The result instance is only usable within the transaction
- it was created in. Trying to manipulate the result after the
- transaction has terminated leads to undefined behavior.odb::result
class template conforms to the
- standard C++ sequence requirements and has the following
- interface:odb::session
class has the following interface:
namespace odb
{
- template <typename T>
- class result
+ class session
{
public:
- typedef odb::result_iterator<T> iterator;
+ session ();
+ ~session ();
+
+ // Copying or assignment of sessions is not supported.
+ //
+ private:
+ session (const session&);
+ session& operator= (const session&);
+ // Current session interface.
+ //
public:
- result ();
+ static session&
+ current ();
- result (const result&);
+ static bool
+ has_current ();
- result&
- operator= (const result&);
+ static void
+ current (session&);
- void
- swap (result&)
+ static void
+ reset_current ();
+ // Object cache interface.
+ //
public:
- iterator
- begin ();
-
- iterator
- end ();
+ typedef odb::database database_type;
- public:
+ template <typename T>
void
- cache ();
+ insert (database_type&,
+ const object_traits<T>::id_type&,
+ const object_traits<T>::pointer_type&);
- bool
- empty () const;
+ template <typename T>
+ object_traits<T>::pointer_type
+ find (database_type&, const object_traits<T>::id_type&) const;
- std::size_t
- size () const;
+ template <typename T>
+ void
+ erase (database_type&, const object_traits<T>::id_type&);
};
}
- cache()
function caches the returned objects'
- state in the application's memory. We have already mentioned
- result caching when we talked about query execution. As you
- may remember the database::query()
function
- caches the result unless instructed not to by the caller.
- The cache()
function allows us to
- cache the result at a later stage if it wasn't already
- cached during query execution.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.empty()
function returns true
if
- there are no objects in the result and false
otherwise.
- The size()
function can only be called for cached results.
- It returns the number of objects in the result. If we call this
- function on an uncached result, the odb::result_not_cached
- exception is thrown.odb::already_in_session
exception. The
+ destructor clears the current session for this thread if this
+ session is the current one.begin()
and end()
functions
- together with the odb::result<T>::iterator
- type, for example:current()
accessor returns the currently active
+ session for this thread. If there is no active session, this function
+ throws the odb::not_in_session
exception. We can check
+ whether there is a session in effect in this thread using the
+ has_current()
static function.
- result r (db.query<person> (query::first == "John"));
+
+ current()
modifier allows us to set the
+ current session for this thread. The reset_current
+ static function clears the current session. These two functions
+ allow for more advanced use cases, such as multiplexing
+ between two or more sessions in the same thread.8.1 Object Cache
- database::persist()
function (@@ ref), loaded
+ by calling the database::load()
or database::find()
+ function (@@ ref), or loaded by iterating over a query result (@@ ref),
+ the pointer to the persistent object, in the form of the canonical object
+ pouinter (@@ ref), is stored in the session. For as long as the
+ session is in effect, any subsequent calls to load the same object will
+ return the cached instance. When an object's state is deleted from the
+ database with the database::erase()
function (@@ ref), the
+ cached object pointer is removed from the session. For example:
-namespace odb
-{
- template <typename T>
- class result_iterator
- {
- public:
- T*
- operator-> () const;
+shared_ptr<person> p (new person ("John", "Doe"));
- T&
- operator* () const;
+session s;
+transaction t (db.begin ());
- typename object_traits<T>::pointer_type
- load ();
+unsigned long id (db.persist (p)); // p is cached in s.
+shared_ptr<person> p1 (db.load<person> (id)); // p1 same as p.
- void
- load (T& x);
- };
-}
+t.commit ();
- *
or ->
operator,
- the iterator will allocate a new instance of the object class
- in the dynamic memory, load its state from the database
- state, and return a reference or pointer to the new instance. The
- iterator maintains the ownership of the returned object and will
- return the same pointer for subsequent calls to either of these
- operators until it is advanced to the next object or we call
- the first load()
function (see below). For example:
- result r (db.query<person> (query::first == "John"));
- for (result::iterator i (r.begin ()); i != r.end ();)
- {
- cout << i->last () << endl; // Create an object.
- person& p (*i); // Reference to the same object.
- cout << p.age () << endl;
- ++i; // Free the object.
- }
-
+ std::auto_ptr
,
+ as an object pointer are never cached since it is not possible to have
+ two such pointers pointing to the same object. When an object is
+ persisted via a pointer or loaded as a dynamically allocated instance,
+ objects with both raw and shared pointers as object pointers are
+ cached. If an object is persisted as a reference or loaded into
+ a pre-allocated instance, the object is only cached if its object
+ pointer is a raw pointer.result_iterator::load()
functions are
- similar to database::load()
. The first function
- returns a dynamically allocated instance of the current
- object. As an optimization, if the iterator already owns an object
- as a result of an earlier
- call to the *
or ->
operator, then it
- relinquishes the ownership of this object and returns it instead.
- This allows us to write code like this without worrying about
- a double allocation:database::persist()
function (in contrast,
+ when loaded, objects are always created and cached as non-constant).
+ If we try to load an object as non-constant that was previously
+ persisted and cached as constant, the odb::const_object
+ exception is thrown. The following transaction illustartes the
+ situation where this would happen:
- result r (db.query<person> (query::first == "John"));
-
- for (result::iterator i (r.begin ()); i != r.end (); ++i)
- {
- if (i->last == "Doe")
- {
- auto_ptr p (i.load ());
- ...
- }
- }
-
-
- load()
call to the *
or ->
- operator results in the allocation of a new object.load()
function allows
- us to load the current object's state into an existing instance.
- For example:
- result r (db.query<person> (query::first == "John"));
+unsigned long id (db.persist (p));
+shared_ptr<const person> p1 (db.load<const person> (id)); // Ok.
+shared_ptr<person> p2 (db.load<person> (id)); // Exception.
- person p;
- for (result::iterator i (r.begin ()); i != r.end (); ++i)
- {
- i.load (p);
- cout << p.last () << endl;
- cout << i.age () << endl;
- }
+t.commit ();
- 5 ODB Pragma Language
+ 5 ODB Pragma Language
5.1 Object Type Pragmas
+ 9.1 Object Type Pragmas
object
qualifier declares a C++ class
as a persistent object type. The qualifier can be optionally followed,
@@ -5054,19 +5106,19 @@ private:
table
the table name for the persistent class
- 5.1.1
+ 9.1.1
pointer
the pointer type for the persistent class
- 5.1.2
+ 9.1.2
5.1.1
+ table
9.1.1
table
table
specifier specifies the table name that should
be used to store objects of the class in a relational database. For
@@ -5083,7 +5135,7 @@ class person
5.1.2
+ pointer
9.1.2
pointer
pointer
specifier specifies the object pointer type
for the persistent class. The object pointer type is used to return,
@@ -5133,7 +5185,7 @@ class person
5.2 Value Type Pragmas
+ 9.2 Value Type Pragmas
value
qualifier describes a value
type. It can be optionally followed, in any order, by one or more
@@ -5150,67 +5202,67 @@ class person
type
the database type for the value type
- 5.2.1
+ 9.2.1
not_null
object pointer cannot be
- NULL
5.2.2
+ 9.2.2
unordered
ordered container should be stored unordered
- 5.2.3
+ 9.2.3
index_type
the database type for the container's index type
- 5.2.4
+ 9.2.4
key_type
the database type for the container's key type
- 5.2.5
+ 9.2.5
value_type
the database type for the container's value type
- 5.2.6
+ 9.2.6
id_column
the column name for the container's table object id
- 5.2.7
+ 9.2.7
index_column
the column name for the container's table index
- 5.2.8
+ 9.2.8
key_column
the column name for the container's table key
- 5.2.9
+ 9.2.9
value_column
the column name for the container's table value
- 5.2.10
+ 9.2.10
5.2.1
+ type
9.2.1
type
type
specifier specifies the native database type
that should be used for data members of this type. For example:bool
, int
, and
std::string
and the database types for each supported
database system. For more information on the default mapping,
- refer to Chapter 6, "Database Systems".
+ refer to Chapter 10, "Database Systems".
bool
type which is now mapped to the INT
database type. In
@@ -5267,7 +5319,7 @@ private:
mapping
example in the odb-examples
package shows how to do this for all the supported database systems.5.2.2
+ not_null
9.2.2
not_null
not_null
specifier specifies that an object pointer
or a container of object pointers type cannot have or contain the
@@ -5295,7 +5347,7 @@ typedef std::vector<shared_ptr<account> > accounts;
#pragma db value(accounts) not_null
- 5.2.3
+ unordered
9.2.3
unordered
unordered
specifier specifies that the ordered
container should be stored in the database unordered. The database
@@ -5308,13 +5360,13 @@ typedef std::vector<std::string> names;
#pragma db value(names) unordered
- 5.2.4
+ index_type
9.2.4
index_type
index_type
specifier specifies the native
database type that should be used for the ordered container's
index column. The semantics of index_type
are similar to that of the type
specifier (see
- Section 5.2.1, "type
"). The native
+ Section 9.2.1, "type
"). The native
database type is expected to be an integer type. For example:
@@ -5322,13 +5374,13 @@ typedef std::vector<std::string> names;
#pragma db value(names) index_type("SMALLINT UNSIGNED NOT NULL")
- 5.2.5
+ key_type
9.2.5
key_type
key_type
specifier specifies the native
database type that should be used for the map container's
key column. The semantics of key_type
are similar to that of the type
specifier (see
- Section 5.2.1, "type
"). For
+ Section 9.2.1, "type
"). For
example:
@@ -5336,13 +5388,13 @@ typedef std::map<unsigned short, float> age_weight_map;
#pragma db value(age_weight_map) key_type("INT UNSIGNED NOT NULL")
- 5.2.6
+ value_type
9.2.6
value_type
value_type
specifier specifies the native
database type that should be used for the container's
value column. The semantics of value_type
are similar to that of the type
specifier (see
- Section 5.2.1, "type
"). For
+ Section 9.2.1, "type
"). For
example:
@@ -5350,7 +5402,7 @@ typedef std::vector<std::string> names;
#pragma db value(names) value_type("VARCHAR(255) NOT NULL")
- 5.2.7
+ id_column
9.2.7
id_column
id_column
specifier specifies the column
name that should be used to store the object id in the
@@ -5364,7 +5416,7 @@ typedef std::vector<std::string> names;
object_id
is used by default.5.2.8
+ index_column
9.2.8
index_column
index_column
specifier specifies the column
name that should be used to store the element index in the
@@ -5378,7 +5430,7 @@ typedef std::vector<std::string> names;
index
is used by default.5.2.9
+ key_column
9.2.9
key_column
key_column
specifier specifies the column
name that should be used to store the key in the map
@@ -5392,7 +5444,7 @@ typedef std::map<unsigned short, float> age_weight_map;
key
is used by default.5.2.10
+ value_column
9.2.10
value_column
value_column
specifier specifies the column
name that should be used to store the element value in the
@@ -5409,7 +5461,7 @@ typedef std::map<unsigned short, float> age_weight_map;
- 5.3 Data Member Pragmas
+ 9.3 Data Member Pragmas
member
qualifier or a positioned
pragma without a qualifier describes a data member. It can
@@ -5427,104 +5479,104 @@ typedef std::map<unsigned short, float> age_weight_map;
id
the member is an object id
- 5.3.1
+ 9.3.1
auto
id is assigned by the database
- 5.3.2
+ 9.3.2
type
the database type for the member
- 5.3.3
+ 9.3.3
column
the column name for the member
- 5.3.4
+ 9.3.4
transient
the member is not stored in the database
- 5.3.5
+ 9.3.5
not_null
object pointer cannot be NULL
- 5.3.6
+ 9.3.6
inverse
the member is an inverse side of a bidirectional relationship
- 5.3.7
+ 9.3.7
unordered
ordered container should be stored unordered
- 5.3.8
+ 9.3.8
table
the table name for the container
- 5.3.9
+ 9.3.9
index_type
the database type for the container's index type
- 5.3.10
+ 9.3.10
key_type
the database type for the container's key type
- 5.3.11
+ 9.3.11
value_type
the database type for the container's value type
- 5.3.12
+ 9.3.12
id_column
the column name for the container's table object id
- 5.3.13
+ 9.3.13
index_column
the column name for the container's table index
- 5.3.14
+ 9.3.14
key_column
the column name for the container's table key
- 5.3.15
+ 9.3.15
value_column
the column name for the container's table value
- 5.3.16
+ 9.3.16
5.3.1
+ id
9.3.1
id
id
specifier specifies that the data member contains
the object id. Every persistent class must have a member designated
@@ -5554,7 +5606,7 @@ private:
5.3.2
+ auto
9.3.2
auto
auto
specifier specifies that the object's identifier
is automatically assigned by the database. Only a member that was
@@ -5582,7 +5634,7 @@ private:
5.3.3
+ type
9.3.3
type
type
specifier specifies the native database type
that should be used for the data member. For example:5.3.4
+ column
9.3.4
column
column
specifier specifies the column name
that should be used to store the member in a relational database.
@@ -5618,14 +5670,14 @@ private:
column
specifier
- specifies the column name prefix. Refer to Section Z.1,
+ specifies the column name prefix. Refer to Section 7.1,
"Composite Value Column and Table Names" for details.m_
prefix, etc.5.3.5
+ transient
9.3.5
transient
transient
specifier instructs the ODB compiler
not to store the data member in the database. For example:
not_null
not_null
The not_null
specifier specifies that the member of
an object pointer or a container of object pointers type cannot
@@ -5677,7 +5729,7 @@ private:
};
-
inverse
inverse
The inverse
specifier specifies that the member of
an object pointer or a container of object pointers type is an
@@ -5717,9 +5769,9 @@ private:
information. Only ordered and set containers can be used for
inverse members. If an inverse member is of an ordered container
type, it is automatically marked as unordered (see
- Section 5.3.8, "unordered
").
unordered
").
- unordered
unordered
The unordered
specifier specifies that the member of
an ordered container type should be stored in the database unordered.
@@ -5739,7 +5791,7 @@ private:
};
-
table
table
The table
specifier specifies the table name that should
be used to store the contents of the container member. For example:
The table
specifier can also be used for members of
composite value types. In this case it specifies the table name
prefix for container members inside the composite value type. Refer
- to Section Z.1, "Composite Value Column and Table
+ to Section 7.1, "Composite Value Column and Table
Names" for details.
index_type
index_type
The index_type
specifier specifies the native
database type that should be used for the ordered container's
index column of the data member. The semantics of index_type
are similar to that of the type
specifier (see
- Section 5.3.3, "type
"). The native
+ Section 9.3.3, "type
"). The native
database type is expected to be an integer type. For example:
@@ -5791,13 +5843,13 @@ private: };-
key_type
key_type
The key_type
specifier specifies the native
database type that should be used for the map container's
key column of the data member. The semantics of key_type
are similar to that of the type
specifier (see
- Section 5.3.3, "type
"). For
+ Section 9.3.3, "type
"). For
example:
@@ -5812,13 +5864,13 @@ private: };-
value_type
value_type
The value_type
specifier specifies the native
database type that should be used for the container's
value column of the data member. The semantics of value_type
are similar to that of the type
specifier (see
- Section 5.3.3, "type
"). For
+ Section 9.3.3, "type
"). For
example:
@@ -5833,14 +5885,14 @@ private: };-
id_column
id_column
The id_column
specifier specifies the column
name that should be used to store the object id in the
container's table for the member. The semantics of
id_column
are similar to that of the
column
specifier (see
- Section 5.3.4, "column
").
+ Section 9.3.4, "column
").
For example:
@@ -5858,14 +5910,14 @@ private:-If the column name is not specified, then
-object_id
is used by default.5.3.14
+index_column
9.3.14
index_column
The
index_column
specifier specifies the column name that should be used to store the element index in the ordered container's table for the member. The semantics ofindex_column
are similar to that of thecolumn
specifier (see - Section 5.3.4, "column
"). + Section 9.3.4, "column
"). For example:@@ -5883,14 +5935,14 @@ private:-If the column name is not specified, then
-index
is used by default.5.3.15
+key_column
9.3.15
key_column
The
key_column
specifier specifies the column name that should be used to store the key in the map container's table for the member. The semantics ofkey_column
are similar to that of thecolumn
specifier (see - Section 5.3.4, "column
"). + Section 9.3.4, "column
"). For example:@@ -5908,14 +5960,14 @@ private:-If the column name is not specified, then
-key
is used by default.5.3.16
+value_column
9.3.16
value_column
The
value_column
specifier specifies the column name that should be used to store the element value in the container's table for the member. The semantics ofvalue_column
are similar to that of thecolumn
specifier (see - Section 5.3.4, "column
"). + Section 9.3.4, "column
"). For example:@@ -5933,7 +5985,7 @@ private:-If the column name is not specified, then
-value
is used by default.5.4 C++ Compiler Warnings
+9.4 C++ Compiler Warnings
When the C++ header file defining our persistent classes and containing ODB pragmas is processed by a C++ compiler, it @@ -5988,7 +6040,7 @@ private:
The disadvantage of this approach is that it can quickly become overly verbose when positioned pragmas are used.
-5.4.1 GNU C++
+9.4.1 GNU C++
GNU g++ does not issue warnings about unknown pragmas unless requested with the
-Wall
command line option. @@ -6000,7 +6052,7 @@ private: g++ -Wall -Wno-unknown-pragmas ...5.4.2 Visual C++
+9.4.2 Visual C++
Microsoft Visual C++ issues an unknown pragma warning (C4068) at warning level 1 or higher. This means that unless we have disabled @@ -6035,7 +6087,7 @@ private: #pragma warning (pop)
5.4.3 Sun C++
+9.4.3 Sun C++
The Sun C++ compiler does not issue warnings about unknown pragmas unless the
+w
or+w2
option is specified. @@ -6047,7 +6099,7 @@ private: CC +w -erroff=unknownpragma ...5.4.4 IBM XL C++
+9.4.4 IBM XL C++
IBM XL C++ issues an unknown pragma warning (1540-1401) by default. To disable this warning we can add the
-qsuppress=1540-1401
@@ -6057,7 +6109,7 @@ CC +w -erroff=unknownpragma ... xlC -qsuppress=1540-1401 ...
HP aC++ (aCC) issues an unknown pragma warning (2161) by default.
To disable this warning we can add the +W2161
@@ -6071,7 +6123,7 @@ aCC +W2161 ...
-
This chapter covers topics specific to the database system implementations and their support in ODB. In particular, it @@ -6080,7 +6132,7 @@ aCC +W2161 ... and native database types.
-To generate support code for the MySQL database you will need
to pass the "--database mysql
"
@@ -6089,12 +6141,12 @@ aCC +W2161 ...
library (libodb-mysql
). All MySQL-specific ODB
classes are defined in the odb::mysql
namespace.
The following table summarizes the default mapping between basic C++ value types and MySQL database types. This mapping can be customized on the per-type and per-member basis using the ODB - Pragmas Language (see Chapter 5, "ODB Pragma + Pragmas Language (see Chapter 9, "ODB Pragma Language").
@@ -6187,7 +6239,7 @@ aCC +W2161 ... toVARCHAR(255) NOT NULL
MySQL type. Otherwise,
it is mapped to TEXT NOT NULL
.
- The MySQL database
class has the following
interface:
This constructor throws the odb::mysql::cli_exception
exception if the MySQL option values are missing or invalid.
- See section Section 6.1.4, "MySQL Exceptions"
+ See section Section 10.1.4, "MySQL Exceptions"
for more information on this exception.
The static print_usage()
function prints the list of options
@@ -6350,7 +6402,7 @@ namespace odb
handle, refer to the MySQL ODB runtime source code for the interface
of the connection
class.
The connection_factory
abstract class has the
following interface:
The MySQL ODB runtime library defines the following MySQL-specific exceptions:
-- cgit v1.1