changeset 14:9774802fd05f

PostgreSQLStorageArea working
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 09 Jul 2018 20:28:27 +0200
parents 927264a0c137
children dfc7002add9c
files Framework/Common/DatabaseManager.h Framework/Common/Dictionary.cpp Framework/Common/Dictionary.h Framework/Plugins/StorageBackend.cpp Framework/Plugins/StorageBackend.h Framework/PostgreSQL/PostgreSQLResult.cpp PostgreSQL/CMakeLists.txt PostgreSQL/Plugins/PostgreSQLStorageArea.cpp PostgreSQL/Plugins/PostgreSQLStorageArea.h PostgreSQL/Plugins/StoragePlugin.cpp PostgreSQL/UnitTests/PostgreSQLTests.cpp PostgreSQL/UnitTests/UnitTestsMain.cpp
diffstat 12 files changed, 400 insertions(+), 56 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Common/DatabaseManager.h	Mon Jul 09 18:42:34 2018 +0200
+++ b/Framework/Common/DatabaseManager.h	Mon Jul 09 20:28:27 2018 +0200
@@ -53,7 +53,7 @@
                                           const Query& query);
 
     ITransaction& GetTransaction();
-    
+
   public:
     DatabaseManager(IDatabaseFactory* factory);  // Takes ownership
     
--- a/Framework/Common/Dictionary.cpp	Mon Jul 09 18:42:34 2018 +0200
+++ b/Framework/Common/Dictionary.cpp	Mon Jul 09 20:28:27 2018 +0200
@@ -22,6 +22,7 @@
 #include "Dictionary.h"
 
 #include "BinaryStringValue.h"
+#include "FileValue.h"
 #include "Integer64Value.h"
 #include "NullValue.h"
 #include "Utf8StringValue.h"
@@ -100,6 +101,21 @@
   }
 
   
+  void Dictionary::SetFileValue(const std::string& key,
+                                const std::string& file)
+  {
+    SetValue(key, new FileValue(file));
+  }
+
+  
+  void Dictionary::SetFileValue(const std::string& key,
+                                const void* content,
+                                size_t size)
+  {
+    SetValue(key, new FileValue(content, size));
+  }
+
+  
   void Dictionary::SetIntegerValue(const std::string& key,
                                    int64_t value)
   {
--- a/Framework/Common/Dictionary.h	Mon Jul 09 18:42:34 2018 +0200
+++ b/Framework/Common/Dictionary.h	Mon Jul 09 20:28:27 2018 +0200
@@ -51,6 +51,13 @@
     void SetBinaryValue(const std::string& key,
                         const std::string& binary);
 
+    void SetFileValue(const std::string& key,
+                      const std::string& file);
+
+    void SetFileValue(const std::string& key,
+                      const void* content,
+                      size_t size);
+
     void SetIntegerValue(const std::string& key,
                          int64_t value);
 
--- a/Framework/Plugins/StorageBackend.cpp	Mon Jul 09 18:42:34 2018 +0200
+++ b/Framework/Plugins/StorageBackend.cpp	Mon Jul 09 20:28:27 2018 +0200
@@ -71,13 +71,46 @@
     }
   }
 
-
+  
   StorageBackend::StorageBackend(IDatabaseFactory* factory) :
     manager_(factory)
   {
   }
 
 
+  void StorageBackend::ReadToString(std::string& content,
+                                    DatabaseManager::Transaction& transaction, 
+                                    const std::string& uuid,
+                                    OrthancPluginContentType type)
+  {
+    void* buffer = NULL; 
+    size_t size;
+    Read(buffer, size, transaction, uuid, type);
+
+    try
+    {
+      content.resize(size);
+    }
+    catch (std::bad_alloc&)
+    {
+      if (size != 0)
+      {
+        free(buffer);
+      }
+
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
+    }
+
+    if (size != 0)
+    {
+      assert(buffer != NULL);
+      memcpy(&content[0], buffer, size);
+    }
+
+    free(buffer);
+  }
+
+
   static OrthancPluginContext* context_ = NULL;
   static std::auto_ptr<StorageBackend>  backend_;
     
