aboutsummaryrefslogtreecommitdiff
path: root/view
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2011-09-27 11:35:29 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2011-09-27 11:35:29 +0200
commit60a14a9eada33984f8f259e082b5e621270ac8d1 (patch)
tree0e9bf1a33ca9529af4a28c4ca33e9e512ae3f908 /view
parent2f262977ed70a5fbb6dac139f84df1b2e5cd24f9 (diff)
Add view example
Diffstat (limited to 'view')
-rw-r--r--view/README68
-rw-r--r--view/database.hxx81
-rw-r--r--view/driver.cxx308
-rw-r--r--view/employee.hxx287
-rw-r--r--view/makefile115
5 files changed, 859 insertions, 0 deletions
diff --git a/view/README b/view/README
new file mode 100644
index 0000000..108d234
--- /dev/null
+++ b/view/README
@@ -0,0 +1,68 @@
+This example shows how to declare and use views in ODB. It includes views
+that show how to load a subset of data member from objects and tables,
+perform aggregate queries, and join multiple objects and tables using
+object relationships and custom join conditions.
+
+The example uses the shared_ptr smart pointer from TR1 and requires a C++
+compiler with TR1 support or an external TR1 implementation, such as the
+one provided by Boost.
+
+The example consists of the following files:
+
+employee.hxx
+ Header file defining the 'country', 'employer', and 'employee' persistent
+ classes. The example also uses a "legacy" 'employee_extra' table that is
+ not mapped to a persistent class.
+
+ After the persistent classes, this header defines a number of views that
+ show how to obtain various information from the above object model.
+
+employee-odb.hxx
+employee-odb.ixx
+employee-odb.cxx
+employee.sql
+ The first three files contain the database support code and the last file
+ contains the database schema for the employee.hxx header.
+
+ These files are generated by the ODB compiler from employee.hxx using the
+ following command line:
+
+ odb -d <database> --generate-schema --generate-query \
+ --default-pointer std::tr1::shared_ptr employee.hxx
+
+ Where <database> stands for the database system we are using, for example,
+ 'mysql'.
+
+ The --default-pointer option is used to make TR1 shared_ptr the default
+ object pointer.
+
+database.hxx
+ Contains the create_database() function which instantiates the concrete
+ database class corresponding to the database system we are using.
+
+driver.cxx
+ Driver for the example. It includes the employee.hxx and employee-odb.hxx
+ headers to gain access to the persistent classes and their database support
+ code. It also includes database.hxx for the create_database() function
+ declaration.
+
+ In main() the driver first calls create_database() to obtain the database
+ instance. It then creates the legacy 'employee_extra' table and proceeds
+ to populate the database with a number of 'employee', 'employer', and
+ 'project' objects. Once this is done, the driver uses views defined in
+ employee.hxx to load and print various information about the object model.
+
+To run the example we may first need to create the database schema (for some
+database systems, such as SQLite, the schema is embedded into the generated
+code which makes this step unnecessary). Using MySQL as an example, this
+can be achieved with the following command:
+
+mysql --user=odb_test --database=odb_test < employee.sql
+
+Here we use 'odb_test' as the database login and also 'odb_test' as the
+database name.
+
+Once the database schema is ready, we can run the example (using MySQL as
+the database):
+
+./driver --user odb_test --database odb_test
diff --git a/view/database.hxx b/view/database.hxx
new file mode 100644
index 0000000..c68e3e1
--- /dev/null
+++ b/view/database.hxx
@@ -0,0 +1,81 @@
+// file : view/database.hxx
+// author : Boris Kolpackov <boris@codesynthesis.com>
+// copyright : not copyrighted - public domain
+
+//
+// Create concrete database instance based on the DATABASE_* macros.
+//
+
+#ifndef DATABASE_HXX
+#define DATABASE_HXX
+
+#include <string>
+#include <memory> // std::auto_ptr
+#include <cstdlib> // std::exit
+#include <iostream>
+
+#include <odb/database.hxx>
+
+#if defined(DATABASE_MYSQL)
+# include <odb/mysql/database.hxx>
+#elif defined(DATABASE_SQLITE)
+# include <odb/connection.hxx>
+# include <odb/transaction.hxx>
+# include <odb/schema-catalog.hxx>
+# include <odb/sqlite/database.hxx>
+#elif defined(DATABASE_PGSQL)
+# include <odb/pgsql/database.hxx>
+#endif
+
+inline std::auto_ptr<odb::database>
+create_database (int& argc, char* argv[])
+{
+ using namespace std;
+ using namespace odb::core;
+
+ if (argc > 1 && argv[1] == string ("--help"))
+ {
+ cerr << "Usage: " << argv[0] << " [options]" << endl
+ << "Options:" << endl;
+
+#if defined(DATABASE_MYSQL)
+ odb::mysql::database::print_usage (cerr);
+#elif defined(DATABASE_SQLITE)
+ odb::sqlite::database::print_usage (cerr);
+#elif defined(DATABASE_PGSQL)
+ odb::pgsql::database::print_usage (cerr);
+#endif
+
+ exit (0);
+ }
+
+#if defined(DATABASE_MYSQL)
+ auto_ptr<database> db (new odb::mysql::database (argc, argv));
+#elif defined(DATABASE_SQLITE)
+ auto_ptr<database> db (
+ new odb::sqlite::database (
+ argc, argv, false, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE));
+
+ // Create the database schema. Due to bugs in SQLite foreign key
+ // support for DDL statements, we need to temporarily disable
+ // foreign keys.
+ //
+ {
+ connection_ptr c (db->connection ());
+
+ c->execute ("PRAGMA foreign_keys=OFF");
+
+ transaction t (c->begin ());
+ schema_catalog::create_schema (*db);
+ t.commit ();
+
+ c->execute ("PRAGMA foreign_keys=ON");
+ }
+#elif defined(DATABASE_PGSQL)
+ auto_ptr<database> db (new odb::pgsql::database (argc, argv));
+#endif
+
+ return db;
+}
+
+#endif // DATABASE_HXX
diff --git a/view/driver.cxx b/view/driver.cxx
new file mode 100644
index 0000000..ccd8d2d
--- /dev/null
+++ b/view/driver.cxx
@@ -0,0 +1,308 @@
+// file : view/driver.cxx
+// author : Boris Kolpackov <boris@codesynthesis.com>
+// copyright : not copyrighted - public domain
+
+#include <memory> // std::auto_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/session.hxx>
+#include <odb/transaction.hxx>
+
+#include "database.hxx" // create_database
+
+#include "employee.hxx"
+#include "employee-odb.hxx"
+
+using namespace std;
+using namespace odb::core;
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ auto_ptr<database> db (create_database (argc, argv));
+
+ // Create the legacy employee_extra table.
+ //
+ {
+ // First try to drop the table if it exists.
+ //
+ {
+ transaction t (db->begin ());
+ try
+ {
+ db->execute ("DROP TABLE view_employee_extra");
+ t.commit ();
+ }
+ catch (const odb::exception&)
+ {
+ }
+ }
+
+ {
+ transaction t (db->begin ());
+
+ db->execute (
+ "CREATE TABLE view_employee_extra ("
+ "employee_id INTEGER NOT NULL,"
+ "vacation_days INTEGER NOT NULL,"
+ "previous_employer_id INTEGER)");
+
+ t.commit ();
+ }
+ }
+
+ // Create a few persistent objects.
+ //
+ {
+ shared_ptr<country> ca (new country ("CA", "Canada"));
+ shared_ptr<country> za (new country ("ZA", "South Africa"));
+ shared_ptr<country> us (new country ("US", "United States"));
+ shared_ptr<country> se (new country ("SE", "Sweden"));
+
+ shared_ptr<employer> st (new employer (1, "Simple Tech Ltd"));
+ shared_ptr<employer> cs (new employer (2, "Complex Systems Inc"));
+
+ shared_ptr<employee> e1 (
+ new employee (1, "John", "Doe", 29, ca, ca, st));
+
+ shared_ptr<employee> e2 (
+ new employee (2, "Jane", "Doe", 30, za, us, cs));
+
+ shared_ptr<employee> e3 (
+ new employee (3, "Joe", "Dirt", 31, us, za, st));
+
+ shared_ptr<employee> e4 (
+ new employee (4, "Johan", "Johansen", 32, se, se, cs));
+
+ transaction t (db->begin ());
+
+ db->persist (ca);
+ db->persist (za);
+ db->persist (us);
+ db->persist (se);
+
+ db->persist (st);
+ db->persist (cs);
+
+ db->persist (e1);
+ db->persist (e2);
+ db->persist (e3);
+ db->persist (e4);
+
+ // Populate the legacy table.
+ //
+ db->execute ("INSERT INTO view_employee_extra ("
+ "employee_id, vacation_days, previous_employer_id)"
+ "VALUES (1, 5, 2)");
+
+ db->execute ("INSERT INTO view_employee_extra ("
+ "employee_id, vacation_days, previous_employer_id)"
+ "VALUES (2, 10, NULL)");
+
+ db->execute ("INSERT INTO view_employee_extra ("
+ "employee_id, vacation_days, previous_employer_id)"
+ "VALUES (3, 0, NULL)");
+
+ db->execute ("INSERT INTO view_employee_extra ("
+ "employee_id, vacation_days, previous_employer_id)"
+ "VALUES (4, 15, 1)");
+
+ t.commit ();
+ }
+
+ // Load names of the employees that are under 31 using the employee_name
+ // view.
+ //
+ {
+ typedef odb::query<employee_name> query;
+ typedef odb::result<employee_name> result;
+
+ transaction t (db->begin ());
+
+ result r (db->query<employee_name> (query::age < 31));
+
+ cout << "Employees under 31" << endl;
+
+ for (result::iterator i (r.begin ()); i != r.end (); ++i)
+ cout << " " << i->first << " " << i->last << endl;
+
+ cout << endl;
+
+ t.commit ();
+ }
+
+ // Count the number of employees which has the Doe last name using the
+ // employee_count view.
+ //
+ {
+ transaction t (db->begin ());
+
+ result<employee_count> r (
+ db->query<employee_count> (query<employee_count>::last == "Doe"));
+
+ // Results of this aggregate query contains only one element.
+ //
+ cout << r.begin ()->count << " employees with the Doe last name" << endl
+ << endl;
+
+ t.commit ();
+ }
+
+ // Load the employee-employer information for all the employess with the
+ // Doe last name using the employee_employer view.
+ //
+ {
+ typedef odb::query<employee_employer> query;
+ typedef odb::result<employee_employer> result;
+
+ transaction t (db->begin ());
+
+ // Note that we need to add the object name after query::.
+ //
+ result r (db->query<employee_employer> (query::employee::last == "Doe"));
+
+ cout << "Employees with the Doe last name" << endl;
+
+ for (result::iterator i (r.begin ()); i != r.end (); ++i)
+ cout << " " << i->first << " " << i->last << " "
+ << i->employer_name << endl;
+
+ cout << endl;
+
+ t.commit ();
+ }
+
+ // Calculate average ages of all employees for each employer.
+ //
+ {
+ typedef odb::query<employer_age> query;
+ typedef odb::result<employer_age> result;
+
+ transaction t (db->begin ());
+
+ result r (db->query<employer_age> ());
+
+ // Some other interesting queries to try:
+ //
+ // This one restricts the calculation to a specific employer:
+ //
+ // result r (db->query<employer_age> (
+ // query::employer::name == "Simple Tech Ltd"));
+ //
+ // And this one filters the employees based on certain criteria.
+ //
+ // result r (db->query<employer_age> (
+ // query::employee::last == "Doe"));
+ //
+
+ cout << "Man/max employee ages" << endl;
+
+ for (result::iterator i (r.begin ()); i != r.end (); ++i)
+ cout << " " << i->employer_name << " "
+ << i->min_age << '/' << i->max_age << endl;
+
+ cout << endl;
+
+ t.commit ();
+ }
+
+ // Load the country information employees different residence and
+ // nationality.
+ //
+ {
+ typedef odb::query<employee_country> query;
+ typedef odb::result<employee_country> result;
+
+ transaction t (db->begin ());
+
+ // Note that we use the alias given in the object pragma after query::.
+ //
+ result r (db->query<employee_country> (
+ query::res_country::name != query::nat_country::name));
+
+ cout << "Employees residing outside of country of nationality" << endl;
+
+ for (result::iterator i (r.begin ()); i != r.end (); ++i)
+ cout << " " << i->first << " " << i->last << " "
+ << i->res_country_name << " " << i->nat_country_name << endl;
+
+ cout << endl;
+
+ t.commit ();
+ }
+
+ // Get the list of employees that have accumulated vacation days.
+ //
+ {
+ typedef odb::result<employee_vacation> result;
+
+ transaction t (db->begin ());
+
+ // With native views we have to use the native query syntax.
+ //
+ result r (db->query<employee_vacation> ("vacation_days != 0"));
+
+ cout << "Employees with accumulated vacation days" << endl;
+
+ for (result::iterator i (r.begin ()); i != r.end (); ++i)
+ cout << " " << i->id << " " << i->days << endl;
+
+ cout << endl;
+
+ t.commit ();
+ }
+
+ // Get the list of employees that have accumulated vacation days,
+ // this time using the improved employee_vacation2 view.
+ //
+ {
+ typedef odb::result<employee_vacation2> result;
+
+ transaction t (db->begin ());
+
+ result r (db->query<employee_vacation2> ("vacation_days != 0"));
+
+ cout << "Employees with accumulated vacation days (take 2)" << endl;
+
+ for (result::iterator i (r.begin ()); i != r.end (); ++i)
+ cout << " " << i->first << " " << i->last << " " << i->days << endl;
+
+ cout << endl;
+
+ t.commit ();
+ }
+
+ // Show the previous employers using the employee_prev_employer view.
+ //
+ {
+ typedef odb::query<employee_prev_employer> query;
+ typedef odb::result<employee_prev_employer> result;
+
+ transaction t (db->begin ());
+
+ result r (db->query<employee_prev_employer> ());
+
+ cout << "Previous employees" << endl;
+
+ for (result::iterator i (r.begin ()); i != r.end (); ++i)
+ {
+ const nullable<string>& pe (i->prev_employer_name);
+
+ cout << " " << i->first << " " << i->last << " "
+ << (pe.null () ? string ("N/A") : *pe) << endl;
+ }
+
+ cout << endl;
+
+ t.commit ();
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/view/employee.hxx b/view/employee.hxx
new file mode 100644
index 0000000..4ff4f1b
--- /dev/null
+++ b/view/employee.hxx
@@ -0,0 +1,287 @@
+// file : view/employee.hxx
+// author : Boris Kolpackov <boris@codesynthesis.com>
+// copyright : not copyrighted - public domain
+
+#ifndef EMPLOYEE_HXX
+#define EMPLOYEE_HXX
+
+#include <string>
+#include <cstddef> // std::size_t
+
+#include <odb/core.hxx>
+#include <odb/nullable.hxx>
+
+// Include TR1 <memory> header in a compiler-specific fashion. Fall back
+// on the Boost implementation if the compiler does not support TR1.
+//
+#include <odb/tr1/memory.hxx>
+
+using std::tr1::shared_ptr;
+
+#pragma db object
+class country
+{
+public:
+ country (const std::string& code, std::string const& name)
+ : code_ (code), name_ (name)
+ {
+ }
+
+ const std::string&
+ name () const
+ {
+ return name_;
+ }
+
+private:
+ friend class odb::access;
+
+ country () {}
+
+ #pragma db id
+ std::string code_; // ISO 2-letter country code.
+
+ std::string name_;
+};
+
+#pragma db object
+class employer
+{
+public:
+ employer (unsigned long id, const std::string& name)
+ : id_ (id), name_ (name)
+ {
+ }
+
+ const std::string&
+ name () const
+ {
+ return name_;
+ }
+
+private:
+ friend class odb::access;
+
+ employer () {}
+
+ #pragma db id
+ unsigned long id_;
+
+ std::string name_;
+};
+
+#pragma db object
+class employee
+{
+public:
+ employee (unsigned long id,
+ const std::string& first,
+ const std::string& last,
+ unsigned short age,
+ shared_ptr<country> res,
+ shared_ptr<country> nat,
+ shared_ptr<employer> e)
+ : id_ (id), first_ (first), last_ (last), age_ (age),
+ residence_ (res),
+ nationality_ (nat),
+ employed_by_ (e)
+ {
+ }
+
+ // Name.
+ //
+ const std::string&
+ first () const
+ {
+ return first_;
+ }
+
+ const std::string&
+ last () const
+ {
+ return last_;
+ }
+
+ // Employer.
+ //
+ shared_ptr<employer>
+ employed_by () const
+ {
+ return employed_by_;
+ }
+
+ // Residence and nationality.
+ //
+ shared_ptr<country>
+ residence () const
+ {
+ return residence_;
+ }
+
+ shared_ptr<country>
+ nationality () const
+ {
+ return nationality_;
+ }
+
+private:
+ friend class odb::access;
+
+ employee () {}
+
+ #pragma db id
+ unsigned long id_;
+
+ std::string first_;
+ std::string last_;
+
+ unsigned short age_;
+
+ shared_ptr<country> residence_;
+ shared_ptr<country> nationality_;
+
+ shared_ptr<employer> employed_by_;
+};
+
+// We also have a "legacy" employee_extra table that is not mapped to any
+// object. It has the following columns:
+//
+// CREATE TABLE employee_extra(
+// employee_id INTEGER NOT NULL,
+// vacation_days INTEGER NOT NULL,
+// previous_employer_id INTEGER)
+//
+
+// A simple view with a single associated object. It allows us to get
+// the name of an employee without loading any of the other data, such
+// as the country and employer objects. The first and last data members
+// in the view are automatically assumed to correspond to the first_ and
+// last_ members in the employee object.
+//
+#pragma db view object(employee)
+struct employee_name
+{
+ std::string first;
+ std::string last;
+};
+
+// A simple aggregate view. It allows us to count the number of employees
+// matching certain criteria. Here we use a column expression with a
+// reference to the id_ data member in the employee object.
+//
+#pragma db view object(employee)
+struct employee_count
+{
+ #pragma db column("count(" + employee::id_ + ")")
+ std::size_t count;
+};
+
+// A simple view with two associated object. It allows us to get the
+// name of an employee and its employer without loading any other data.
+// Because there is a relationship between the employee and employer
+// objects (employee::employed_by_), the ODB compiler automatically
+// used this relationship as a join condition. Also, similar to the
+// employee_name view, the first and last data members are automatically
+// assumed to correspond to the first_ and last_ members in the employee
+// object. For the employer_name member we provide an explicit member
+// reference.
+//
+#pragma db view object(employee) object(employer)
+struct employee_employer
+{
+ std::string first;
+ std::string last;
+
+ #pragma db column(employer::name_)
+ std::string employer_name;
+};
+
+// A more interesting aggregate view using GROUP BY. It allows us to
+// calculate the min/max age of employees for each employer. Here we
+// use the C++-integrated syntax for the query condition template with
+// a placeholder (?).
+//
+#pragma db view object(employee) object(employer) \
+ query ((?) + "GROUP BY" + employer::name_)
+struct employer_age
+{
+ #pragma db column(employer::name_)
+ std::string employer_name;
+
+ #pragma db column("min(" + employee::age_ + ")")
+ unsigned short min_age;
+
+ #pragma db column("max(" + employee::age_ + ")")
+ unsigned short max_age;
+};
+
+// A more complex view with three associated objects, two of which are
+// of the same type, which requires us to use aliases and disambiguate
+// the relationships used to join each object.
+//
+#pragma db view object(employee) \
+ object(country = res_country: employee::residence_) \
+ object(country = nat_country: employee::nationality_)
+struct employee_country
+{
+ std::string first;
+ std::string last;
+
+ #pragma db column(res_country::name_)
+ std::string res_country_name;
+
+ #pragma db column(nat_country::name_)
+ std::string nat_country_name;
+};
+
+// A native view. A native view provides a complete query and is normally
+// based on an ad-hoc table. This view allows us to load the employee
+// vacation information from the legacy employee_extra table.
+//
+#pragma db view query("SELECT employee_id, vacation_days " \
+ "FROM view_employee_extra")
+struct employee_vacation
+{
+ #pragma db type("INTEGER")
+ unsigned long id;
+
+ #pragma db type("INTEGER")
+ unsigned short days;
+};
+
+// An improved version of the previous view that extracts the employee
+// first and last names instead of the id. To get the names we need to
+// add the employee object to this view and use a custom join condition
+// to tie it up with our legacy table.
+//
+#pragma db view table("view_employee_extra") \
+ object(employee: "view_employee_extra.employee_id = " + employee::id_)
+struct employee_vacation2
+{
+ std::string first;
+ std::string last;
+
+ #pragma db column("view_employee_extra.vacation_days") type("INTEGER")
+ unsigned short days;
+};
+
+// An advanced view that joins two objects via a legacy table. It returns
+// the previous employer information for each employee.
+//
+#pragma db view object(employee) \
+ table("view_employee_extra" = "extra": \
+ "extra.employee_id = " + employee::id_) \
+ object(employer: "extra.previous_employer_id = " + employer::id_)
+struct employee_prev_employer
+{
+ std::string first;
+ std::string last;
+
+ // If previous_employer_id is NULL, then the name will be NULL as well.
+ // We use the odb::nullable wrapper to handle this.
+ //
+ #pragma db column(employer::name_)
+ odb::nullable<std::string> prev_employer_name;
+};
+
+#endif // EMPLOYEE_HXX
diff --git a/view/makefile b/view/makefile
new file mode 100644
index 0000000..d1aeeec
--- /dev/null
+++ b/view/makefile
@@ -0,0 +1,115 @@
+# file : view/makefile
+# author : Boris Kolpackov <boris@codesynthesis.com>
+# copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC
+# license : GNU GPL v2; see accompanying LICENSE file
+
+include $(dir $(lastword $(MAKEFILE_LIST)))../build/bootstrap.make
+
+cxx_tun := driver.cxx
+odb_hdr := employee.hxx
+cxx_obj := $(addprefix $(out_base)/,$(cxx_tun:.cxx=.o) $(odb_hdr:.hxx=-odb.o))
+cxx_od := $(cxx_obj:.o=.o.d)
+
+driver := $(out_base)/driver
+dist := $(out_base)/.dist
+test := $(out_base)/.test
+clean := $(out_base)/.clean
+
+# Import.
+#
+$(call import,\
+ $(scf_root)/import/odb/stub.make,\
+ odb: odb,odb-rules: odb_rules)
+
+$(call import,\
+ $(scf_root)/import/libodb/stub.make,\
+ l: odb.l,cpp-options: odb.l.cpp-options)
+
+ifdef db_id
+$(call import,\
+ $(scf_root)/import/libodb-$(db_id)/stub.make,\
+ l: odb_db.l,cpp-options: odb_db.l.cpp-options)
+endif
+
+ifeq ($(odb_db.l.cpp-options),)
+odb_db.l.cpp-options := $(out_base)/.unbuildable
+endif
+
+# Build.
+#
+$(driver): $(cxx_obj) $(odb_db.l) $(odb.l)
+$(cxx_obj) $(cxx_od): cpp_options := -I$(out_base) -I$(src_base) -D$(db_macro)
+$(cxx_obj) $(cxx_od): $(odb.l.cpp-options) $(odb_db.l.cpp-options)
+
+genf := $(addprefix $(odb_hdr:.hxx=-odb),.hxx .ixx .cxx) $(odb_hdr:.hxx=.sql)
+gen := $(addprefix $(out_base)/,$(genf))
+
+$(gen): $(odb)
+$(gen): odb := $(odb)
+$(gen) $(dist): export odb_options += --database $(db_id) --generate-query \
+--generate-schema --default-pointer std::tr1::shared_ptr \
+--table-prefix view_
+$(gen): cpp_options := -I$(src_base)
+$(gen): $(odb.l.cpp-options)
+
+$(call include-dep,$(cxx_od),$(cxx_obj),$(gen))
+
+# Alias for default target.
+#
+$(out_base)/: $(driver)
+
+# Dist
+#
+name := $(subst /,-,$(subst $(src_root)/,,$(src_base)))
+
+$(dist): db_id := @database@
+$(dist): sources := $(cxx_tun)
+$(dist): headers := $(odb_hdr)
+$(dist): export name := $(name)
+$(dist): export odb_header_stem := $(basename $(odb_hdr))
+$(dist): export extra_dist := README $(call vc9projs,$(name)) \
+$(call vc10projs,$(name))
+$(dist):
+ $(call dist-data,$(sources) $(headers) README database.hxx)
+ $(call meta-automake,../template/Makefile.am)
+ $(call meta-vc9projs,../template/template,$(name))
+ $(call meta-vc10projs,../template/template,$(name))
+
+# Test.
+#
+$(test): header := $(odb_hdr)
+$(test): $(driver)
+ $(call schema)
+ $(call message,test $<,$< --options-file $(dcf_root)/db.options)
+
+# Clean.
+#
+$(clean): \
+ $(driver).o.clean \
+ $(addsuffix .cxx.clean,$(cxx_obj)) \
+ $(addsuffix .cxx.clean,$(cxx_od)) \
+ $(addprefix $(out_base)/,$(odb_hdr:.hxx=-odb.cxx.hxx.clean))
+
+# Generated .gitignore.
+#
+ifeq ($(out_base),$(src_base))
+$(driver): | $(out_base)/.gitignore
+
+$(out_base)/.gitignore: files := driver $(genf)
+$(clean): $(out_base)/.gitignore.clean
+
+$(call include,$(bld_root)/git/gitignore.make)
+endif
+
+# How to.
+#
+$(call include,$(bld_root)/dist.make)
+$(call include,$(bld_root)/meta/vc9proj.make)
+$(call include,$(bld_root)/meta/vc10proj.make)
+$(call include,$(bld_root)/meta/automake.make)
+
+$(call include,$(odb_rules))
+$(call include,$(bld_root)/cxx/cxx-d.make)
+$(call include,$(bld_root)/cxx/cxx-o.make)
+$(call include,$(bld_root)/cxx/o-e.make)
+