From af15cca60b6bb967383d1b70d19ed715c62a984c Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 9 Nov 2011 09:30:45 +0200 Subject: Document tracing support --- doc/manual.xhtml | 226 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 216 insertions(+), 10 deletions(-) diff --git a/doc/manual.xhtml b/doc/manual.xhtml index 05da67e..f7bea75 100644 --- a/doc/manual.xhtml +++ b/doc/manual.xhtml @@ -329,7 +329,7 @@ for consistency. 3.9Updating Persistent Objects 3.10Deleting Persistent Objects 3.11Executing Native SQL Statements - 3.12ODB Exceptions + 3.13ODB Exceptions @@ -1436,7 +1436,7 @@ main (int argc, char* argv[])

The final bit of code in our example is the catch block that handles the database exceptions. We do this by catching - the base ODB exception (Section 3.12, "ODB + the base ODB exception (Section 3.13, "ODB Exceptions") and printing the diagnostics.

Let's now compile (Section 2.3, "Compiling and @@ -1473,7 +1473,45 @@ mysql> select * from person; mysql> quit -

In the next section we will see how to access persistent objects +

Another way to get more insight into what's going on under the hood, + is to trace the SQL statements executed by ODB as a result of + each database operation. Here is how we can enable tracing just for + the duration of our transaction:

+ +
+    // Create a few persistent person objects.
+    //
+    {
+      ...
+
+      transaction t (db->begin ());
+
+      t.tracer (stderr_tracer);
+
+      // Make objects persistent and save their ids for later use.
+      //
+      john_id = db->persist (john);
+      jane_id = db->persist (jane);
+      joe_id = db->persist (joe);
+
+      t.commit ();
+    }
+  
+ +

With this modification our application now produces the following + output:

+ +
+INSERT INTO `person` (`id`,`first`,`last`,`age`) VALUES (?,?,?,?)
+INSERT INTO `person` (`id`,`first`,`last`,`age`) VALUES (?,?,?,?)
+INSERT INTO `person` (`id`,`first`,`last`,`age`) VALUES (?,?,?,?)
+  
+ +

Note that we see question marks instead of the actual values + because ODB uses prepared statements and sends the data to the + database in binary form. For more information on tracing refer + to Section 3.12, "Tracing SQL Statement Execution". + In the next section we will see how to access persistent objects from our application.

2.5 Querying the Database for Objects

@@ -2682,7 +2720,7 @@ c->execute ("SET FOREIGN_KEY_CHECKS = 1"); instance, by obtaining a valid object id and trying again. The hard errors and corresponding ODB exceptions that can be thrown by each database function are described in the remainder - of this chapter with Section 3.12, "ODB Exceptions" + of this chapter with Section 3.13, "ODB Exceptions" providing a quick reference for all the ODB exceptions.

The second group of ODB exceptions signify soft or @@ -3233,7 +3271,175 @@ t.commit (); connection::execute() functions as described in Section 3.5, "Connections".

-

3.12 ODB Exceptions

+

3.12 Tracing SQL Statement Execution

+ +

Oftentimes it is useful to understand what SQL statement are + executed as a result of high-level database operations. For + example, we can use this information to figure out why certain + transactions don't produce desired results or why they take + longer than expected.

+ +

While this information can usually be obtained from the database + logs, ODB provides an application-side SQL statement tracing + support that is both more convenient and finer-grained. + For example, in a typical situation that calls for tracing + we would like to see the SQL statements executed as a result + of a specific transaction. While it may be difficult to + extract such a subset of statements from the database logs, + it is easy to achieve with ODB tracing support:

+ +
+transaction t (db.begin ());
+t.tracer (stderr_tracer);
+
+...
+
+t.commit ();
+  
+ +

ODB allows us to specify a tracer on the database, connection, + and transaction levels. If specified for the database, then + all the statements executed on this database will be traced. + On the other hand, if a tracer is specified for the + connection, then only the SQL statements executed on this + connection will be traced. Similarly, a tracer specified + for a transaction will only show statements that are + executed as part of this transaction. All three classes + (odb::database, odb::connection, + and odb::transaction) provide the identical + tracing API:

+ +
+  void
+  tracer (odb::tracer&);
+
+  void
+  tracer (odb::tracer*);
+
+  odb::tracer*
+  tracer () const;
+  
+ +

The first two tracer() functions allow us to set + the tracer object with the second one allowing us to clear the + current tracer by passing a NULL pointer. The + last tracer() function allow us to get the + current tracer object. It returns a NULL pointer + if there is no tracer in effect. Note that the tracing API + does not manage the lifetime of the tracer object. The tracer + should be valid for as long as it is being used. Furthermore, + the tracing API is not thread-safe. Trying to set a tracer + from multiple threads simultaneously will result in + undefined behavior.

+ +

The odb::tracer class defines a callback interface + that can be used to create custom tracer implementations. The + odb::stderr_tracer is a built-in tracer implementation + provided by the ODB runtime. It prints each executed SQL statement + to the standard error stream.

+ +

The odb::tracer class is defined in the + <odb/tracer.hxx> header file which you will need to + include in order to make this class available in your application. + The odb::tracer interface provided the following + callback functions:

+ +
+namespace odb
+{
+  class tracer
+  {
+  public:
+    virtual void
+    prepare (connection&, const statement&);
+
+    virtual void
+    execute (connection&, const statement&);
+
+    virtual void
+    execute (connection&, const char* statement) = 0;
+
+    virtual void
+    deallocate (connection&, const statement&);
+  };
+}
+  
+ +

The prepare() and deallocate() functions + are called when a prepared statement is created and destroyed, + respectively. The first execute() function is called + when a prepared statement is executed while the second one is called + when a normal statement is executed. The default implementations + for the prepare() and deallocate() + functions do nothing while the first execute() function + calls the second one passing the statement text as the second + argument. As a result, if all you are interested in are the + SQL statements being executed, that you only need to override the + second execute() function.

+ +

In addition to the common odb::tracer interface, + each database runtime provides a database-specific version + as odb::<database>::tracer. It has exactly + the same interface as the common version except that the + connection and statement types + are database-specific, which gives us access to additional, + database-specific information.

+ +

As an example, consider a more elaborate, PostgreSQL-specific + tracer implementation. Here we rely on the fact that the PostgreSQL + ODB runtime uses names to identify prepared statement and this + information can be obtained from the odb::pgsql::statement + object:

+ +
+#include <odb/pgsql/tracer.hxx>
+#include <odb/pgsql/database.hxx>
+#include <odb/pgsql/connection.hxx>
+#include <odb/pgsql/statement.hxx>
+
+class pgsql_tracer: public odb::pgsql::tracer
+{
+  virtual void
+  prepare (odb::pgsql::connection& c, const odb::pgsql::statement& s)
+  {
+    cerr << c.database ().db () << ": PREPARE " << s.name ()
+         << " AS " << s.text () << endl;
+  }
+
+  virtual void
+  execute (odb::pgsql::connection& c, const odb::pgsql::statement& s)
+  {
+    cerr << c.database ().db () << ": EXECUTE " << s.name () << endl;
+  }
+
+  virtual void
+  execute (odb::pgsql::connection& c, const char* statement)
+  {
+    cerr << c.database ().db () << ": " << statement << endl;
+  }
+
+  virtual void
+  deallocate (odb::pgsql::connection& c, const odb::pgsql::statement& s)
+  {
+    cerr << c.database ().db () << ": DEALLOCATE " << s.name () << endl;
+  }
+};
+  
+ +

Note also that you can only set a database-specific tracer object + using a database-specific database instance, for example:

+ +
+pgsql_tracer tracer;
+
+odb::database& db = ...;
+db.tracer (tracer); // Compile error.
+
+odb::pgsql::database& db = ...;
+db.tracer (tracer); // Ok.
+  
+ +

3.13 ODB Exceptions

In the previous sections we have already mentioned some of the exceptions that can be thrown by the database functions. In this @@ -11375,7 +11581,7 @@ namespace odb SQLite results is the unavailability of the result::size() function. If you call this function on an SQLite query result, then the odb::result_not_cached exception - (Section 3.12, "ODB Exceptions") is always + (Section 3.13, "ODB Exceptions") is always thrown. Future versions of the SQLite ODB runtime library may add support for result caching.

@@ -11474,8 +11680,8 @@ CREATE TABLE Employee ( to distinguish between the duplicate primary key and other constraint violations. As a result, when making an object persistent, The SQLite ODB runtime will translate all constraint violation errors to the - object_not_persistent exception (Section - 3.12, "ODB Exceptions").

+ object_not_persistent exception (Section + 3.13, "ODB Exceptions").

14.5.5 Sharing of Queries

@@ -12196,7 +12402,7 @@ odb --profile boost/date-time ... system. All such exceptions derive from the odb::boost::exception class which in turn derives from the root of the ODB exception hierarchy, class odb::exception - (Section 3.12, "ODB Exceptions"). The + (Section 3.13, "ODB Exceptions"). The odb::boost::exception class is defined in the <odb/boost/exception.hxx> header file and has the same interface as odb::exception. The concrete exceptions @@ -12617,7 +12823,7 @@ class person system. All such exceptions derive from the odb::qt::exception class which in turn derives from the root of the ODB exception hierarchy, class odb::exception - (Section 3.12, "ODB Exceptions"). The + (Section 3.13, "ODB Exceptions"). The odb::qt::exception class is defined in the <odb/qt/exception.hxx> header file and has the same interface as odb::exception. The concrete exceptions -- cgit v1.1