--- a/Framework/Plugins/StorageBackend.h	Mon Jul 09 18:42:34 2018 +0200
+++ b/Framework/Plugins/StorageBackend.h	Mon Jul 09 20:28:27 2018 +0200
@@ -71,6 +71,12 @@
     static void Register(OrthancPluginContext* context,
                          StorageBackend* backend);   // Takes ownership
 
+    // For unit testing!
+    void ReadToString(std::string& content,
+                      DatabaseManager::Transaction& transaction, 
+                      const std::string& uuid,
+                      OrthancPluginContentType type);
+
     static void Finalize();
   };
 }
--- a/Framework/PostgreSQL/PostgreSQLResult.cpp	Mon Jul 09 18:42:34 2018 +0200
+++ b/Framework/PostgreSQL/PostgreSQLResult.cpp	Mon Jul 09 20:28:27 2018 +0200
@@ -22,6 +22,7 @@
 #include "PostgreSQLResult.h"
 
 #include "../Common/BinaryStringValue.h"
+#include "../Common/FileValue.h"
 #include "../Common/Integer64Value.h"
 #include "../Common/NullValue.h"
 #include "../Common/Utf8StringValue.h"
@@ -221,7 +222,7 @@
 
       case OIDOID:
       {
-        std::auto_ptr<BinaryStringValue> value(new BinaryStringValue);
+        std::auto_ptr<FileValue> value(new FileValue);
         GetLargeObject(value->GetContent(), column);
         return value.release();
       }
--- a/PostgreSQL/CMakeLists.txt	Mon Jul 09 18:42:34 2018 +0200
+++ b/PostgreSQL/CMakeLists.txt	Mon Jul 09 20:28:27 2018 +0200
@@ -30,6 +30,7 @@
   )
 
 add_library(OrthancPostgreSQLStorage SHARED
+  Plugins/PostgreSQLStorageArea.cpp
   Plugins/StoragePlugin.cpp
   ${DATABASES_SOURCES}
   ${AUTOGENERATED_SOURCES}
@@ -63,8 +64,9 @@
 
 add_executable(UnitTests
   Plugins/PostgreSQLIndex.cpp
+  Plugins/PostgreSQLStorageArea.cpp
+  UnitTests/PostgreSQLTests.cpp
   UnitTests/UnitTestsMain.cpp
-  UnitTests/PostgreSQLTests.cpp
   ${DATABASES_SOURCES}
   ${GOOGLE_TEST_SOURCES}
   ${AUTOGENERATED_SOURCES}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PostgreSQL/Plugins/PostgreSQLStorageArea.cpp	Mon Jul 09 20:28:27 2018 +0200
@@ -0,0 +1,155 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PostgreSQLStorageArea.h"
+
+#include "../../Framework/Common/FileValue.h"
+#include "../../Framework/PostgreSQL/PostgreSQLTransaction.h"
+
+#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
+#include <Core/Logging.h>
+
+
+namespace OrthancDatabases
+{
+  IDatabase* PostgreSQLStorageArea::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();
+  }
+
+
+  PostgreSQLStorageArea::PostgreSQLStorageArea(const PostgreSQLParameters& parameters) :
+    StorageBackend(new Factory(*this)),
+    parameters_(parameters),
+    clearAll_(false)
+  {
+  }
+
+
+  void PostgreSQLStorageArea::Create(DatabaseManager::Transaction& transaction,
+                                     const std::string& uuid,
+                                     const void* content,
+                                     size_t size,
+                                     OrthancPluginContentType type)
+  {
+    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.SetFileValue("content", content, size);
+    args.SetIntegerValue("type", type);
+     
+    statement.Execute(args);
+  }
+
+
+  void PostgreSQLStorageArea::Read(void*& content,
+                                   size_t& size,
+                                   DatabaseManager::Transaction& transaction, 
+                                   const std::string& uuid,
+                                   OrthancPluginContentType type) 
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, GetManager(),
+      "SELECT content 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);
+
+    if (statement.IsDone())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+    else if (statement.GetResultFieldsCount() != 1 ||
+             statement.GetResultField(0).GetType() != ValueType_File)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);        
+    }
+    else
+    {
+      const FileValue& value = dynamic_cast<const FileValue&>(statement.GetResultField(0));
+      ReadFromString(content, size, value.GetContent());
+    }
+  }
+
+
+  void PostgreSQLStorageArea::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);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PostgreSQL/Plugins/PostgreSQLStorageArea.h	Mon Jul 09 20:28:27 2018 +0200
@@ -0,0 +1,84 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Framework/Plugins/StorageBackend.h"
+#include "../../Framework/PostgreSQL/PostgreSQLParameters.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();
+
+  public:
+    PostgreSQLStorageArea(const PostgreSQLParameters& parameters);
+
+    void SetClearAll(bool clear)
+    {
+      clearAll_ = clear;
+    }
+
+    virtual void Create(DatabaseManager::Transaction& transaction,
+                        const std::string& uuid,
+                        const void* content,
+                        size_t size,
+                        OrthancPluginContentType type);
+
+    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);
+  };
+}
--- a/PostgreSQL/Plugins/StoragePlugin.cpp	Mon Jul 09 18:42:34 2018 +0200
+++ b/PostgreSQL/Plugins/StoragePlugin.cpp	Mon Jul 09 20:28:27 2018 +0200
@@ -145,6 +145,33 @@
                       const std::string& uuid,
                       OrthancPluginContentType type) 
     {
+      DatabaseManager::CachedStatement statement(
+        STATEMENT_FROM_HERE, GetManager(),
+        "SELECT content FROM StorageArea WHERE uuid=$1 AND type=$2");
+     
+      statement.SetParameterType("uuid", ValueType_Utf8String);
+      statement.SetParameterType("type", ValueType_Integer64);
+
+      Dictionary args;
+      args.SetUtf8Value("uuid", uuid);
+      args.SetIntegerValue("type", type);
+     
+      statement.Execute(args);
+
+      if (statement.IsDone())
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+      }
+      else if (statement.GetResultFieldsCount() != 1 ||
+               statement.GetResultField(0).GetType() != ValueType_File)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);        
+      }
+      else
+      {
+        const FileValue& value = dynamic_cast<const FileValue&>(statement.GetResultField(0));
+        ReadFromString(content, size, value.GetContent());
+      }
     }
 
 
