- 20.1 | Basic Types Library
+ | 21.1 | Basic Types Library
|
- 20.2 | Smart Pointers Library |
- 20.3 | Containers Library |
+ 21.2 | Smart Pointers Library |
+ 21.3 | Containers Library |
- 20.4 | Date Time Library
+ | 21.4 | Date Time Library
|
@@ -1529,7 +1545,7 @@ main (int argc, char* argv[])
database name, etc., from the command line. In your own applications
you may prefer to use other mysql::database
constructors which allow you to pass this information directly
- (Section 13.2, "MySQL Database Class").
+ (Section 14.2, "MySQL Database Class").
Next, we create three person
objects. Right now they are
transient objects, which means that if we terminate the application
@@ -2039,7 +2055,155 @@ max age: 33
}
-
+
+
+ Accessing multiple databases (that is, data stores) is simply a
+ matter of creating multiple odb::<db>::database
+ instances representing each database. For example:
+
+
+odb::mysql::database db1 ("john", "secret", "test_db1");
+odb::mysql::database db2 ("john", "secret", "test_db2");
+
+
+ A more interesting question is how we access multiple database
+ systems (that is, database implementations) from the same application.
+ For example, our application may need to store some objects in a
+ remote MySQL database and others in a local SQLite file. Or, our
+ application may need to be able to store its objects in a database
+ system that is selected by the user at runtime.
+
+ ODB provides comprehensive multi-database support that ranges from
+ tight integration with specific database systems to being able to
+ write database-agnostic code and loading individual database systems
+ support dynamically. While all these aspects are covered in detail
+ in Chapter 13, "Multi-Database Support", in this
+ section we will get a taste of this functionality by extending our
+ "Hello World" example to be able to store its data either in MySQL
+ or PostgreSQL (other database systems supported by ODB can be added
+ in a similar manner).
+
+ The first step in adding multi-database support is to re-compile
+ our person.hxx
header to generate database support
+ code for additional database systems:
+
+
+odb --multi-database dynamic -d common -d mysql -d pgsql \
+--generate-query --generate-schema person.hxx
+
+
+ The --multi-database
ODB compiler option turns on
+ multi-database support. For now it is not important what the
+ dynamic
value that we passed to this option means,
+ but if you are curious, see Chapter 13. The result of this
+ command are three sets of generated files: person-odb.?xx
+ (common interface; corresponds to the common
database),
+ person-odb-mysql.?xx
(MySQL support code), and
+ person-odb-pgsql.?xx
(PostgreSQL support code). There
+ are also two schema files: person-mysql.sql
and
+ person-pgsql.sql
.
+
+ The only part that we need to change in driver.cxx
+ is how we create the database instance. Specifically, this line:
+
+
+auto_ptr<database> db (new odb::mysql::database (argc, argv));
+
+
+ Now our example is capable of storing its data either in MySQL or
+ PostgreSQL so we need to somehow allow the caller to specify which
+ database we must use. To keep things simple, we will make the first
+ command line argument specify the database system we must use while
+ the rest will contain the database-specific options which we will
+ pass to the odb::<db>::database
constructor as
+ before. Let's put all this logic into a separate function which we
+ will call create_database()
. Here is what the beginning
+ of our modified driver.cxx
will look like (the remainder
+ is unchanged):
+
+
+// driver.cxx
+//
+
+#include <string>
+#include <memory> // std::auto_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <odb/mysql/database.hxx>
+#include <odb/pgsql/database.hxx>
+
+#include "person.hxx"
+#include "person-odb.hxx"
+
+using namespace std;
+using namespace odb::core;
+
+auto_ptr<database>
+create_database (int argc, char* argv[])
+{
+ auto_ptr<database> r;
+
+ if (argc < 2)
+ {
+ cerr << "error: database system name expected" << endl;
+ return r;
+ }
+
+ string db (argv[1]);
+
+ if (db == "mysql")
+ r.reset (new odb::mysql::database (argc, argv));
+ else if (db == "pgsql")
+ r.reset (new odb::pgsql::database (argc, argv));
+ else
+ cerr << "error: unknown database system " << db << endl;
+
+ return r;
+}
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ auto_ptr<database> db (create_database (argc, argv));
+
+ if (db.get () == 0)
+ return 1; // Diagnostics has already been issued.
+
+ ...
+
+
+ And that's it. The only thing left is to build and run our
+ example:
+
+
+c++ -c driver.cxx
+c++ -c person-odb.cxx
+c++ -c person-odb-mysql.cxx
+c++ -c person-odb-pgsql.cxx
+c++ -o driver driver.o person-odb.o person-odb-mysql.o \
+person-odb-pgsql.o -lodb-mysql -lodb-pgsql -lodb
+
+
+ Here is how we can access a MySQL database:
+
+
+mysql --user=odb_test --database=odb_test < person-mysql.sql
+./driver mysql --user odb_test --database odb_test
+
+
+ Or a PostgreSQL database:
+
+
+psql --user=odb_test --dbname=odb_test -f person-pgsql.sql
+./driver pgsql --user odb_test --database odb_test
+
+
+
This chapter presented a very simple application which, nevertheless,
exercised all of the core database functions: persist()
,
@@ -4698,7 +4862,7 @@ namespace odb
the prepared query name. This name is used as a key for prepared
query caching (discussed later) and must be unique. For some databases,
notably PostgreSQL, it is also used as a name of the underlying prepared
- statement. The name "object_query"
(e.g.,
+ statement. The name "object_query"
(for example,
"person_query"
) is reserved for the once-off queries
executed by the database::query()
function. Note that
the prepare_query()
function makes only a shallow copy
@@ -7223,7 +7387,7 @@ namespace odb
consider using a more efficient implementation of the
optional value concept such as the
optional
class template from Boost
- (Section 19.4, "Optional Library").
+ (Section 20.4, "Optional Library").
Another common C++ representation of a value that can be
NULL
is a pointer. ODB will automatically
@@ -13718,18 +13882,20 @@ class person
DATABASE SYSTEMS
Part II covers topics specific to the database system
- implementations and their support in ODB. In particular, it
- describes the system-specific database
classes
- as well as the default mapping between basic C++ value types
- and native database types. Part II consists of the following
- chapters.
+ implementations and their support in ODB. The first chapter in
+ Part II discusses how to use multiple database systems in the
+ same application. The subsequent chapters describe the system-specific
+ database
classes as well as the default mapping
+ between basic C++ value types and native database types. Part
+ II consists of the following chapters.
@@ -13737,7 +13903,719 @@ class person
-
+
+
+ Some applications may need to access multiple database systems, either
+ simultaneously or one at a time. For example, an application may
+ utilize an embedded database such as SQLite as a local cache and use
+ a client-server database such as PostgreSQL for more permanent
+ but slower to access remote storage. Or an application may need
+ to be able to store its data in any database selected at runtime
+ by the user. Yet another scenario is the data migration from one
+ database system to another. In this case, multi-database support
+ is only required for a short period. It is also plausible that an
+ application implements all three of these scenarios, that is, it
+ uses SQLite as a local cache, allows the user to select the remote
+ database system, and supports data migration from one remote database
+ system to another.
+
+ ODB provides two types of multi-database support: static
+ and dynamic. With static support we use the
+ database system-specific interfaces to perform database
+ operations. That is, instead of using odb::database
,
+ odb::transaction
, or odb::query
, we
+ would use, for example, odb::sqlite::database
,
+ odb::sqlite::transaction
, or
+ odb::sqlite::query
to access an SQLite database.
+
+ In contrast, with dynamic multi-database support we can
+ use the common interface to access any database without having to
+ know which one it is. At runtime, ODB will automatically dispatch
+ a call on the common interface to the specific database implementation
+ based on the actual database
instance being
+ used. In fact, this mechanism is very similar to C++ virtual
+ functions.
+
+ Both static and dynamic multi-database support have a different set
+ of advantages and disadvantages which makes them more or less suitable
+ for different use cases. Static support has zero overhead compared
+ to single-database support and allows us to use database
+ system-specific features, extensions, etc. At the same time, the
+ code that we write will be tied to the specific database system.
+ As a result, this type of multi-database support is more
+ suitable for situations where different parts of an application
+ access different but specific database systems. For example,
+ using SQLite as a local cache most likely falls into this
+ category since we are using a specific database system (SQLite)
+ and the code that will check the cache will most likely (but
+ not necessarily) be separate from the code that interact with
+ the remote database. Another example where static multi-database
+ support might be more suitable is a once-off data migration from
+ one database system to another. In this case both the source and
+ target are specific database systems. In contrast, if data migration
+ from one database system to another is a general feature in an
+ application, then dynamic multi-database support might be more
+ suitable.
+
+ The main advantage of dynamic multi-database support is the
+ database system-independence of the code that we write. The same
+ application code will work with any database system supported by
+ ODB and the generated database support code can be packaged into
+ separate libraries and loaded dynamically by the application. The
+ disadvantages of dynamic support are slight overhead and certain
+ limitations in functionality compared to static support (see
+ Section 13.2, "Dynamic Multi-Database Support"
+ for details). As a result, dynamic multi-database support is most
+ suitable to situations where we need the same code to
+ work with a range of database systems. For example, if your
+ application must be able to store its data in any database
+ selected by the user, then dynamic support is probably the
+ best option.
+
+ Note also that it is possible to mix and match static and dynamic
+ support in the same application. In fact, dynamic support is built
+ on top of static support so it is possible to use the same database
+ system both "statically" and "dynamically". In particular, the ability
+ to "drop down" from dynamic to static support can be used to overcome
+ the functionality limitations mentioned above. Finally,
+ single-database support is just a special case of static
+ multi-database support with a single database system.
+
+ By default ODB assumes single-database support. To enable
+ multi-database support we use the --multi-database
+ (or -m
) ODB compiler option. This option is also used to
+ specify the support type: static
or dynamic
.
+ For example:
+
+
+odb -m static ... person.hxx
+
+
+ With multi-database support enabled, we can now generate the database
+ support code for several database systems. This can be accomplished
+ either with a single ODB compiler invocation by specifying multiple
+ --database
(or -d
) options or with multiple
+ ODB compiler invocations. Both approaches produce the same result,
+ for example:
+
+
+odb -m static -d common -d sqlite -d pgsql person.hxx
+
+
+ Is equivalent to:
+
+
+odb -m static -d common person.hxx
+odb -m static -d sqlite person.hxx
+odb -m static -d pgsql person.hxx
+
+
+ Notice that the first -d
option has common
+ as its value. This is not a real database system. Rather, it instructs
+ the ODB compiler to generate code that is common to all the database
+ systems and, in case of dynamic support, is also the common
+ interfaces.
+
+ If you look at the result of the above commands, you will also notice
+ changes in the output file names. In the single-database mode the ODB
+ compiler produces a single set of the person-odb.?xx
files
+ which contain both the common as well as the database specific
+ generated code (since there is only one database system in use,
+ there is no reason to split the two). In contrast, in the
+ multi-database mode, the person-odb.?xx
set of files
+ contains the common code while the database system-specific code is
+ written to files in the form person-odb-<db>.?xx
.
+ That is, person-odb-sqlite.?xx
for SQLite,
+ person-odb-pgsql.?xx
for PostgreSQL, etc.
+
+ If we need dynamic support for some databases and static for
+ others, then the common
code must be generated
+ in the dynamic mode. For example, if we need static support
+ for SQLite and dynamic support for PostgreSQL and Oracle, then
+ the ODB compiler invocations could look like this:
+
+
+odb -m dynamic -d common person.hxx
+odb -m static -d sqlite person.hxx
+odb -m dynamic -d pgsql person.hxx
+odb -m dynamic -d oracle person.hxx
+
+
+ With multi-database support enabled, it is possible to restrict ODB
+ pragmas to apply only to a specific database system (unrestricted
+ pragmas apply to all the databases). For example:
+
+
+#pragma db object
+class person
+{
+ ...
+
+ #pragma db pgsql:type("VARCHAR(128)") sqlite:type("TEXT")
+ std::string name_;
+
+ unsigned short age_;
+
+ #pragma db pgsql index member(age_)
+};
+
+
+ Above, the pragma for the name_
data member shows the
+ use of a database prefix (for example, pgsql:
) that
+ only applies to the specifier that follows. The pragma that defines
+ an index on the age_
data member shows the use of a
+ database prefix that applies to the whole pragma. In this case the
+ database name must immediately follow the db
keyword.
+
+
+ Similar to pragmas, ODB compiler options that determine the kind
+ (for example, --schema-format
), names (for example,
+ --odb-file-suffix
), or content (for example, prologue
+ and epilogue options) of the output files can be prefixed with the
+ database name. For example:
+
+
+odb --odb-file-suffix common:-odb-common ...
+
+
+ Dynamic multi-database support requires consistent mapping across
+ all the databases. That is, the same classes and data members
+ should be mapped to objects, simple/composite values, etc., for
+ all the databases. In contrast, static multi-database support
+ does not have this restriction. Specifically, with static support,
+ some data members can be transient for some database systems.
+ Similarly, the same class (for example, point
) can
+ be mapped to a simple value in one database (for example, to the
+ POINT
PostgreSQL type) and to a composite value
+ in another (for example, in SQLite, which does not have a
+ built-in point type).
+
+ The following sections discuss static and dynamic multi-database
+ support in more detail.
+
+
+
+
+ With static multi-database support, instead of including
+ person-odb.hxx
, application source code has
+ to include person-odb-<db>.hxx
header files
+ corresponding to the database systems that will be used.
+
+ The application code has to also use database system-specific
+ interfaces when performing database operations. As an example,
+ consider the following transaction in a single-database
+ application. It uses the common interfaces, that is, classes
+ from the odb
namespace.
+
+
+#include "person-odb.hxx"
+
+odb::database& db = ...
+
+typedef odb::query<person> query;
+typedef odb::result<person> result;
+
+odb::transaction t (db.begin ());
+result r (db.query<person> (query::age < 30));
+...
+t.commit ();
+
+
+ In an application that employs static multi-database support
+ the same transaction for SQLite would be rewritten like this:
+
+
+#include "person-odb-sqlite.hxx"
+
+odb::sqlite::database& db = ...
+
+typedef odb::sqlite::query<person> query;
+typedef odb::result<person> result; // odb:: not odb::sqlite::
+
+odb::sqlite::transaction t (db.begin ());
+result r (db.query<person> (query::age < 30));
+...
+t.commit ();
+
+
+ That is, the database
, transaction
, and
+ query
classes now come from the odb::sqlite
+ namespace instead of odb
. Other classes that have
+ database system-specific interfaces are connection
,
+ statement
, and tracer
. Note that
+ all of them derive from the corresponding common versions. It
+ is also possible to use common transaction
,
+ connection
, and statement
classes
+ with static support, if desired.
+
+ Notice that we didn't use the odb::sqlite
namespace
+ for the result
class template. This is because
+ result
is database system-independent. All other
+ classes defined in namespace odb
, except those
+ specifically mentioned above, are database system-independent.
+ In particular, result
, prepared_query
,
+ session
, schema_catalog
, and all the
+ exceptions are database system-independent.
+
+ Writing odb::sqlite::
before every name can quickly
+ become burdensome. As we have seen before, in single-database
+ applications that use the common interface we can add the
+ using namespace
directive to avoid qualifying
+ each name. For example:
+
+
+#include "person-odb.hxx"
+
+odb::database& db = ...
+
+{
+ using namespace odb::core;
+
+ typedef query<person> person_query;
+ typedef result<person> person_result;
+
+ transaction t (db.begin ());
+ person_result r (db.query<person> (person_query::age < 30));
+ ...
+ t.commit ();
+}
+
+
+ A similar mechanism is available in multi-database support. Each
+ database runtime defines the odb::<db>::core
+ namespace that contains all the database system-independent
+ names as well as the database system-specific ones for this
+ database. Here is how we can rewire the above transaction
+ using this approach:
+
+
+#include "person-odb-sqlite.hxx"
+
+odb::sqlite::database& db = ...
+
+{
+ using namespace odb::sqlite::core;
+
+ typedef query<person> person_query;
+ typedef result<person> person_result;
+
+ transaction t (db.begin ());
+ person_result r (db.query<person> (person_query::age < 30));
+ ...
+ t.commit ();
+}
+
+
+ If the using namespace
directive cannot be used, for
+ example, because the same code fragment accesses several databases,
+ then we can still make the namespace qualifications more concise
+ by assigning shorter aliases to database namespaces. For example:
+
+
+#include "person-odb-pgsql.hxx"
+#include "person-odb-sqlite.hxx"
+
+namespace pg = odb::pgsql;
+namespace sl = odb::sqlite;
+
+pg::database& pg_db = ...
+sl::database& sl_db = ...
+
+typedef pg::query<person> pg_query;
+typedef sl::query<person> sl_query;
+typedef odb::result<person> result;
+
+// First check the local cache.
+//
+odb::transaction t (sl_db.begin ()); // Note: using common transaction.
+result r (sl_db.query<person> (sl_query::age < 30));
+
+// If no hits, try the remote database.
+//
+if (r.empty ())
+{
+ t.commit (); // End the SQLite transaction.
+ t.reset (pg_db.begin ()); // Start the PostgreSQL transaction.
+
+ r = pg_db.query<person> (pg_query::age < 30);
+}
+
+// Handle the result.
+//
+...
+
+t.commit ();
+
+
+ With static multi-database support we can make one of the databases
+ the default database with the --default-database
option.
+ The default database can be accessed via the common interface, just
+ like with single-database support. For example:
+
+
+odb -m static -d common -d pgsql -d sqlite --default-database pgsql ...
+
+
+ The default database mechanism can be useful when one of the
+ databases is primary or when retrofitting multi-database support
+ into an existing single-database application. For example, if
+ we are adding SQLite as a local cache into an existing
+ application that uses PostgreSQL as its only database, then
+ by making PostgreSQL the default database we avoid having to
+ change all the existing code. Note that if dynamic multi-database
+ support is enabled, then the common (dynamic) interface is always
+ made the default database.
+
+
+
+ With dynamic multi-database support, application source code only
+ needs to include the person-odb.hxx
header file, just
+ like with single-database support. In particular, we don't need
+ to include any of the person-odb-<db>.hxx
files
+ unless we would also like to use certain database systems in the
+ static multi-database mode.
+
+ When performing database operations, the application code
+ uses the common interfaces from the odb
namespace,
+ just like with single-database support. As an example, consider
+ a function that can be used to load an object either from a local
+ SQLite cache or a remote PostgreSQL database (in reality, this
+ function can be used with any database system support by ODB
+ provided we generated the database support code for this database
+ and linked it into our application):
+
+
+#include "person-odb.hxx"
+
+std::unique_ptr<person>
+load (odb::database& db, const std::string& name)
+{
+ odb::transaction t (db.begin ());
+ std::unique_ptr<person> p (db.find (name));
+ t.commit ();
+ return p;
+}
+
+odb::pgsql::database& pg_db = ...
+odb::sqlite::database& sl_db = ...
+
+// First try the local cache.
+//
+std::unique_ptr<person> p (load (sl_db, "John Doe"));
+
+// If not found, try the remote database.
+//
+if (p == 0)
+ p = load (pg_db, "John Doe");
+
+...
+
+
+ As you can see, we can use dynamic multi-database support just like
+ single-database support except that now our code can work with
+ different database systems. Note, however, one difference: with
+ single-database support we could perform database operations using
+ either the common odb::database
or a database system-specific
+ (for example, odb::sqlite::database
) interface
+ with the same effect. In contrast, with dynamic multi-database support,
+ the use of the database system-specific interface results in the
+ switch to the static mode (for which, as was mentioned earlier, we would
+ need to include the corresponding person-odb-<db>.hxx
+ header file). As we will discuss shortly, switching from dynamic to
+ static mode can be used to overcome limitations imposed by dynamic
+ multi-database support.
+
+ Dynamic multi-database support has certain overheads and limitations
+ compared to static support. For database operations, the generated code
+ maintains function tables that are used to dispatch calls to the database
+ system-specific implementations. In single-database and static
+ multi-database support, the query
type implements a thin
+ wrapper around the underlying database system's SELECT
+ statement. With dynamic multi-database support, because the
+ underlying database system is only known at query execution
+ (or preparation) time, the query
type stores a
+ database system-independent representation of the query that
+ is then translated to the database system-specific form. Because
+ of this database system-independent representation, dynamic
+ support queries have a number of limitations. Specifically, dynamic
+ queries do not support parameter binding in native query fragments.
+ They also make copies of by-value parameterd (by-reference parameters
+ can be used to remove this overhead). Finally, parameters of array
+ types (for example, char[256]
) can only be bound
+ by-reference.
+
+ As we mentioned earlier, switching from dynamic to static mode
+ can be an effective way to overcome these limitations. As an
+ example, consider a function that prints the list of people of
+ a certain age. The caller also specified the limit on the number
+ of entries to print. Some database systems, for example, PostgreSQL,
+ allow us to propagate this limit to the database server with the
+ LIMIT
clause. To add this clause we would need to
+ construct a native query fragment and, as we discussed above, we
+ won't be able to bind a parameter (the limit) while in the dynamic
+ mode. The following implementation shows how we can overcome this
+ by switching to the static mode and using the PostgreSQL-specific
+ interface:
+
+
+#include "person-odb.hxx"
+#include "person-odb-pgsql.hxx" // Needed for static mode.
+
+void
+print (odb::database& db, unsigned short age, unsigned long limit)
+{
+ typedef odb::query<person> query;
+ typedef odb::result<person> result;
+
+ odb::transaction t (db.begin ());
+
+ query q (query::age == age);
+ result r;
+
+ if (db.id () == odb::id_pgsql)
+ {
+ // We are using PostgreSQL. Drop down to the static mode and
+ // add the LIMIT clause to the query.
+ //
+ namespace pg = odb::pgsql;
+ typedef pg::query<person> pg_query;
+
+ pg::database& pg_db (static_cast<pg::database&> (db));
+ pg_query pg_q (pg_query (q) + "LIMIT" + pg_query::_val (limit));
+ r = pg_db.query<person> (pg_q);
+ }
+ else
+ r = db.query<person> (q);
+
+ // Handle the result up to the limit elements.
+ //
+ ...
+
+ t.commit ();
+}
+
+odb::pgsql::database& pg_db = ...
+odb::sqlite::database& sl_db = ...
+
+print (sl_db, 30, 100);
+print (sl_db, 30, 100);
+
+
+ A few things to note about this example. First, we use the
+ database::id()
function to determine the actual database
+ system we use. This function has the following signature:
+
+
+namespace odb
+{
+ enum database_id
+ {
+ id_mysql,
+ id_sqlite,
+ id_pgsql,
+ id_oracle,
+ id_mssql,
+ id_common
+ };
+
+ class database
+ {
+ public:
+ ...
+
+ database_id
+ id () const;
+ }
+}
+
+
+ Note that database::id()
can never return the
+ id_common
value.
+
+ The other thing to note is how we translate the dynamic query
+ to the database system-specific one (the pg_query (q)
+ expression). Every odb::<db>::query
class provides
+ such a translation constructor.
+
+
+
+ With dynamic multi-database support, the generated database support
+ code automatically registers itself with the function tables that
+ we mentioned earlier. This makes it possible to package the generated
+ code for each database into a separate dynamic-link library (Windows
+ DLL) or dynamic shared object (Unix DSO; collectively referred to as
+ DLLs from now on) and load/unload them from the application
+ dynamically using APIs such as Win32 LoadLibrary()
or
+ POSIX dlopen()
. This allows the application address
+ space to contain code only for database systems that are actually
+ needed in any particular moment. Another advantage of this approach
+ is the ability to distribute individual database system support
+ separately.
+
+ This section provides an overview of how to package the generated
+ database support code into DLLs for both Windows and Unix using
+ GNU/Linux as an example. Note also that if static multi-database
+ support is used for a particular database system, then the dynamic
+ loading cannot be used for this database. It is, however, still
+ possible to package the generated code into a DLL but this DLL
+ will have to be linked to the executable at link-time rather
+ than at runtime. If dynamic loading is desirable in this situation,
+ then another alternative would be to package the functionality
+ that requires static support together with the database support
+ code into the DLL and import this functionality dynamically
+ using the GetProcAddress()
(Win32) or dlsym()
+ (Unix) function.
+
+ The first step in packaging the generated code into DLLs is to
+ set up the symbol exporting. This step is required for
+ Windows DLLs but is optional for Unix DSOs. Most modern Unix
+ systems (such as GNU/Linux) provide control over symbol
+ visibility, which is a mechanism similar to Windows symbol
+ exporting. Notable advantages of using this mechanism to
+ explicitly specify which symbols are visible include
+ smaller Unix DSOs and faster load times. If, however, you are
+ not planning to control symbol visibility on Unix, then you can
+ skip directly to the second step below.
+
+ An important point to understand is that we only need to export
+ the common interface, that is, the classes defined in the
+ person-odb.hxx
header. In particular, we don't need
+ to export the database system-specific classes defined in
+ the person-odb-<db>.hxx
, unless we are also using
+ this database in the static mode (in which case, the procedure
+ described below will need to be repeated for that database as
+ well).
+
+ The ODB compiler provides two command line options,
+ --export-symbol
and --extern-symbol
,
+ which can be used to insert the export and extern
+ macros in all the necessary places in the generated header file.
+ You are probably familiar with the concept of export macro which
+ expands to an export directive if we are building the DLL and to
+ an import directive if we are building client code. The
+ extern macro is a supplementary mechanism which is necessary to
+ export explicit template instantiations used by the generated
+ code when query support is enabled. As we will see shortly, the
+ extern macro must expand into the extern
C++ keyword
+ in certain situations and must be left undefined in others. To
+ manage all these macro definitions, it is customary to create the
+ so called export header. Based on a single macro that is normally
+ defined in the project file or on the command line and which
+ indicates whether we are building the DLL or client code, the
+ export header file sets the export and extern macros to their
+ appropriate values. Continuing with our person example, on Windows
+ the export header, which we will call person-export.hxx
,
+ could look like this:
+
+
+// person-export.hxx
+//
+// Define PERSON_BUILD_DLL if we are building the DLL. Leave it
+// undefined in client code.
+//
+#ifndef PERSON_EXPORT_HXX
+#define PERSON_EXPORT_HXX
+
+#ifdef PERSON_BUILD_DLL
+# define PERSON_EXPORT __declspec(dllexport)
+#else
+# define PERSON_EXPORT __declspec(dllimport)
+# define PERSON_EXTERN extern
+#endif
+
+#endif // PERSON_EXPORT_HXX
+
+
+ The equivalent export header for GCC on GNU/Linux is shown below.
+ Note also that on GNU/Linux, by default, all symbols are visible
+ and we need to add the GCC -fvisibility=hidden
option to
+ make them hidden by default.
+
+
+// person-export.hxx
+//
+#ifndef PERSON_EXPORT_HXX
+#define PERSON_EXPORT_HXX
+
+#define PERSON_EXPORT __attribute__ ((visibility ("default")))
+#define PERSON_EXTERN extern
+
+#endif // PERSON_EXPORT_HXX
+
+
+ Next we need to export the person
persistent class
+ using the export macro and re-compile our person.hxx
file
+ with the --export-symbol
and --extern-symbol
+ options. We will also need to include person-export.hxx
+ into the generated person-odb.hxx
file. For that we use
+ the --hxx-prologue
option. Here is how we can do
+ this with multiple invocations of the ODB compiler:
+
+
+odb -m dynamic -d common --hxx-prologue "#include \"person-export.hxx\"" \
+--export-symbol PERSON_EXPORT --extern-symbol PERSON_EXTERN person.hxx
+
+odb -m dynamic -d sqlite person.hxx
+odb -m dynamic -d pgsql person.hxx
+
+
+ It is also possible to achieve the same with a single invocation.
+ Here we need to restrict some option values to apply only to the
+ common
database:
+
+
+odb -m dynamic -d common -d sqlite -d pgsql \
+--hxx-prologue "common:#include \"person-export.hxx\"" \
+--export-symbol common:PERSON_EXPORT --extern-symbol common:PERSON_EXTERN \
+person.hxx
+
+
+ The second step in packaging the generated code into DLLs is to
+ decide where to place the generated common interface code. One
+ option is to place it into a DLL of its own so that we will end
+ up with (replace *.dll
with lib*.so
for
+ Unix): person.dll
plus person-sqlite.dll
and
+ person-pgsql.dll
, which both link to person.dll
,
+ as well as person.exe
, which links to person.dll
+ and dynamically loads person-sqlite.dll
+ and/or person-pgsql.dll
. If this is the organization
+ that you prefer, then the next step is to build all the DLLs as you
+ normally would any other DLL, placing person-odb.cxx
+ and person.cxx
into person.dll
,
+ person-odb-sqlite.cxx
into person-sqlite.dll
,
+ etc. Note that in the pure dynamic multi-database support,
+ person-sqlite.dll
and person-pgsql.dll
+ do not export any symbols.
+
+ We can improve on the above organization by getting rid of
+ person.dll
, which is not really necessary unless
+ we have multiple executables sharing the same database support.
+ To achieve this, we will place person-odb.cxx
into
+ person.exe
and export its symbols from the executable
+ instead of a DLL. Exporting symbols from an executable is a seldom
+ used functionality, especially on Windows, however, it is well
+ supported on both Windows and most Unix platforms. Note also that
+ this approach won't work if we also use one of the databases in the
+ static mode.
+
+ On Windows all we have to do is place person-odb.cxx
+ into the executable and compile it as we would in a DLL (that is,
+ with the PERSON_BUILD_DLL
macro defined). If Windows
+ linker detects that an executable exports any symbols, then it
+ will automatically create the corresponding import library
+ (person.lib
in our case). We then use this import
+ library to build person-sqlite.dll
and
+ person-pgsql.dll
as before.
+
+ To export symbols from an executable on GNU/Linux all we need to
+ do is add the -rdynamic
option when linking our
+ executable.
+
+
+
+
+
+
To generate support code for the MySQL database you will need
to pass the "--database mysql
"
@@ -13746,7 +14624,7 @@ class person
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
@@ -13939,7 +14817,7 @@ class object
Section 12.7, "Database Type Mapping
Pragmas".
-
+
The MySQL database
class has the following
interface:
@@ -14099,7 +14977,7 @@ namespace odb
This constructor throws the odb::mysql::cli_exception
exception if the MySQL option values are missing or invalid.
- See section Section 13.4, "MySQL Exceptions"
+ See section Section 14.4, "MySQL Exceptions"
for more information on this exception.
The static print_usage()
function prints the list of options
@@ -14118,10 +14996,10 @@ namespace odb
The connection()
function returns a pointer to the
MySQL database connection encapsulated by the
odb::mysql::connection
class. For more information
- on mysql::connection
, refer to Section
- 13.3, "MySQL Connection and Connection Factory".
+ on mysql::connection
, refer to Section
+ 14.3, "MySQL Connection and Connection Factory".
-
+
The mysql::connection
class has the following interface:
@@ -14313,7 +15191,7 @@ main (int argc, char* argv[])
}
-
+
The MySQL ODB runtime library defines the following MySQL-specific
exceptions:
@@ -14365,12 +15243,12 @@ namespace odb
what()
function returns a human-readable description
of an error.
-
+
The following sections describe MySQL-specific limitations imposed
by the current MySQL and ODB runtime versions.
-
+
ODB relies on standard SQL behavior which requires that foreign
key constraints checking is deferred until the transaction is
@@ -14381,7 +15259,7 @@ namespace odb
foreign key definitions commented out. They are retained only
for documentation.
-
+
When the index
pragma (Section 12.6,
"Index Definition Pragmas") is used to define a MySQL index,
@@ -14410,7 +15288,7 @@ class object
-
+
To generate support code for the SQLite database you will need
to pass the "--database sqlite
"
@@ -14419,7 +15297,7 @@ class object
library (libodb-sqlite
). All SQLite-specific ODB
classes are defined in the odb::sqlite
namespace.
-
+
The following table summarizes the default mapping between basic
C++ value types and SQLite database types. This mapping can be
@@ -14597,7 +15475,7 @@ class object
Section 12.7, "Database Type Mapping
Pragmas".
-
+
The SQLite database
class has the following
interface:
@@ -14670,7 +15548,7 @@ namespace odb
values, refer to the sqlite3_open_v2()
function description
in the SQLite C API documentation. The foreign_keys
argument specifies whether foreign key constraints checking
- should be enabled. See Section 14.5.3,
+ should be enabled. See Section 15.5.3,
"Foreign Key Constraints" for more information on foreign
keys. The vfs
argument specifies the SQLite
virtual file system module that should be used to access the
@@ -14726,7 +15604,7 @@ auto_ptr<odb::database> db (
The third constructor throws the odb::sqlite::cli_exception
exception if the SQLite option values are missing or invalid.
- See Section 14.4, "SQLite Exceptions"
+ See Section 15.4, "SQLite Exceptions"
for more information on this exception.
The static print_usage()
function prints the list of options
@@ -14755,10 +15633,10 @@ auto_ptr<odb::database> db (
The connection()
function returns a pointer to the
SQLite database connection encapsulated by the
odb::sqlite::connection
class. For more information
- on sqlite::connection
, refer to Section
- 14.3, "SQLite Connection and Connection Factory".
+ on sqlite::connection
, refer to Section
+ 15.3, "SQLite Connection and Connection Factory".
-
+
The sqlite::connection
class has the following interface:
@@ -14803,7 +15681,7 @@ namespace odb
functions allow us to start an immediate and an exclusive SQLite
transaction on the connection, respectively. Their semantics are
equivalent to the corresponding functions defined in the
- sqlite::database
class (Section 14.2,
+ sqlite::database
class (Section 15.2,
"SQLite Database Class"). The handle()
accessor
returns the SQLite handle corresponding to the connection.
@@ -15003,7 +15881,7 @@ main (int argc, char* argv[])
}
-
+
The SQLite ODB runtime library defines the following SQLite-specific
exceptions:
@@ -15056,12 +15934,12 @@ namespace odb
of an error.
-
+
The following sections describe SQLite-specific limitations imposed by
the current SQLite and ODB runtime versions.
-
+
SQLite ODB runtime implementation does not perform query result caching
(Section 4.4, "Query Result") even when explicitly
@@ -15076,7 +15954,7 @@ namespace odb
thrown. Future versions of the SQLite ODB runtime library may add support
for result caching.
-
+
Due to SQLite API limitations, every automatically assigned object id
(Section 12.4.2, "auto
") should have
@@ -15102,7 +15980,7 @@ class person
};
-
+
By default the SQLite ODB runtime enables foreign key constraints
checking (PRAGMA foreign_keys=ON
). You can disable foreign
@@ -15166,7 +16044,7 @@ CREATE TABLE Employee (
-
+
Due to the granularity of the SQLite error codes, it is impossible
to distinguish between the duplicate primary key and other constraint
@@ -15175,7 +16053,7 @@ CREATE TABLE Employee (
object_already_persistent
exception (Section
3.14, "ODB Exceptions").
-
+
As discussed in Section 4.3, "Executing a Query", a
query instance that does not have any by-reference parameters is
@@ -15184,7 +16062,7 @@ CREATE TABLE Employee (
functionality. Future versions of the library will remove this
limitation.
-
+
When the index
pragma (Section 12.6,
"Index Definition Pragmas") is used to define an SQLite index,
@@ -15213,7 +16091,7 @@ class object
-
+
To generate support code for the PostgreSQL database you will need
to pass the "--database pgsql
"
@@ -15227,7 +16105,7 @@ class object
of the messaging protocol version 3.0. For this reason, ODB supports
only PostgreSQL version 7.4 and later.
-
+
The following table summarizes the default mapping between basic
C++ value types and PostgreSQL database types. This mapping can be
@@ -15405,7 +16283,7 @@ class object
For more information, refer to Section 12.7,
"Database Type Mapping Pragmas".
-
+
The PostgreSQL database
class has the following
interface:
@@ -15525,7 +16403,7 @@ namespace odb
This constructor throws the odb::pgsql::cli_exception
exception if the PostgreSQL option values are missing or invalid.
- See section Section 15.4, "PostgreSQL Exceptions"
+ See section Section 16.4, "PostgreSQL Exceptions"
for more information on this exception.
The static print_usage()
function prints the list of options
@@ -15551,10 +16429,10 @@ namespace odb
The connection()
function returns a pointer to the
PostgreSQL database connection encapsulated by the
odb::pgsql::connection
class. For more information
- on pgsql::connection
, refer to Section
- 15.3, "PostgreSQL Connection and Connection Factory".
+ on pgsql::connection
, refer to Section
+ 16.3, "PostgreSQL Connection and Connection Factory".
-
+
The pgsql::connection
class has the following interface:
@@ -15735,7 +16613,7 @@ main (int argc, char* argv[])
}
-
+
The PostgreSQL ODB runtime library defines the following
PostgreSQL-specific exceptions:
@@ -15784,12 +16662,12 @@ namespace odb
what()
function returns a human-readable description
of an error.
-
+
The following sections describe PostgreSQL-specific limitations imposed
by the current PostgreSQL and ODB runtime versions.
-
+
The PostgreSQL ODB runtime implementation will always return a
cached query result (Section 4.4, "Query Result")
@@ -15797,7 +16675,7 @@ namespace odb
PostgreSQL client library (libpq
) which does not
support uncached (streaming) query results.
-
+
ODB relies on standard SQL behavior which requires that
foreign key constraints checking is deferred until the
@@ -15815,7 +16693,7 @@ CREATE TABLE Employee (
employer BIGINT REFERENCES Employer(id) INITIALLY DEFERRED);
-
+
Due to the granularity of the PostgreSQL error codes, it is impossible
to distinguish between the duplicate primary key and other unique
@@ -15824,7 +16702,7 @@ CREATE TABLE Employee (
errors to the object_already_persistent
exception
(Section 3.14, "ODB Exceptions").
-
+
ODB expects the PostgreSQL server to use integers as a binary
format for the date-time types, which is the default for most
@@ -15839,14 +16717,14 @@ CREATE TABLE Employee (
SHOW integer_datetimes
-
+
ODB does not currently natively support the PostgreSQL date-time types
with timezone information. However, these types can be accessed by
mapping them to one of the natively supported types, as discussed
in Section 12.7, "Database Type Mapping Pragmas".
-
+
Support for the PostgreSQL NUMERIC
type is limited
to providing a binary buffer containing the binary representation
@@ -15858,7 +16736,7 @@ SHOW integer_datetimes
Type Mapping Pragmas".
-
+
When the index
pragma (Section 12.6,
"Index Definition Pragmas") is used to define a PostgreSQL index,
@@ -15897,7 +16775,7 @@ class object
-
+
To generate support code for the Oracle database you will need
to pass the "--database oracle
"
@@ -15906,7 +16784,7 @@ class object
library (libodb-oracle
). All Oracle-specific ODB
classes are defined in the odb::oracle
namespace.
-
+
The following table summarizes the default mapping between basic
C++ value types and Oracle database types. This mapping can be
@@ -16081,7 +16959,7 @@ class object
Section 12.7, "Database Type Mapping
Pragmas".
-
+
The Oracle database
class encapsulates the OCI environment
handle as well as the database connection string and user credentials
@@ -16206,7 +17084,7 @@ namespace odb
This constructor throws the odb::oracle::cli_exception
exception if the Oracle option values are missing or invalid. See section
- Section 16.4, "Oracle Exceptions" for more
+ Section 17.4, "Oracle Exceptions" for more
information on this exception.
The static print_usage()
function prints the list of options
@@ -16256,10 +17134,10 @@ namespace odb
The connection()
function returns a pointer to the
Oracle database connection encapsulated by the
odb::oracle::connection
class. For more information
- on oracle::connection
, refer to Section
- 16.3, "Oracle Connection and Connection Factory".
+ on oracle::connection
, refer to Section
+ 17.3, "Oracle Connection and Connection Factory".
-
+
The oracle::connection
class has the following interface:
@@ -16461,7 +17339,7 @@ main (int argc, char* argv[])
}
-
+
The Oracle ODB runtime library defines the following
Oracle-specific exceptions:
@@ -16542,12 +17420,12 @@ namespace odb
condition. The what()
function returns a human-readable
description of an error.
-
+
The following sections describe Oracle-specific limitations imposed
by the current Oracle and ODB runtime versions.
-
+
Oracle limits the length of database identifiers (table, column, etc.,
names) to 30 characters. The ODB compiler automatically truncates
@@ -16592,7 +17470,7 @@ class long_class_name
};
-
+
Oracle ODB runtime implementation does not perform query result caching
(Section 4.4, "Query Result") even when explicitly
@@ -16607,7 +17485,7 @@ class long_class_name
always thrown. Future versions of the Oracle ODB runtime library
may add support for result caching.
-
+
ODB relies on standard SQL behavior which requires that
foreign key constraints checking is deferred until the
@@ -16626,7 +17504,7 @@ CREATE TABLE Employee (
DEFERRABLE INITIALLY DEFERRED);
-
+
Due to the granularity of the Oracle error codes, it is impossible
to distinguish between the duplicate primary key and other unique
@@ -16635,7 +17513,7 @@ CREATE TABLE Employee (
errors to the object_already_persistent
exception
(Section 3.14, "ODB Exceptions").
- The following sections describe SQL Server-specific limitations imposed
by the current SQL Server and ODB runtime versions.
- 17.5.1 Query Result Caching
+ 18.5.1 Query Result Caching
SQL Server ODB runtime implementation does not perform query result
caching (Section 4.4, "Query Result") even when
@@ -17602,7 +18480,7 @@ namespace odb
3.14, "ODB Exceptions") is always thrown. Future versions of the
SQL Server ODB runtime library may add support for result caching.
- 17.5.2 Foreign Key Constraints
+ 18.5.2 Foreign Key Constraints
ODB relies on standard SQL behavior which requires that foreign
key constraints checking is deferred until the transaction is
@@ -17611,7 +18489,7 @@ namespace odb
the ODB compiler for SQL Server have foreign key definitions
commented out. They are retained only for documentation.
- 17.5.3 Unique Constraint Violations
+ 18.5.3 Unique Constraint Violations
Due to the granularity of the ODBC error codes, it is impossible
to distinguish between the duplicate primary key and other unique
@@ -17620,7 +18498,7 @@ namespace odb
errors to the object_already_persistent
exception
(Section 3.14, "ODB Exceptions").
- 17.5.4 Multi-threaded Windows Applications
+ 18.5.4 Multi-threaded Windows Applications
Multi-threaded Windows applications must use the
_beginthread()
/_beginthreadex()
and
@@ -17629,7 +18507,7 @@ namespace odb
Win32 functions to start and terminate threads. This is a limitation of
the ODBC implementation on Windows.
- 17.5.5 Affected Row Count and DDL Statements
+ 18.5.5 Affected Row Count and DDL Statements
SQL Server always returns zero as the number of affected rows
for DDL statements. In particular, this means that the
@@ -17637,7 +18515,7 @@ namespace odb
"Executing Native SQL Statements") function will always
return zero for such statements.
- 17.5.6 Long Data and Automatically Assigned Object Ids
+ 18.5.6 Long Data and Automatically Assigned Object Ids
SQL Server 2005 has a bug that causes it to fail on an INSERT
statement with the OUTPUT
clause (used to return
@@ -17655,7 +18533,7 @@ namespace odb
by passing the --mssql-server-version 9.0
ODB
compiler option.
- 17.5.7 Long Data and By-Value Accessors/Modifiers
+ 18.5.7 Long Data and By-Value Accessors/Modifiers
As discussed in Section 12.4.5,
"get
/set
/access
", by-value
@@ -17665,7 +18543,7 @@ namespace odb
by-reference accessors and modifiers should be used for these data
types.
- 17.6 SQL Server Index Definitions
+ 18.6 SQL Server Index Definitions
When the index
pragma (Section 12.6,
"Index Definition Pragmas") is used to define an SQL Server index,
@@ -17702,9 +18580,9 @@ class object
and libraries. It consists of the following chapters.
@@ -17712,7 +18590,7 @@ class object
- 18 Profiles Introduction
+ 19 Profiles Introduction
ODB profiles are a generic mechanism for integrating ODB with
widely-used C++ frameworks and libraries. A profile provides glue
@@ -17766,7 +18644,7 @@ odb --profile boost/date-time ...
- 19 Boost Profile
+ 20 Boost Profile
The ODB profile implementation for Boost is provided by the
libodb-boost
library and consists of multiple sub-profiles
@@ -17791,7 +18669,7 @@ odb --profile boost/date-time ...
that can be thrown by the Boost sub-profiles are described in the
following sections.
- 19.1 Smart Pointers Library
+ 20.1 Smart Pointers Library
The smart-ptr
sub-profile provides persistence
support for a subset of smart pointers from the Boost
@@ -17861,7 +18739,7 @@ class employee
this behavior, add the --default-pointer
option specifying
the alternative pointer type after the --profile
option.
- 19.2 Unordered Containers Library
+ 20.2 Unordered Containers Library
The unordered
sub-profile provides persistence support for
the containers from the Boost unordered
library. To enable
@@ -17887,7 +18765,7 @@ class person
};
-
19.3 Multi-Index Container Library
+ 20.3 Multi-Index Container Library
The multi-index
sub-profile provides persistence support for
boost::multi_index_container
from the Boost Multi-Index
@@ -17972,7 +18850,7 @@ class person
};
-
19.4 Optional Library
+ 20.4 Optional Library
The optional
sub-profile provides persistence support for
the boost::optional
container from the Boost
@@ -18004,7 +18882,7 @@ class person
this profile is used, the NULL
values are automatically
enabled for data members of the boost::optional
type.
- 19.5 Date Time Library
+ 20.5 Date Time Library
The date-time
sub-profile provides persistence support for a
subset of types from the Boost date_time
library. It is
@@ -18077,7 +18955,7 @@ namespace odb
exceptions are thrown are database system dependent and are discussed in
more detail in the following sub-sections.
- 19.5.1 MySQL Database Type Mapping
+ 20.5.1 MySQL Database Type Mapping
The following table summarizes the default mapping between the currently
supported Boost date_time
types and the MySQL database
@@ -18138,7 +19016,7 @@ class person
the out_of_range
exception. Refer to the MySQL
documentation for more information on the MySQL data type ranges.
- 19.5.2 SQLite Database Type Mapping
+ 20.5.2 SQLite Database Type Mapping
The following table summarizes the default mapping between the currently
supported Boost date_time
types and the SQLite database
@@ -18216,7 +19094,7 @@ class person
will result in the out_of_range
exception.
- 19.5.3 PostgreSQL Database Type Mapping
+ 20.5.3 PostgreSQL Database Type Mapping
The following table summarizes the default mapping between the currently
supported Boost date_time
types and the PostgreSQL database
@@ -18267,7 +19145,7 @@ class person
result in the special_value
exception.
- 19.5.4 Oracle Database Type Mapping
+ 20.5.4 Oracle Database Type Mapping
The following table summarizes the default mapping between the currently
supported Boost date_time
types and the Oracle database
@@ -18329,7 +19207,7 @@ class person
the special_value
exception.
- 19.5.5 SQL Server Database Type Mapping
+ 20.5.5 SQL Server Database Type Mapping
The following table summarizes the default mapping between the currently
supported Boost date_time
types and the SQL Server database
@@ -18400,7 +19278,7 @@ class person
posix_time::time_duration
value out of this range will
result in the value_out_of_range
exception.
- 19.6 Uuid Library
+ 20.6 Uuid Library
The uuid
sub-profile provides persistence support for the
uuid
type from the Boost uuid
library. To
@@ -18432,7 +19310,7 @@ class object
};
-
19.6.1 MySQL Database Type Mapping
+ 20.6.1 MySQL Database Type Mapping
The following table summarizes the default mapping between the Boost
uuid
type and the MySQL database type.
@@ -18452,7 +19330,7 @@ class object
-