From 7939f7972cf22ee9a74518978e4f7d4d77535e09 Mon Sep 17 00:00:00 2001
From: Boris Kolpackov <boris@codesynthesis.com>
Date: Thu, 24 Mar 2011 14:02:16 +0200
Subject: Add support for clearing connection from active and uncached
 statements

---
 odb/sqlite/connection.cxx           |  32 +++++++--
 odb/sqlite/connection.hxx           |  14 ++++
 odb/sqlite/container-statements.hxx |  12 ++++
 odb/sqlite/object-statements.hxx    |  16 +++++
 odb/sqlite/statement-cache.cxx      |   3 +
 odb/sqlite/statement.cxx            |  57 ++++++---------
 odb/sqlite/statement.hxx            | 140 +++++++++++++++++++++++++++++++++++-
 odb/sqlite/transaction-impl.cxx     |  13 ++++
 8 files changed, 243 insertions(+), 44 deletions(-)

diff --git a/odb/sqlite/connection.cxx b/odb/sqlite/connection.cxx
index 37f5e20..9af5a3b 100644
--- a/odb/sqlite/connection.cxx
+++ b/odb/sqlite/connection.cxx
@@ -9,6 +9,7 @@
 
 #include <odb/sqlite/database.hxx>
 #include <odb/sqlite/connection.hxx>
+#include <odb/sqlite/statement.hxx>
 #include <odb/sqlite/statement-cache.hxx>
 #include <odb/sqlite/error.hxx>
 
@@ -19,8 +20,17 @@ namespace odb
   namespace sqlite
   {
     connection::
+    ~connection ()
+    {
+      statement_cache_.reset (); // Free prepared statements.
+
+      if (sqlite3_close (handle_) == SQLITE_BUSY)
+        assert (false); // Connection has outstanding prepared statements.
+    }
+
+    connection::
     connection (database_type& db)
-        : db_ (db)
+        : db_ (db), statements_ (0)
     {
       int f (db.flags ());
       const string& n (db.name ());
@@ -47,13 +57,21 @@ namespace odb
       statement_cache_.reset (new statement_cache_type (*this));
     }
 
-    connection::
-    ~connection ()
+    void connection::
+    clear ()
     {
-      statement_cache_.reset (); // Free prepared statements.
-
-      if (sqlite3_close (handle_) == SQLITE_BUSY)
-        assert (false); // Connection has outstanding prepared statements.
+      // The current first statement will remove itself from the list
+      // and make the second statement (if any) the new first.
+      //
+      while (statement* s = statements_)
+      {
+        if (!s->cached ())
+          s->finilize ();
+        else if (s->active ())
+          s->reset ();
+        else
+          assert (false); // Statement is neither active nor unached.
+      }
     }
   }
 }
diff --git a/odb/sqlite/connection.hxx b/odb/sqlite/connection.hxx
index 0df08b3..5390609 100644
--- a/odb/sqlite/connection.hxx
+++ b/odb/sqlite/connection.hxx
@@ -23,6 +23,7 @@ namespace odb
 {
   namespace sqlite
   {
+    class statement;
     class statement_cache;
 
     class LIBODB_SQLITE_EXPORT connection: public details::shared_base
@@ -55,14 +56,27 @@ namespace odb
         return *statement_cache_;
       }
 
+    public:
+      // Reset active and finalize uncached statements.
+      //
+      void
+      clear ();
+
     private:
       connection (const connection&);
       connection& operator= (const connection&);
 
     private:
+      friend class statement;
+
       database_type& db_;
       sqlite3* handle_;
 
+      // Linked list of active and uncached statements currently associated
+      // with this connection.
+      //
+      statement* statements_;
+
       std::auto_ptr<statement_cache_type> statement_cache_;
     };
   }
diff --git a/odb/sqlite/container-statements.hxx b/odb/sqlite/container-statements.hxx
index 5f5caef..9bff64c 100644
--- a/odb/sqlite/container-statements.hxx
+++ b/odb/sqlite/container-statements.hxx
@@ -164,10 +164,14 @@ namespace odb
       insert_one_statement ()
       {
         if (insert_one_ == 0)
+        {
           insert_one_.reset (
             new (details::shared) insert_statement_type (
               conn_, traits::insert_one_statement, data_image_binding_));
 
+          insert_one_->cached (true);
+        }
+
         return *insert_one_;
       }
 
@@ -175,6 +179,7 @@ namespace odb
       select_all_statement ()
       {
         if (select_all_ == 0)
+        {
           select_all_.reset (
             new (details::shared) select_statement_type (
               conn_,
@@ -182,6 +187,9 @@ namespace odb
               cond_image_binding_,
               data_image_binding_));
 
+          select_all_->cached (true);
+        }
+
         return *select_all_;
       }
 