--- a/PostgreSQL/UnitTests/PostgreSQLTests.cpp	Mon Jul 09 18:42:34 2018 +0200
+++ b/PostgreSQL/UnitTests/PostgreSQLTests.cpp	Mon Jul 09 20:28:27 2018 +0200
@@ -34,6 +34,7 @@
 #  undef S_IXOTH
 #endif
 
+#include "../Plugins/PostgreSQLStorageArea.h"
 #include "../../Framework/PostgreSQL/PostgreSQLTransaction.h"
 #include "../../Framework/PostgreSQL/PostgreSQLResult.h"
 #include "../../Framework/PostgreSQL/PostgreSQLLargeObject.h"
@@ -66,7 +67,6 @@
 static int64_t CountLargeObjects(PostgreSQLDatabase& db)
 {
   // Count the number of large objects in the DB
-  PostgreSQLTransaction t(db);
   PostgreSQLStatement s(db, "SELECT COUNT(*) FROM pg_catalog.pg_largeobject", true);
   PostgreSQLResult r(s);
   return r.GetInteger64(0);
@@ -338,12 +338,60 @@
 }
 
 
+TEST(PostgreSQL, StorageArea)
+{
+  OrthancDatabases::PostgreSQLStorageArea storageArea(globalParameters_);
+  storageArea.SetClearAll(true);
 
-#if ORTHANC_POSTGRESQL_STATIC == 1
-#  include <c.h>  // PostgreSQL includes
+  {
+    OrthancDatabases::DatabaseManager::Transaction transaction(storageArea.GetManager());
+    OrthancDatabases::PostgreSQLDatabase& db = 
+      dynamic_cast<OrthancDatabases::PostgreSQLDatabase&>(transaction.GetDatabase());
+
+    ASSERT_EQ(0, CountLargeObjects(db));
+  
+    for (int i = 0; i < 10; i++)
+    {
+      std::string uuid = boost::lexical_cast<std::string>(i);
+      std::string value = "Value " + boost::lexical_cast<std::string>(i * 2);
+      storageArea.Create(transaction, uuid, value.c_str(), value.size(), OrthancPluginContentType_Unknown);
+    }
+
+    std::string tmp;
+    ASSERT_THROW(storageArea.ReadToString(tmp, transaction, "nope", OrthancPluginContentType_Unknown), 
+                 Orthanc::OrthancException);
+  
+    ASSERT_EQ(10, CountLargeObjects(db));
+    storageArea.Remove(transaction, "5", OrthancPluginContentType_Unknown);
+
+    ASSERT_EQ(9, CountLargeObjects(db));
 
-TEST(PostgreSQL, Version)
-{
-  ASSERT_STREQ("9.6.1", PG_VERSION);
+    for (int i = 0; i < 10; i++)
+    {
+      std::string uuid = boost::lexical_cast<std::string>(i);
+      std::string expected = "Value " + boost::lexical_cast<std::string>(i * 2);
+      std::string content;
+
+      if (i == 5)
+      {
+        ASSERT_THROW(storageArea.ReadToString(content, transaction, uuid, OrthancPluginContentType_Unknown), 
+                     Orthanc::OrthancException);
+      }
+      else
+      {
+        storageArea.ReadToString(content, transaction, uuid, OrthancPluginContentType_Unknown);
+        ASSERT_EQ(expected, content);
+      }
+    }
+
+    for (int i = 0; i < 10; i++)
+    {
+      storageArea.Remove(transaction, boost::lexical_cast<std::string>(i),
+                         OrthancPluginContentType_Unknown);
+    }
+
+    ASSERT_EQ(0, CountLargeObjects(db));
+
+    transaction.Commit();
+  }
 }
-#endif
--- a/PostgreSQL/UnitTests/UnitTestsMain.cpp	Mon Jul 09 18:42:34 2018 +0200
+++ b/PostgreSQL/UnitTests/UnitTestsMain.cpp	Mon Jul 09 20:28:27 2018 +0200
@@ -29,6 +29,16 @@
 #include "../../Framework/Plugins/IndexUnitTests.h"
 
 
+#if ORTHANC_POSTGRESQL_STATIC == 1
+#  include <c.h>  // PostgreSQL includes
+
+TEST(PostgreSQL, Version)
+{
+  ASSERT_STREQ("9.6.1", PG_VERSION);
+}
+#endif
+
+
 TEST(PostgreSQLParameters, Basic)
 {
   OrthancDatabases::PostgreSQLParameters p;
@@ -96,51 +106,6 @@
 }
 
 
-#if 0
-TEST(PostgreSQL, StorageArea)
-{
-  std::auto_ptr<PostgreSQLDatabase> pg(CreateTestDatabase(true));
-  PostgreSQLStorageArea s(pg.release(), true, true);
-
-  ASSERT_EQ(0, CountLargeObjects(s.GetDatabase()));
-  
-  for (int i = 0; i < 10; i++)
-  {
-    std::string uuid = boost::lexical_cast<std::string>(i);
-    std::string value = "Value " + boost::lexical_cast<std::string>(i * 2);
-    s.Create(uuid, value.c_str(), value.size(), OrthancPluginContentType_Unknown);
-  }
-
-  std::string tmp;
-  ASSERT_THROW(s.Read(tmp, "nope", OrthancPluginContentType_Unknown), Orthanc::OrthancException);
-  
-  ASSERT_EQ(10, CountLargeObjects(s.GetDatabase()));
-  s.Remove("5", OrthancPluginContentType_Unknown);
-  ASSERT_EQ(9, CountLargeObjects(s.GetDatabase()));
-
-  for (int i = 0; i < 10; i++)
-  {
-    std::string uuid = boost::lexical_cast<std::string>(i);
-    std::string expected = "Value " + boost::lexical_cast<std::string>(i * 2);
-    std::string content;
-
-    if (i == 5)
-    {
-      ASSERT_THROW(s.Read(content, uuid, OrthancPluginContentType_Unknown), Orthanc::OrthancException);
-    }
-    else
-    {
-      s.Read(content, uuid, OrthancPluginContentType_Unknown);
-      ASSERT_EQ(expected, content);
-    }
-  }
-
-  s.Clear();
-  ASSERT_EQ(0, CountLargeObjects(s.GetDatabase()));
-}
-#endif
-
-
 int main(int argc, char **argv)
 {
   if (argc < 6)