From f50c5464689903fa06f68d66be1f0fd9672bf26b Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Fri, 17 Sep 2010 15:07:04 +0200 Subject: First few chapters of the manual --- doc/Makefile.am | 2 +- doc/makefile | 45 +- doc/manual.html2ps | 69 +++ doc/manual.xhtml | 1344 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1446 insertions(+), 14 deletions(-) create mode 100644 doc/manual.html2ps create mode 100644 doc/manual.xhtml (limited to 'doc') diff --git a/doc/Makefile.am b/doc/Makefile.am index baa689b..a6e9ec6 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -5,4 +5,4 @@ dist_doc_DATA = __file__(docs) dist_man_MANS = __file__(mans) -EXTRA_DIST = __file__(extra_dist) + diff --git a/doc/makefile b/doc/makefile index d192c61..1995e36 100644 --- a/doc/makefile +++ b/doc/makefile @@ -17,48 +17,67 @@ $(call import,\ # Build. # -$(default): $(out_base)/odb.xhtml $(out_base)/odb.1 +$(default): \ +$(out_base)/odb.1 \ +$(out_base)/odb.xhtml \ +$(out_base)/odb-manual.ps \ +$(out_base)/odb-manual.pdf +# Man/html pages. +# $(out_base)/odb.xhtml $(out_base)/odb.1: cli := $(cli) $(out_base)/odb.xhtml: $(src_root)/odb/options.cli \ $(src_base)/odb-prologue.xhtml \ - $(src_base)/odb-epilogue.xhtml + $(src_base)/odb-epilogue.xhtml | $(out_base)/. $(call message,cli-html $<,$(cli) --generate-html --stdout \ --html-prologue $(src_base)/odb-prologue.xhtml \ --html-epilogue $(src_base)/odb-epilogue.xhtml $< >$@) -$(out_base)/odb.1: $(src_root)/odb/options.cli \ - $(src_base)/odb-prologue.1 \ - $(src_base)/odb-epilogue.1 +$(out_base)/odb.1: $(src_root)/odb/options.cli \ + $(src_base)/odb-prologue.1 \ + $(src_base)/odb-epilogue.1 | $(out_base)/. $(call message,cli-man $<,$(cli) --generate-man --stdout \ --man-prologue $(src_base)/odb-prologue.1 \ --man-epilogue $(src_base)/odb-epilogue.1 $< >$@) +# Manual. +# +$(out_base)/odb-manual.ps: $(src_base)/manual.xhtml \ + $(src_base)/manual.html2ps | $(out_base)/. + $(call message,html2ps $<,html2ps -f $(src_base)/manual.html2ps -o $@ $<) + +$(out_base)/odb-manual.pdf: $(out_base)/odb-manual.ps + $(call message,ps2pdf $<,ps2pdf14 $< $@) + # Dist. # -$(dist): data_dist := default.css -$(dist): export docs := odb.xhtml +$(dist): export docs := default.css odb.xhtml odb-manual.ps odb-manual.pdf $(dist): export mans := odb.1 -$(dist): export extra_dist := $(data_dist) -$(dist): $(out_base)/odb.xhtml $(out_base)/odb.1 +$(dist): data_dist := default.css +$(dist): \ +$(out_base)/odb.1 \ +$(out_base)/odb.xhtml \ +$(out_base)/odb-manual.ps \ +$(out_base)/odb-manual.pdf + $(call dist-data,$^) $(call dist-data,$(data_dist)) - $(call dist-data,$(out_base)/odb.1) - $(call dist-data,$(out_base)/odb.xhtml) $(call meta-automake) # Clean. # $(clean): - $(call message,rm $$1,rm -f $$1,$(out_base)/odb.xhtml) $(call message,rm $$1,rm -f $$1,$(out_base)/odb.1) + $(call message,rm $$1,rm -f $$1,$(out_base)/odb.xhtml) + $(call message,rm $$1,rm -f $$1,$(out_base)/odb-manual.ps) + $(call message,rm $$1,rm -f $$1,$(out_base)/odb-manual.pdf) # Generated .gitignore. # ifeq ($(out_base),$(src_base)) $(out_base)/odb.xhtml $(out_base)/odb.1: | $(out_base)/.gitignore -$(out_base)/.gitignore: files := odb.xhtml odb.1 +$(out_base)/.gitignore: files := odb.1 odb.xhtml odb-manual.ps odb-manual.pdf $(clean): $(out_base)/.gitignore.clean $(call include,$(bld_root)/git/gitignore.make) diff --git a/doc/manual.html2ps b/doc/manual.html2ps new file mode 100644 index 0000000..36b9c30 --- /dev/null +++ b/doc/manual.html2ps @@ -0,0 +1,69 @@ +@html2ps { + option { + toc: hb; + colour: 1; + hyphenate: 1; + titlepage: 1; + } + + datefmt: "%B %Y"; + + titlepage { + content: " +
+

C++ Object Persistence with ODB

+

 

+

 

+

 

+

 

+

 

+

 

+

 

+

 

+
+

Copyright © 2009-2010 Code Synthesis Tools CC

+ +

Permission is granted to copy, distribute and/or modify this + document under the terms of the + GNU Free + Documentation License, version 1.3; with no Invariant Sections, + no Front-Cover Texts and no Back-Cover Texts. +

+ +

Revision $[revision], $D

+ +

This revision of the manual describes ODB $[version] and is available + in the following formats: + XHTML, + PDF, and + PostScript.

"; + } + + toc { + indent: 2em; + } + + header { + odd-right: $H; + even-left: $H; + } + + footer { + odd-left: Revision $[revision], $D; + odd-center: $T; + odd-right: $N; + + even-left: $N; + even-center: $T; + even-right: Revision $[revision], $D; + } +} + +body { + font-size: 12pt; + text-align: justify; +} + +pre { + font-size: 10pt; +} diff --git a/doc/manual.xhtml b/doc/manual.xhtml new file mode 100644 index 0000000..066ecf9 --- /dev/null +++ b/doc/manual.xhtml @@ -0,0 +1,1344 @@ + + + + + + C++ Object Persistence with ODB + + + + + + + + + + + + + + + +
+
+ +
+ +
+
C++ Object Persistence with ODB
+ +

Copyright © 2009-2010 Code Synthesis Tools CC

+ +

Permission is granted to copy, distribute and/or modify this + document under the terms of the + GNU Free + Documentation License, version 1.3; with no Invariant Sections, + no Front-Cover Texts and no Back-Cover Texts.

+ + +

Revision 1.0, September 2010

+

This revision of the manual describes ODB 1.0.0 and is available + in the following formats: + XHTML, + PDF, and + PostScript.

+
+ +

Table of Contents

+ + + +
+
+ + + + +

2 Hello World Example

+ +

In this chapter we will examine how to create a simple C++ + application that relies on ODB for object persistence using + the traditional "Hello World" example. In particular, we will + discuss how to declare persistent classes, generate database + support code, as well as compile and run our application. We + will also learn how to make objects persistent as well as + query, update and delete persistent objects.

+ +

The code presented in this chapter is based on the + hello example which can be found in the + odb-examples package of the ODB distribution.

+ +

2.1 Declaring a Persistent Class

+ +

In our "Hello World" example we will depart slighly from + the norm and say hello to people instead of the world. People + in our application will be represented as objects of C++ class + person which is saved in person.hxx:

+ +
+// person.hxx
+//
+
+#include <string>
+
+class person
+{
+public:
+  person (const std::string& first,
+          const std::string& last,
+          unsigned short age);
+
+  const std::string&
+  first () const;
+
+  const std::string&
+  last () const;
+
+  unsigned short
+  age () const;
+
+  void
+  age (unsigned short);
+
+private:
+  std::string first_;
+  std::string last_;
+  unsigned short age_;
+};
+  
+ +

In order not to miss anyone whom we need to greet, we would like + to save the person objects in a database. To achive this we declare + the person class as persistent:

+ +
+// person.hxx
+//
+
+#include <string>
+
+#include <odb/code.hxx>     // (1)
+
+#pragma db object           // (2)
+class person
+{
+  ...
+
+private:
+  person () {}              // (3)
+
+  friend class odb::access; // (4)
+
+  #pragma db id auto        // (5)
+  unsigned long id_;        // (5)
+
+  std::string first_;
+  std::string last_;
+  unsigned short age_;
+};
+  
+ +

To be able to save person objects in the database we had to make + five changes, marked with (1) to (5), to the orignal class + definition. The first change is the inclusion of the ODB + headers core.hxx. This headers provides a number + of core ODB declarations, such as odb::access, that + are used to define peristent classes.

+ +

The second change is the addition of db object + pragma just before the class definition. This pragma tells the + ODB compiler that the class that follows is persistent. Note + that making a class persistent does not mean that all objects + of this class will automatiacally be stored in the database. + You would still create ordinary or transient instances + of this class just as you would before. The difference is that + now you can make such transient instances persistent, as we will + see shortly.

+ +

The third change is the addition of the default constructor. + The ODB-generated database support code will use this constructor + when instantiating an object from the persistent state. As we have + done for the person class, you can make the default + constructor private or protected if you don't want to make it + available to the ordinary users of your class.

+ +

With the fourth change we make the odb::access class + friend of our person class. This is necessary to make + the default constructor and the data members accessible to the + ODB support code. If your class has public default constructor and + public data members, then the friend declaration is + unnecessary.

+ +

The final change adds a data member called id_ which + is preceded by another pragma. In ODB every persistent object must + have a unique, within its class, identifier. Or, in other words, + no two persistent instances of the same type have equal + identifiers. For our class we use an integer id. The + db id auto pragma that preceeds the id_ + member tells the ODB compiler that the following member is the + object's id. The auto specifier indicates that it is + a database-assigned id. A unique id will be automatically generated + by the database and assigned to the object when it is made + persistent.

+ +

In this example we choose to add an identifier because none of + the existing members could serve the same purpose. However, if + a class already has a member with suitable properties, then it + is natural to use that member for an identifier. For example, + if our person class contained some form of personal + identification (SSN in the United States or ID/passport number + in other countries), then we could use that as an id. Or, if + we stored an email associated with each person, then we could + have used that since each person is presumed to have a unqiue + email address:

+ +
+class person
+{
+  ...
+
+  #pragma db id
+  std::string email_;
+
+  std::string first_;
+  std::string last_;
+  unsigned short age_;
+};
+  
+ +

Now that we have the header file with the persistent class, let's + see how to generate that database support code that we talked + about.

+ +

2.2 Generating Database Support Code

+ +

The persistent class definition that we created in the previous + section was particularly light on code that could actualy + do the job and store the person't data to a database. There + was no serialization or deserialization code, not even data member + registration, that you would normally have to write by hand in + other ORM libraries for C++. This is because in ODB code + that translates between the database and C++ representations + of an object is automatically generated by the ODB compiler.

+ +

To compile the person.hxx header we created in the + previous section and generate the support code for the MySQL + database we invoke the ODB compiler from a terminal (UNIX) or + a command prompt (Windows):

+ +
+odb -d mysql --generate-query person.hxx
+  
+ +

We will use MySQL in the reminder of this chapter though other + supported database systems can be used instead.

+ +

If you haven't installed the common ODB runtime library + (libodb) or installed it into a directory where + the C++ compiler doesn't search for headers by default, + then you may get the following error:

+ +
+person.hxx:10:24: fatal error: odb/core.hxx: No such file or directory
+  
+ +

To resolve this you will need to specify libodb headers + location with the -I preprocessor option, for example:

+ +
+odb -I.../libodb -d mysql --generate-query person.hxx
+  
+ +

Here .../libodb represents the path to the + libodb directory.

+ +

The above invocation of the ODB compiler produces three C++ files: + person-odb.hxx, person-odb.ixx, + person-odb.cxx. You normally don't use types + or functions contained in these files directly. Rather, all + you have to do is include person-odb.hxx in + C++ files where you are performing database operations + with classes from person.hxx as well as compile + person-odb.cxx and link the resulting object + file to your application.

+ +

You may be wondering what is the --generate-query + option for. It instructs the ODB compiler to generate + optional query support code that we will use later in our + "Hello World" example. Another option that we will find + useful is --generate-schema. This option + makes the ODB compiler generate a fourth file, + person.sql, which contains the database + schema for the classes defined in person.hxx:

+ +
+odb -d mysql --generate-query --generate-schema person.hxx
+  
+ + +

If you would like to see the list of all the available options, + refer to the ODB + Compiler Command Line Manual.

+ + +

Now that we have the persistent class and the database support + code, the only part that is left is the application code that + does something useful with all this. But before we move on to + the fun part, let first learn how to build and run an application + that uses ODB. This way when we have some application code + to try, there are no more delays before we can run it.

+ +

2.3 Compiling and Running

+ +

Assuming that the main() function with some application + code is saved in driver.cxx and the database support + code and schema are generated as described in the previous section, + to build our application we will first need to compile all the C++ + source files and then link them with two ODB runtime libraries.

+ +

On UNIX, the compilation part can be done with the following commands + (for Microsoft Visual Studio setup, see the odb-examples + package):

+ +
+c++ -c driver.cxx
+c++ -c person-odb.cxx
+  
+ +

Similar to the ODB compilation, if you get an error stating that + a headers in odb/ or odb/mysql directory + in not found. In this case you will need to use the -I + preprocessor option to specify the location of the common ODB runtime + library (libodb) and MySQL ODB runtime library + (libodb-mysql).

+ +

Once the compilation is done, we can link the application with + the following command:

+ +
+c++ -o driver driver.o person-odb.o -lodb-mysql -lodb
+  
+ +

Notice that we link our application with two ODB libraries: + libodb which is a common runtime library and + libodb-mysql which is a MySQL runtime library + (if you use another database, then the name of this library + will change accordingly). If you get an error saying that + one of these libraries could not be found, then you will need + to use the -L linker option to specify their locations.

+ +

Before we can run our application we need to create a database + schema using the generated person.sql file. For MySQL + we can use the mysql client program, for example:

+ +
+mysql --user=odb_test --database=odb_test < person.sql
+  
+ +

The above command will login to a local MySQL server as user + odb_test without a password and use database + named odb_test. Note that after executing this + command all data stored in the odb_test database + will be deleted.

+ +

Once the database schema is ready, we run our application + using the same login and database name:

+ +
+./driver --user odb_test --database odb_test
+  
+ + +

2.4 Making Objects Persistent

+ +

Now that we have the infrastructure work out of the way, it + is time to see our first code fragment that interracts with the + database. In this section we will learn how to make person + objects persistent:

+ +
+// driver.cxx
+//
+
+#include <memory>   // std::auto_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+
+#include <odb/mysql/database.hxx>
+
+#include "person.hxx"
+#include "person-odb.hxx"
+
+using namespace std;
+using namespace odb;
+
+int
+main (int argc, char* argv[])
+{
+  try
+  {
+    auto_ptr<database> db (new mysql::database (argc, argv));
+
+    unsigned long john_id, jane_id, joe_id;
+
+    // Create a few persistent person objects.
+    //
+    {
+      person john ("John", "Doe", 33);
+      person jane ("Jane", "Doe", 32);
+      person joe ("Joe", "Dirt", 30);
+
+      transaction t (db->begin_transaction ());
+
+      db->persist (john);
+      db->persist (jane);
+      db->persist (joe);
+
+      t.commit ();
+
+      // Save object ids for later use.
+      //
+      john_id = john.id ();
+      jane_id = jane.id ();
+      joe_id = joe.id ();
+    }
+  }
+  catch (const odb::exception& e)
+  {
+    cerr << e.what () << endl;
+    return 1;
+  }
+}
+  
+ +

Let's examine this code piece by piece. At the beginnig we include + a bunch of headers. Those include odb/database.hxx and + odb/transaction.hxx which define database + system-independant odb::database and + odb::transaction interfaces. Then we include + odb/mysql/database.hxx which defines the + MySQL implementation of the database interface. Finaly, + we include person.hxx and person-odb.hxx + which define our persistent person class.

+ +

Once we are in main(), the first thing we do is create + the MySQL database object. Notice that this is the last line in + driver.cxx that mentions MySQL explicitly; the rest + of the code works though the common interfaces and is database + system-independant. We use the argc/argv + mysql::database constructor which automatically + extract the database parameters, such as login name, passowrd, + database name, etc., from the command line. In your own applications + you may prefer to use other versions of the mysql::database + constructors which allow you to pass this information directly + (@@ ref MySQL database).

+ +

Next we create three person objects. Right now they are + transient objects, which means that if we terminate the application + at this point, they will be gone without any evidence of them ever + existed. The next line starts a database transaction. We discuss + transactions in detail later in this manual. For now all we need + to know is that all ODB database operations must be performed within + a transaction and that a transaction is an atomic unit of work; all + database operations performed within a transaction either succeed + (commited) together or are automatically undone (rolled back).

+ +

Once we are in a transaction, we call the persist() + database function on each of our person objects. + At this point the state of each object is saved in the database. + However, note that this state is not permanent until and unless + the transaction is commited. If, for example, our application + crashes at this point, there will still be no evidence of our + objects ever existed.

+ +

In our case one more thing happens when we call persist() + on a person object. Remember that we decided to use + database-assigned identifiers for our objects. The call to + persist() is where this assignment happens. Once + this function returns, the id_ member contains this + object's unique identifier.

+ +

After we have persisted our objects, it is time to commit the + transaction and make the changes permanent. Only after the + commit() function returns succefully are we + guaranteed that the objects are made persistent. Following + the crashing example, if our application terminates after + the commit for whatever reason, the objects' state in the + database will remain intact. In fact, as we will discover + shortly, our application can be restarted and load the + orignal objects from the database. Note also that a + transaction must be commited explicitly with the + commit() call. If the transaction + object leaves scope without the transaction beeing + explicitly commited or rolled back, it will be automatically + rolled back. This behavior allows you not to worry about + exceptions being thrown within a transaction; if they + cross the transaction boundaries, the transaction will + be automatically rolled back and all the changes made + to the database undone.

+ +

After the transaction has been commited, we save the persistent + objects' ids in local variables. We will use them later in this + chapter to perform other database operations on our persistent + objects. You might have noticed that our person + class doesn't have the id() function that we use + here. To make our code work we need to add a simple accessor + with this name that returns the value of the id_ + data member.

+ +

The final bit of code in our example is the catch + block that handles the ODB exceptions. We do this by catching + the base ODB exception and printing the diagnostics. (@@ Ref + exceptions)

+ +

Let's now compile (see @@ Ref "Compiling and Running") and then + run our first ODB application:

+ +
+mysql --user=odb_test --database=odb_test < person.sql
+./driver --user odb_test --database odb_test
+  
+ +

Our first application doesn't print anything except for error + messages so we can't really tell whether it actually stored the + objects' state in the database. While we will extend our application + to be more enternaining, for now we can use the mysql + client to examine the database content. It will also give us a feel + for how the object are stored:

+ +
+mysql --user=odb_test --database=odb_test
+
+Welcome to the MySQL monitor.
+
+mysql> select * from person;
+
++----+-------+------+-----+
+| id | first | last | age |
++----+-------+------+-----+
+|  1 | John  | Doe  |  33 |
+|  2 | Jane  | Doe  |  32 |
+|  3 | Joe   | Dirt |  30 |
++----+-------+------+-----+
+3 rows in set (0.00 sec)
+
+mysql> quit
+  
+ +

In the next section we will examine how to query persistent objects + from our application.

+ +

2.4 Querying Persistent Objects

+ +

So far our application doesn't resemble a typical "Hello World" + example. It doesn't print anything except for error messages. + Let's change that and teach our application to say hello to + people from our database. To make it a bit more interesting, + let's say hello only to people over 30:

+ +
+// driver.cxx
+//
+
+...
+
+int
+main (int argc, char* argv[])
+{
+  try
+  {
+    ...
+
+    // Create a few persistent person objects.
+    //
+    {
+      ...
+    }
+
+    typedef odb::query<person> query;
+    typedef odb::result<person> result;
+
+    // Say hello to those over 30.
+    //
+    {
+      transaction t (db->begin_transaction ());
+
+      result r (db->query<person> (query::age > 30));
+
+      for (result::iterator i (r.begin ()); i != r.end (); ++i)
+      {
+        cout << "Hello, " << i->first () << "!" << endl;
+      }
+
+      t.commit ();
+    }
+  }
+  catch (const odb::exception& e)
+  {
+    cerr << e.what () << endl;
+    return 1;
+  }
+}
+  
+ +

The first half of our application is the same as before and is + replaced with "..." in the above listing for brievety. Again, let's + examine the rest of it piece by piece.

+ +

The two typedefs create convenient aliases for two + template instantiations that will be used a lot in our application. + The first is the query type for the person objects + and the second is the result type of that query.

+ +

Then we begin a new transaction and call the query() + database function. We pass a query expression + (query::age > 30) which limits the returned objects + only to those with age greater than 30. We also save the result + of the query in a local variable.

+ +

The next few lines perform a pretty standard for-loop iteration + over the result sequence printing hello for every returned person. + Then we commit the transaction and we are node. Let's see what + this application will print:

+ +
+mysql --user=odb_test --database=odb_test < person.sql
+./driver --user odb_test --database odb_test
+
+Hello, John!
+Hello, Jane!
+  
+ + +

That looks about right but how do we know that the query actually + used the database instead of just using some in-memory artifacts of + the earlier persist() calls. One way to test this + would be to comment out the first transaction in our application + and re-run it without re-creating the database schema so that the + objects that were persisted during the previous run will be returned. + Alternatively, we can just re-run the same application without + re-creating the schema and notice that we now how duplicate + objects:

+ +
+./driver --user odb_test --database odb_test
+
+Hello, John!
+Hello, Jane!
+Hello, John!
+Hello, Jane!
+  
+ +

What happens here is that the previous run of our application + persisted a set of person objects and when we re-run + the application, we persist another set with the same names but + with different id. When we later run the query, matches from + both sets are returned. We can change the line where we print + the "Hello" string as follows to illustrate this point:

+ +
+cout << "Hello, " << i->first () << " (" << i->id () << ")!" << endl;
+  
+ +

If we now re-run this modified program, we will get the following + output:

+ +
+./driver --user odb_test --database odb_test
+
+Hello, John (1)!
+Hello, Jane (2)!
+Hello, John (4)!
+Hello, Jane (5)!
+Hello, John (7)!
+Hello, Jane (8)!
+  
+ +

The identifiers 3, 6, and 9 that miss from the above list belong to + the "Joe Dirt" objects which are not selected by this query.

+ +

2.5 Updating Persistent Objects

+ +

While making objects persistent and then querying them are + useful oprations, most applications will also need to change + the object's state and then make these changes persistent. Let's + illustrate this by updating Joe's age who just had a birthday:

+ +
+// driver.cxx
+//
+
+...
+
+int
+main (int argc, char* argv[])
+{
+  try
+  {
+    ...
+
+    unsigned long john_id, jane_id, joe_id;
+
+    // Create a few persistent person objects.
+    //
+    {
+      ...
+
+      // Save object ids for later use.
+      //
+      john_id = john.id ();
+      jane_id = jane.id ();
+      joe_id = joe.id ();
+    }
+
+    // Joe Dirt just had a birthday, so update his age.
+    //
+    {
+      transaction t (db->begin_transaction ());
+
+      auto_ptr<person> joe (db->load<person> (joe_id));
+      joe->age (joe->age () + 1);
+      db->store (*joe);
+
+      t.commit ();
+    }
+
+    // Say hello to those over 30.
+    //
+    {
+      ...
+    }
+  }
+  catch (const odb::exception& e)
+  {
+    cerr << e.what () << endl;
+    return 1;
+  }
+}
+  
+ +

The beginning and the end of this transaction are the same as + the previous two. Once within a transaction, we call the + load() database function to instantiate a + person object with Joe's persistent state. We + pass Joe's object identifer that we stored earlier when we + made this object persistent.

+ +

With the instantiated object in hand we increment the age + and call the store() database function to update + the object's state in the database. Once the transaction is + commited, the changes are made permanent in the database.

+ +

If we now run this application, we will see Joe in the output + since he is now over 30:

+ +
+mysql --user=odb_test --database=odb_test < person.sql
+./driver --user odb_test --database odb_test
+
+Hello, John!
+Hello, Jane!
+Hello, Joe!
+  
+ +

What if we didn't have an identifier for Joe? Maybe this object + was made persisted in another run of our application or by another + application altogether. Provided that we have only one Joe Dirt + in the database, we can use query to come up with an alternative + implementation of the above transaction:

+ +
+    // Joe Dirt just had a birthday, so update his age. An
+    // alternative implementation without using the object id.
+    //
+    {
+      transaction t (db->begin_transaction ());
+
+      result r (db->query<person> (query::first == "Joe" &&
+                                   query::last == "Dirt"));
+
+      result::iterator i (r.begin ());
+
+      if (i != r.end ())
+      {
+        auto_ptr<person> joe (*i);
+        joe->age (joe->age () + 1);
+        db->store (*joe);
+      }
+
+      t.commit ();
+    }
+  
+ +

2.5 Deleting Persistent Objects

+ +

The last operation that we will discuss in this chapter is deleting + the persistent object from the database. The following code + fragment shows how we can delete an object given its identifier:

+ +
+    // John Doe is no longer in our database.
+    //
+    {
+      transaction t (db->begin_transaction ());
+      db->erase<person> (john_id);
+      t.commit ();
+    }
+  
+ +

To delete John from the database we start a transaction, call + the erase() database function with John's object + id, and commit the transaction. After the transaction is commited + the erased object is no longer persistent.

+ +

If we don't have an object id handy, we can use query to find and + delete the object:

+ +
+    // John Doe is no longer in our database. An alternative
+    // implementation without using the object id.
+    //
+    {
+      transaction t (db->begin_transaction ());
+
+      result r (db->query<person> (query::first == "John" &&
+                                   query::last == "Doe"));
+
+      result::iterator i (r.begin ());
+
+      if (i != r.end ())
+      {
+        auto_ptr<person> john (*i);
+        db->erase (*john);
+      }
+
+      t.commit ();
+    }
+  
+ +

2.5 Summary

+ +

This chapter presented a very simple application which, nevertheless, + excercised all core database functions: persist(), + query(), load(), store(), + and erase(). We also saw that writing an application + that uses ODB involves the following steps:

+ +
    +
  1. Declare persistent classes in header files.
  2. +
  3. Compile these headers to generate database support code.
  4. +
  5. Link the application with the support code and two ODB runtime libraries.
  6. +
+ + + +

Do not be concerned if, at this point, much appears unclear. The intent + of this chapter is to give you only a general idea of how to persist C++ + objects with ODB. We will cover all the details throughout the remainder + of this manual.

+ + +

3 Working Title

+ + +

3.1 Base Concepts

+ +

The term database can refer to three distinct things: + a general notion of a place where an application stores its data, + a software implementation for managing this data (for example + MySQL), and, finally, some database software implementations + may manage several data stores which are usually distinguished + by name. This name is also commonly referred to as database.

+ +

In this manual, when we use just the word database, we + refer to the first meaning above, for example, + "The store() function saves the object's state to + the database." The term Database Management System (DBMS) is + often used to refer to the second meaning of the words database. + In this manual we will use the term database system + for short, for example, "Database system-independant + application code." Finally, to distinguish the third meaning + from the other two we will use the term database name, + for example, "The second option specfies the database name + that the application should use to store its data."

+ +

In C++ there is only one notion of a type and an instance + of a type. For example, a fundamental type, such as int, + is, for the most part, treated the same as a user defined class + type. However, when it comes to persistence, we have to place + certain restrictions and requirements on certain C++ types that + can be stored in the database. As a result, we devide persistent + C++ types into two groups: object types and value + types. An stances of an object type is called an object + and an instance of a value type — a value.

+ +

An object is an independant entity. It can be stored, updated, + and deleted in the database independant of other objects or values. + An object has an identifier, called object id, that is + unique among all instances of an object type within a database. + An object consits of data members which are either values or + references to other objects. In contrast, a value can only be + stored in the database as part of an object and doesn't have + its own unique identifier.

+ +

An object type is a C++ class. Because of this one to one + relationship, we will use terms object type + and object class interchangably. In contrast, + a value type can be a fundamental C++ type, such as + int or a class type, such as std::string. + If a value consists of other values then is is called a + composite value and its type — a + composite value type. Otherwise the the value is + called simple value and its type — a + simple value type. Note that the distinction between + simple and composite values is conceptual rather than + representational. For example, std::string + is a simple value type because conceptually string is a + single value even though the representation of the string + class may contain several data member each of which would be + considered a value. In fact, the same value type can be + viewed (and mapped) as both simple and composite by different + applications.

+ +

Seeing how all these concepts map to the relational model + will hopefully make these distinctions more clear. In a relational + database an object type is mapped to a table and a value type is + mapped to one or more columns. A simple value type is mapped + to a single column while a composite value type is mapped to + several columns. Conversly, an object is stored as a row in this + table and a value is stored as one or more cells in this row. + A simple value is stored in a single cell while a composite + value occupies several cells.

+ +

Going back to the distinction beetween simple and composite + values, consider a date type which has three integer data + members: year, month, and day. In one application it can be + conidered a composite value and each member will get its + own column in the relational database. In another application + it can considered as a simple value and stored a single + column as a number of day from some predefined date.

+ +

Until now, we have been using the term persistent class + to refer to object classes. We will continue to do so even though + a value type can also be a class. The reason for this assimetry + is the subordinate nature of value types when it comes to + database operations. Remember that values are never stored + directly but rather as part of an object that contains them. + As a result, when we say that we want to make a C++ class + persistent or persist an instance of a class in the database, + we invariably refer to an object class rather than a value + class.

+ +

To make a C++ class a persistent object class we need to declare + it as such using the db object pragma:

+ +
+    #pragma db object
+    class person
+    {
+      ...
+    };
+  
+ +

The other pargma that we need to use is the db id + which designates one of the data members as an object id:

+ +
+    #pragma db object
+    class person
+    {
+
+    private:
+      #pragma db id
+      unsigned long id_;
+    };
+  
+ +

These two pragmas are the minimum required to declare a + persistent class. Other pragmas can be used to fine-tune + the persistence-related properties of a class and its + members.

+ +

You may be wondering whether we aslo have to do declare value types + as persistent. We don't need to do anything special for simple value + types such as int or std::string since the + ODB compiler knows how to map them to the database system types and + how to convert between the two. On the other hand, if a simple value + is unknown to the ODB compiler then you will need to provide the + mapping to the database system type and, possibly, the code to + convert between the two. For more information on this see @@ Ref + Custom value types/pragma value type. Composite value types are + not yet supported by ODB.

+ +

Normally, you would use object types to model real-world entities, + things that have their own identity. For example, in the + previous chapter we created a person class to model + a person which is a real-world enitity. Name and age, which we + used as data members in our person class are clearly + values. It is hard to think of age 31 or name "Joe" as having their + own identity.

+ +

A good test to determine whether something is an object or + a value is to consider if other objects might reference + it. A person is clearly an object because it can be refered + to by other object's such as a spouce, an employer, or a + bank. On the other hand, a person's age or name is not + something that other objects would normally refer to.

+ +

Also, when an object represents a real entity, it is easy to + choose a suitable object identifier. For example, for a + person there is an established notion of an identifier + (SSN, student id, passport number, etc). Another alternative + is to use person't email address as an identifier.

+ +

Note, however, that these are only guidelines. There could + be goot reasons to make something that would normally be + a value an object. Consider, for example, a database that + stores a vast number of people. Many of the person objects + in this database have the same names and surnames and the + overhead of repeating them in every object may negatively + affect the performance. In this case we could make first name + and last name each an object and only store references to + these objects in the person class.

+ +

An instance of a persistent class can be in one of two states: + transient and persistent. A transient + instance only has a representation in the applciation's + memory and will ceas to exist when the application terminates + unless it is explicitly made persistent. A persistent instance + has a representation in both the application's memory and the + database. A persistent instance will remain even after the + application terminates unless and until it is explicitly + deleted from the database. In other words, a transient instance + of a persistent class behaves just like an instance of any + ordinary C++ class.

+ +

3.2 Transactions and Concurrency

+ +

A transaction is an atomic, consistent, isolated and durable + (ACID) unit of work. All database operations can only be + performed within a transaction and each thread of execution + in an application can have only one active transaction at a + time.

+ +

By atomicity we mean that when it comes to making changes to + the database state within a transaction, + either all the changes succeed or none at all. Consider, + for example, a transaction that transfers funds between two + objects representing bank accounts. If the debit function + on the first object succeeds but the credit function on + the second fails, the transaction is rolled back and the + database state of the first object remains unchanged.

+ +

By consistency we mean that a transaction must take all the + objects stored in the database from one consistent state + to another. For example, if a bank account object must + reference a person object as its owner and we forget to + set this reference before making the object persistent, + the transaction will be rolled back and the database + will remain unchanged.

+ +

By isolation we mean that the changes made to the database + state during a transaction are only visible inside this + transaction until and unless it is commited. Using the + above example with bank transfer, the results of the + debit operation performed on the first object is not + visible to other transactions until the credit operation + is successfully completed and the transaction is commited.

+ +

By durability we mean that once the transaction is committed, + the changes that it made to the database state are permanent + and will survive failures such as an application crash. From + now the only way to alter this state is to execute and commit + another transaction.

+ +

Note that all of the above guarantees only apply to the + object's state in the database as opposed to the object's + state in the application's memory. It is possible to roll + a transaction back but still have changes from this + transaction in the application's memory. An easy way to + avoid this potentiall inconsistency is to instantiate + persistent objects withing the transaction's scope. Consider, + for example, this two implementations of the same transaction:

+ +
+void
+update_age (database& db, person& p)
+{
+  transaction t (db.begin_transaction ());
+
+  p.age (p.age () + 1);
+  db.store (p);
+
+  t.commit ()
+}
+  
+ +

In the above implementation, if the store() call fails + and the transaction is rolled back, the state of the person + object in the database and the state of the same object in the + application's memory will differ. Now consider an + alternative implementation which only instantiates the + person object for the duration of the transaction:

+ +
+void
+update_age (database& db, unsigned long id)
+{
+  transaction t (db.begin_transaction ());
+
+  auto_ptr<person> p (db.load<person> (id));
+  p.age (p.age () + 1);
+  db.store (p);
+
+  t.commit ()
+}
+  
+ +

Of course, it may be not always be possible to write the + application in this style. Oftentimes we need to access and + modify application's state of persistent objects out of + transactions. In this case it may make sense to try to + roll back the changes made to the application state if + the transaction was rolled back and the database state + remains unchanged. One way to do this is to re-load + the object's state from the database:

+ +
+void
+update_age (database& db, person& p)
+{
+  try
+  {
+    transaction t (db.begin_transaction ());
+
+    p.age (p.age () + 1);
+    db.store (p);
+
+    t.commit ()
+  }
+  catch (...)
+  {
+    transaction t (db.begin_transaction ());
+    db.load (p.id (), p);
+    t.commit ();
+
+    throw;
+  }
+}
+  
+ +

A transaction is started by calling the begin_transaction() + database function. The returned transaction handle is stored in + an instance of the odb::transaction class which has + the following interface:

+ +
+namespace odb
+{
+  class transaction
+  {
+  public:
+    typedef odb::database database_type;
+
+    void
+    commit ();
+
+    void
+    rollback ();
+
+    database_type&
+    database ();
+
+    static transaction&
+    current ();
+
+    static bool
+    has_current ();
+  };
+}
+  
+ +

The commit() function commits a transaction and + rollback() rolls it back. Unless the transaction + has been finalized, (explicitly commited or rolled back), + the destructor of the odb::transaction class will + automatically roll it back when the transaction instance goes + out of scope.

+ +

The database() function returns the database this + transaction is working on. The current() static + function returns the currently active transaction for this + thread. If there is no active transaction, this function + throws the odb::not_in_transaction exception. + You can check whether there is a transaction in effect using + the has_current() static function.

+ +

If two or more transaction access or modify more than one object + and are executed concurrently by different applications or by + different threads within the same application, then it is possible + that these transactions will try to access objects in an incompatible + order and deadlock. The canonical example of a deadlock are + two transactions in which the first has modified object1 + and is waiting for the second transaction to commit its changes to + object2 so that it can update object2. At + the same time the second transaction has modified object2 + and is waiting for the first transaction to commit its changes to + object1 because it also needs to modify object1. + As a result none of the two transactions can complete.

+ +

The database system detects such situations and automatically + aborts the waiting operation in one of the deadlocked transactions. + In ODB this translates to the odb::deadlock exception + being thrown from one of the database functions. You would normally + handle a deadlock by restarting the transaction, for example:

+ +
+for (;;)
+{
+  try
+  {
+    transaction t (db.begin_transaction ());
+
+    ...
+
+    t.commit ()
+    break;
+  }
+  catch (const odb::deadlock&)
+  {
+    continue;
+  }
+}
+  
+ +
+
+ + + + -- cgit v1.1