@@ -189,10 +197,14 @@ namespace odb
       delete_all_statement ()
       {
         if (delete_all_ == 0)
+        {
           delete_all_.reset (
             new (details::shared) delete_statement_type (
               conn_, traits::delete_all_statement, cond_image_binding_));
 
+          delete_all_->cached (true);
+        }
+
         return *delete_all_;
       }
 
diff --git a/odb/sqlite/object-statements.hxx b/odb/sqlite/object-statements.hxx
index 68fd04e..3ab627d 100644
--- a/odb/sqlite/object-statements.hxx
+++ b/odb/sqlite/object-statements.hxx
@@ -246,10 +246,14 @@ namespace odb
       persist_statement ()
       {
         if (persist_ == 0)
+        {
           persist_.reset (
             new (details::shared) persist_statement_type (
               conn_, object_traits::persist_statement, in_image_binding_));
 
+          persist_->cached (true);
+        }
+
         return *persist_;
       }
 
@@ -257,6 +261,7 @@ namespace odb
       find_statement ()
       {
         if (find_ == 0)
+        {
           find_.reset (
             new (details::shared) find_statement_type (
               conn_,
@@ -264,6 +269,9 @@ namespace odb
               id_image_binding_,
               out_image_binding_));
 
+          find_->cached (true);
+        }
+
         return *find_;
       }
 
@@ -271,6 +279,7 @@ namespace odb
       update_statement ()
       {
         if (update_ == 0)
+        {
           update_.reset (
             new (details::shared) update_statement_type (
               conn_,
@@ -278,6 +287,9 @@ namespace odb
               id_image_binding_,
               in_image_binding_));
 
+          update_->cached (true);
+        }
+
         return *update_;
       }
 
@@ -285,12 +297,16 @@ namespace odb
       erase_statement ()
       {
         if (erase_ == 0)
+        {
           erase_.reset (
             new (details::shared) erase_statement_type (
               conn_,
               object_traits::erase_statement,
               id_image_binding_));
 
+          erase_->cached (true);
+        }
+
         return *erase_;
       }
 
diff --git a/odb/sqlite/statement-cache.cxx b/odb/sqlite/statement-cache.cxx
index 66ef266..383a78e 100644
--- a/odb/sqlite/statement-cache.cxx
+++ b/odb/sqlite/statement-cache.cxx
@@ -21,6 +21,9 @@ namespace odb
           commit_ (new (shared) simple_statement (conn, "COMMIT", 7)),
           rollback_ (new (shared) simple_statement (conn, "ROLLBACK", 9))
     {
+      rollback_->cached (true);
+      commit_->cached (true);
+      begin_->cached (true);
     }
   }
 }
diff --git a/odb/sqlite/statement.cxx b/odb/sqlite/statement.cxx
index fc9e780..aa87939 100644
--- a/odb/sqlite/statement.cxx
+++ b/odb/sqlite/statement.cxx
@@ -24,27 +24,11 @@ namespace odb
     statement::
     ~statement ()
     {
-      sqlite3_finalize (stmt_);
+      finilize ();
     }
 
