changeset 12:41543239072d

transactions for storage area
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 09 Jul 2018 18:34:56 +0200
parents 0217486720b3
children 927264a0c137
files Framework/Common/DatabaseManager.cpp Framework/Common/DatabaseManager.h Framework/Plugins/StorageBackend.cpp Framework/Plugins/StorageBackend.h Framework/PostgreSQL/PostgreSQLDatabase.cpp Framework/PostgreSQL/PostgreSQLDatabase.h PostgreSQL/Plugins/PostgreSQLIndex.cpp PostgreSQL/Plugins/StoragePlugin.cpp
diffstat 8 files changed, 290 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Common/DatabaseManager.cpp	Mon Jul 09 11:45:52 2018 +0200
+++ b/Framework/Common/DatabaseManager.cpp	Mon Jul 09 18:34:56 2018 +0200
@@ -286,16 +286,9 @@
   }
 
 
-  DatabaseManager::CachedStatement::CachedStatement(const StatementLocation& location,
-                                                    DatabaseManager& manager,
-                                                    const char* sql) :
-    lock_(manager.mutex_),
-    manager_(manager),
-    location_(location),
-    database_(manager.GetDatabase()),
-    transaction_(manager.GetTransaction())
+  void DatabaseManager::CachedStatement::Setup(const char* sql)
   {
-    statement_ = manager_.LookupCachedStatement(location);
+    statement_ = manager_.LookupCachedStatement(location_);
 
     if (statement_ == NULL)
     {
@@ -304,10 +297,34 @@
     else
     {
       LOG(TRACE) << "Reusing cached statement from "
-                 << location.GetFile() << ":" << location.GetLine();
+                 << location_.GetFile() << ":" << location_.GetLine();
     }
   }
 
+
+  DatabaseManager::CachedStatement::CachedStatement(const StatementLocation& location,
+                                                    DatabaseManager& manager,
+                                                    const char* sql) :
+    lock_(manager.mutex_),
+    manager_(manager),
+    location_(location),
+    transaction_(manager.GetTransaction())
+  {
+    Setup(sql);
+  }
+
+      
+  DatabaseManager::CachedStatement::CachedStatement(const StatementLocation& location,
+                                                    Transaction& transaction,
+                                                    const char* sql) :
+    lock_(manager_.mutex_),
+    manager_(transaction.GetManager()),
+    location_(location),
+    transaction_(manager_.GetTransaction())
+  {
+    Setup(sql);
+  }
+
       
   void DatabaseManager::CachedStatement::SetReadOnly(bool readOnly)
   {
--- a/Framework/Common/DatabaseManager.h	Mon Jul 09 11:45:52 2018 +0200
+++ b/Framework/Common/DatabaseManager.h	Mon Jul 09 18:34:56 2018 +0200
@@ -80,18 +80,57 @@
     
     void RollbackTransaction();
 
+
+    class Transaction : public boost::noncopyable
+    {
+    private:
+      boost::recursive_mutex::scoped_lock  lock_;
+      DatabaseManager&                     manager_;
+      IDatabase&                           database_;
+
+    public:
+      Transaction(DatabaseManager& manager) :
+      lock_(manager.mutex_),
+      manager_(manager),
+      database_(manager.GetDatabase())
+      {
+      }
+
+      void Commit()
+      {
+        manager_.CommitTransaction();
+      }
+    
+      void Rollback()
+      {
+        manager_.RollbackTransaction();
+      }
+
+      DatabaseManager& GetManager()
+      {
+        return manager_;
+      }
+
+      IDatabase& GetDatabase()
+      {
+        return database_;
+      }
+    };
+
+
     class CachedStatement : public boost::noncopyable
     {
     private:
       boost::recursive_mutex::scoped_lock  lock_;
       DatabaseManager&                     manager_;
       StatementLocation                    location_;
-      IDatabase&                           database_;
       ITransaction&                        transaction_;
       IPrecompiledStatement*               statement_;
       std::auto_ptr<Query>                 query_;
       std::auto_ptr<IResult>               result_;
 
+      void Setup(const char* sql);
+
       IResult& GetResult() const;
 
     public:
@@ -99,6 +138,10 @@
                       DatabaseManager& manager,
                       const char* sql);
 
+      CachedStatement(const StatementLocation& location,
+                      Transaction& transaction,
+                      const char* sql);
+
       void SetReadOnly(bool readOnly);
 
       void SetParameterType(const std::string& parameter,
@@ -108,11 +151,6 @@
 
       void Execute(const Dictionary& parameters);
 
-      IDatabase& GetDatabase()
-      {
-        return database_;
-      }
-      
       bool IsDone() const;
       
       void Next();
--- a/Framework/Plugins/StorageBackend.cpp	Mon Jul 09 11:45:52 2018 +0200
+++ b/Framework/Plugins/StorageBackend.cpp	Mon Jul 09 18:34:56 2018 +0200
@@ -48,16 +48,40 @@
 
 namespace OrthancDatabases
 {
+  void StorageBackend::ReadFromString(void*& buffer,
+                                      size_t& size,
+                                      const std::string& content)
+  {
+    size = content.size();
+
+    if (content.empty())
+    {
+      buffer = NULL;
+    }
+    else
+    {
+      buffer = malloc(size);
+
+      if (buffer == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
+      }
+
+      memcpy(buffer, content.c_str(), size);
+    }
+  }
+
+
   StorageBackend::StorageBackend(IDatabaseFactory* factory) :
     manager_(factory)
   {
   }
 
 
-
   static OrthancPluginContext* context_ = NULL;
   static std::auto_ptr<StorageBackend>  backend_;
     
+
   static OrthancPluginErrorCode StorageCreate(const char* uuid,
                                               const void* content,
                                               int64_t size,
@@ -65,7 +89,9 @@
   {
     try
     {
-      backend_->Create(uuid, content, static_cast<size_t>(size), type);
+      DatabaseManager::Transaction transaction(backend_->GetManager());
+      backend_->Create(transaction, uuid, content, static_cast<size_t>(size), type);
+      transaction.Commit();
       return OrthancPluginErrorCode_Success;
     }
     ORTHANC_PLUGINS_DATABASE_CATCH;
@@ -79,9 +105,11 @@
   {
     try
     {
+      DatabaseManager::Transaction transaction(backend_->GetManager());
       size_t tmp;
-      backend_->Read(*content, tmp, uuid, type);
+      backend_->Read(*content, tmp, transaction, uuid, type);
       *size = static_cast<int64_t>(tmp);
+      transaction.Commit();
       return OrthancPluginErrorCode_Success;
     }
     ORTHANC_PLUGINS_DATABASE_CATCH;
@@ -93,7 +121,9 @@
   {
     try
     {
-      backend_->Remove(uuid, type);
+      DatabaseManager::Transaction transaction(backend_->GetManager());
+      backend_->Remove(transaction, uuid, type);
+      transaction.Commit();
       return OrthancPluginErrorCode_Success;
     }
     ORTHANC_PLUGINS_DATABASE_CATCH;
--- a/Framework/Plugins/StorageBackend.h	Mon Jul 09 11:45:52 2018 +0200
+++ b/Framework/Plugins/StorageBackend.h	Mon Jul 09 18:34:56 2018 +0200
@@ -33,31 +33,39 @@
     DatabaseManager   manager_;
 
   protected:
-    DatabaseManager& GetManager()
-    {
-      return manager_;
-    }
-    
+    void ReadFromString(void*& buffer,
+                        size_t& size,
+                        const std::string& content);
+
   public:
     StorageBackend(IDatabaseFactory* factory);
 
     virtual ~StorageBackend()
     {
     }
+
+    DatabaseManager& GetManager() 
+    {
+      return manager_;
+    }
     
-    // WARNING: These methods can possibly be invoked simultaneously
-    // (no mutual exclusion in the storage area plugins)
-    virtual void Create(const std::string& uuid,
+    // NB: These methods will always be invoked in mutual exclusion,
+    // as having access to some "DatabaseManager::Transaction" implies
+    // that the parent "DatabaseManager" is locked
+    virtual void Create(DatabaseManager::Transaction& transaction,
+                        const std::string& uuid,
                         const void* content,
                         size_t size,
                         OrthancPluginContentType type) = 0;
 
     virtual void Read(void*& content,
                       size_t& size,
+                      DatabaseManager::Transaction& transaction, 
                       const std::string& uuid,
                       OrthancPluginContentType type) = 0;
 
-    virtual void Remove(const std::string& uuid,
+    virtual void Remove(DatabaseManager::Transaction& transaction,
+                        const std::string& uuid,
                         OrthancPluginContentType type) = 0;
 
     static void Register(OrthancPluginContext* context,
--- a/Framework/PostgreSQL/PostgreSQLDatabase.cpp	Mon Jul 09 11:45:52 2018 +0200
+++ b/Framework/PostgreSQL/PostgreSQLDatabase.cpp	Mon Jul 09 18:34:56 2018 +0200
@@ -96,26 +96,25 @@
       LOG(ERROR) << "PostgreSQL error: " << message;
       throw Orthanc::OrthancException(Orthanc::ErrorCode_DatabaseUnavailable);
     }
+  }
 
-    if (parameters_.HasLock())
-    {
-      PostgreSQLTransaction transaction(*this);
 
-      int32_t lock = 42;  // Some arbitrary constant
+  void PostgreSQLDatabase::AdvisoryLock(int32_t lock)
+  {
+    PostgreSQLTransaction transaction(*this);
 
-      PostgreSQLStatement s(*this, "select pg_try_advisory_lock(" + 
-                            boost::lexical_cast<std::string>(lock) + ");");
+    PostgreSQLStatement s(*this, "select pg_try_advisory_lock(" + 
+                          boost::lexical_cast<std::string>(lock) + ");");
 
-      PostgreSQLResult result(s);
-      if (result.IsDone() ||
-          !result.GetBoolean(0))
-      {
-        LOG(ERROR) << "The PostgreSQL database is locked by another instance of Orthanc";
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
-      }
+    PostgreSQLResult result(s);
+    if (result.IsDone() ||
+        !result.GetBoolean(0))
+    {
+      LOG(ERROR) << "The PostgreSQL database is locked by another instance of Orthanc";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
+    }
 
-      transaction.Commit();
-    }
+    transaction.Commit();
   }
 
 
--- a/Framework/PostgreSQL/PostgreSQLDatabase.h	Mon Jul 09 11:45:52 2018 +0200
+++ b/Framework/PostgreSQL/PostgreSQLDatabase.h	Mon Jul 09 18:34:56 2018 +0200
@@ -57,6 +57,8 @@
 
     void Open();
 
+    void AdvisoryLock(int32_t lock);
+
     void Execute(const std::string& sql);
 
     bool DoesTableExist(const char* name);
--- a/PostgreSQL/Plugins/PostgreSQLIndex.cpp	Mon Jul 09 11:45:52 2018 +0200
+++ b/PostgreSQL/Plugins/PostgreSQLIndex.cpp	Mon Jul 09 18:34:56 2018 +0200
@@ -66,6 +66,11 @@
 
     db->Open();
 
+    if (parameters_.HasLock())
+    {
+      db->AdvisoryLock(42 /* some arbitrary constant */);
+    }
+
     if (clearAll_)
     {
       db->ClearAll();
@@ -87,12 +92,6 @@
         SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_HasTrigramIndex, 0);
       }
           
-      t.Commit();
-    }
-
-    {
-      PostgreSQLTransaction t(*db);
-
       if (!db->DoesTableExist("Resources"))
       {
         LOG(ERROR) << "Corrupted PostgreSQL database";
--- a/PostgreSQL/Plugins/StoragePlugin.cpp	Mon Jul 09 11:45:52 2018 +0200
+++ b/PostgreSQL/Plugins/StoragePlugin.cpp	Mon Jul 09 18:34:56 2018 +0200
@@ -21,10 +21,154 @@
 
 #include "../../Framework/Plugins/StorageBackend.h"
 
+#include "../../Framework/Common/FileValue.h"
+#include "../../Framework/PostgreSQL/PostgreSQLDatabase.h"
+#include "../../Framework/PostgreSQL/PostgreSQLLargeObject.h"
+#include "../../Framework/PostgreSQL/PostgreSQLTransaction.h"
+
 #include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
 #include <Core/Logging.h>
 
 
+namespace OrthancDatabases
+{
+  class PostgreSQLStorageArea : public StorageBackend
+  {
+  private:
+    class Factory : public IDatabaseFactory
+    {
+    private:
+      PostgreSQLStorageArea&  that_;
+
+    public:
+      Factory(PostgreSQLStorageArea& that) :
+      that_(that)
+      {
+      }
+
+      virtual Dialect GetDialect() const
+      {
+        return Dialect_PostgreSQL;
+      }
+
+      virtual IDatabase* Open()
+      {
+        return that_.OpenInternal();
+      }
+    };
+
+    OrthancPluginContext*  context_;
+    PostgreSQLParameters   parameters_;
+    bool                   clearAll_;
+
+    IDatabase* OpenInternal()
+    {
+      std::auto_ptr<PostgreSQLDatabase> db(new PostgreSQLDatabase(parameters_));
+
+      db->Open();
+
+      if (parameters_.HasLock())
+      {
+        db->AdvisoryLock(43 /* some arbitrary constant */);
+      }
+
+      if (clearAll_)
+      {
+        db->ClearAll();
+      }
+
+      {
+        PostgreSQLTransaction t(*db);
+
+        if (!db->DoesTableExist("StorageArea"))
+        {
+          db->Execute("CREATE TABLE IF NOT EXISTS StorageArea("
+                      "uuid VARCHAR NOT NULL PRIMARY KEY,"
+                      "content OID NOT NULL,"
+                      "type INTEGER NOT NULL)");
+
+          // Automatically remove the large objects associated with the table
+          db->Execute("CREATE OR REPLACE RULE StorageAreaDelete AS ON DELETE "
+                      "TO StorageArea DO SELECT lo_unlink(old.content);");
+        }
+
+        t.Commit();
+      }
+
+      return db.release();
+    }
+
+  public:
+    PostgreSQLStorageArea(const PostgreSQLParameters& parameters) :
+    StorageBackend(new Factory(*this)),
+    parameters_(parameters),
+    clearAll_(false)
+    {
+    }
+
+    void SetClearAll(bool clear)
+    {
+      clearAll_ = clear;
+    }
+
+
+    virtual void Create(DatabaseManager::Transaction& transaction,
+                        const std::string& uuid,
+                        const void* content,
+                        size_t size,
+                        OrthancPluginContentType type)
+    {
+      std::auto_ptr<FileValue> file(new FileValue(content, size));
+      
+      {
+        DatabaseManager::CachedStatement statement(
+          STATEMENT_FROM_HERE, GetManager(),
+          "INSERT INTO StorageArea VALUES (${uuid}, ${content}, ${type})");
+     
+        statement.SetParameterType("uuid", ValueType_Utf8String);
+        statement.SetParameterType("content", ValueType_File);
+        statement.SetParameterType("type", ValueType_Integer64);
+
+        Dictionary args;
+        args.SetUtf8Value("uuid", uuid);
+        args.SetValue("content", file.release());
+        args.SetIntegerValue("type", type);
+     
+        statement.Execute(args);
+      }
+    }
+
+
+    virtual void Read(void*& content,
+                      size_t& size,
+                      DatabaseManager::Transaction& transaction, 
+                      const std::string& uuid,
+                      OrthancPluginContentType type) 
+    {
+    }
+
+
+    virtual void Remove(DatabaseManager::Transaction& transaction,
+                        const std::string& uuid,
+                        OrthancPluginContentType type)
+    {
+      DatabaseManager::CachedStatement statement(
+        STATEMENT_FROM_HERE, GetManager(),
+        "DELETE FROM StorageArea WHERE uuid=${uuid} AND type=${type}");
+     
+      statement.SetParameterType("uuid", ValueType_Utf8String);
+      statement.SetParameterType("type", ValueType_Integer64);
+
+      Dictionary args;
+      args.SetUtf8Value("uuid", uuid);
+      args.SetIntegerValue("type", type);
+     
+      statement.Execute(args);
+    }
+  };
+}
+
+
 static bool DisplayPerformanceWarning()
 {
   (void) DisplayPerformanceWarning;   // Disable warning about unused function
@@ -79,8 +223,9 @@
 
     try
     {
-      // TODO
-      //OrthancDatabases::StorageBackend::Register();
+      OrthancDatabases::PostgreSQLParameters parameters(postgresql);
+      OrthancDatabases::StorageBackend::Register
+        (context, new OrthancDatabases::PostgreSQLStorageArea(parameters));
     }
     catch (Orthanc::OrthancException& e)
     {