-    statement::
-    statement (connection& conn, const string& s)
-        : conn_ (conn)
-    {
-      if (int e = sqlite3_prepare_v2 (
-            conn_.handle (),
-            s.c_str (),
-            static_cast<int> (s.size () + 1),
-            &stmt_,
-            0))
-      {
-        translate_error (e, conn_);
-      }
-    }
-
-    statement::
-    statement (connection& conn, const char* s, std::size_t n)
-        : conn_ (conn)
+    void statement::
+    init (const char* s, std::size_t n)
     {
       if (int e = sqlite3_prepare_v2 (
             conn_.handle (),
@@ -55,6 +39,14 @@ namespace odb
       {
         translate_error (e, conn_);
       }
+
+      active_ = false;
+      cached_ = false;
+
+      prev_ = 0;
+      next_ = this;
+
+      list_add (); // Add to the list because we are uncached.
     }
 
     void statement::
@@ -233,14 +225,19 @@ namespace odb
     void select_statement::
     execute ()
     {
+      if (active ())
+        reset ();
+
       done_ = false;
       bind_param (cond_.bind, cond_.count);
+      active (true);
     }
 
     void select_statement::
     free_result ()
     {
-      sqlite3_reset (stmt_);
+      reset ();
+      done_ = true;
     }
 
     bool select_statement::
@@ -250,23 +247,13 @@ namespace odb
       {
         int e (sqlite3_step (stmt_));
 
-        switch (e)
+        if (e != SQLITE_ROW)
         {
-        case SQLITE_DONE:
-          {
-            done_ = true;
-            sqlite3_reset (stmt_);
-            break;
-          }
-        case SQLITE_ROW:
-          {
-            break;
-          }
-        default:
-          {
-            sqlite3_reset (stmt_);
+          reset ();
+          done_ = true;
+
+          if (e != SQLITE_DONE)
             translate_error (e, conn_);
-          }
         }
       }
 
diff --git a/odb/sqlite/statement.hxx b/odb/sqlite/statement.hxx
index fe541e5..2a629fe 100644
--- a/odb/sqlite/statement.hxx
+++ b/odb/sqlite/statement.hxx
@@ -12,12 +12,14 @@
 
 #include <string>
 #include <cstddef>  // std::size_t
+#include <cassert>
 
 #include <odb/forward.hxx>
 #include <odb/details/shared-ptr.hxx>
 
 #include <odb/sqlite/version.hxx>
 #include <odb/sqlite/binding.hxx>
+#include <odb/sqlite/connection.hxx>
 #include <odb/sqlite/details/export.hxx>
 
 namespace odb
@@ -32,10 +34,49 @@ namespace odb
       virtual
       ~statement () = 0;
 
+      sqlite3_stmt*
+      handle ()
+      {
+        return stmt_;
+      }
+
+      // Cached state (public part).
+      //
+    public:
+      bool
+      cached () const
+      {
+        return cached_;
+      }
+
+      void
+      cached (bool cached)
+      {
+        assert (cached);
+
+        if (!cached_)
+        {
+          if (!active_)
+            list_remove ();
+
+          cached_ = true;
+        }
+      }
+
     protected:
-      statement (connection&, const std::string& statement);
-      statement (connection&, const char* statement, std::size_t n);
+      statement (connection& conn, const std::string& statement)
+        : conn_ (conn)
+      {
+        init (statement.c_str (), statement.size () + 1);
+      }
+
+      statement (connection& conn, const char* statement, std::size_t n)
+        : conn_ (conn)
+      {
+        init (statement, n);
+      }
 
+    protected:
       void
       bind_param (const bind*, std::size_t count, std::size_t start_param = 0);
 
@@ -47,9 +88,104 @@ namespace odb
       bool
       bind_result (const bind*, std::size_t count, bool truncated = false);
 
+      // Active state.
+      //
+    protected:
+      bool
+      active () const
+      {
+        return active_;
+      }
+
+      void
+      active (bool active)
+      {
+        assert (active);
+
+        if (!active_)
+        {
+          list_add ();
+          active_ = true;
+        }
+      }
+
+      void
+      reset ()
+      {
+        if (active_)
+        {
+          if (stmt_ != 0)
+            sqlite3_reset (stmt_);
+
+          if (cached_)
+            list_remove ();
+
+          active_ = false;
+        }
+      }
+
+      // Cached state (protected part).
+      //
+    protected:
+      void
+      finilize ()
+      {
+        list_remove ();
+
+        if (stmt_ != 0)
+        {
+          sqlite3_finalize (stmt_);
+          stmt_ = 0;
+        }
+      }
+
     protected:
+      friend class connection;
+
       connection& conn_;
       sqlite3_stmt* stmt_;
+
+      bool active_;
+      bool cached_;
+
+    private:
+      void
+      init (const char* statement, std::size_t n);
+
+      // Doubly-linked list of active/uncached statements.
+      //
+    private:
+      void list_add ()
+      {
+        if (next_ == this)
+        {
+          next_ = conn_.statements_;
+          conn_.statements_ = this;
+        }
+      }
+
+      void list_remove ()
+      {
+        if (next_ != this)
+        {
+          if (prev_ == 0)
+            conn_.statements_ = next_;
+          else
+          {
+            prev_->next_ = next_;
+            prev_ = 0;
+          }
+
+          next_ = this;
+        }
+      }
+
+      // prev_ == 0 means we are the first element.
+      // next_ == 0 means we are the last element.
+      // next_ == this means we are not on the list (prev_ should be 0).
+      //
+      statement* prev_;
+      statement* next_;
     };
 
     class LIBODB_SQLITE_EXPORT simple_statement: public statement
diff --git a/odb/sqlite/transaction-impl.cxx b/odb/sqlite/transaction-impl.cxx
index 3730100..36d6c2b 100644
--- a/odb/sqlite/transaction-impl.cxx
+++ b/odb/sqlite/transaction-impl.cxx
@@ -28,6 +28,13 @@ namespace odb
     void transaction_impl::
     commit ()
     {
+      // Reset active and finilize uncached statements. Active statements
+      // will prevent COMMIT from completing (write statements) or releasing
+      // the locks (read statements). Finilization of uncached statements is
+      // needed to release the connection.
+      //
+      connection_->clear ();
+
       connection_->statement_cache ().commit_statement ().execute ();
 
       // Release the connection.
@@ -38,6 +45,12 @@ namespace odb
     void transaction_impl::
     rollback ()
     {
+      // Reset active and finilize uncached statements. Active statements
+      // will prevent ROLLBACK from completing. Finilization of uncached
+      // statements is needed to release the connection.
+      //
+      connection_->clear ();
+
       connection_->statement_cache ().rollback_statement ().execute ();
 
       // Release the connection.
-- 
cgit v1.1