changeset 71:d40c5fecd160 db-changes

new extension implemented for PostgreSQL: CreateInstance
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 04 Jan 2019 13:51:52 +0100
parents e6c13ddd26d9
children 8dd29af7c844
files Framework/Plugins/OrthancCppDatabasePlugin.h PostgreSQL/CMakeLists.txt PostgreSQL/Plugins/CreateInstance.sql PostgreSQL/Plugins/PostgreSQLIndex.cpp PostgreSQL/Plugins/PostgreSQLIndex.h PostgreSQL/UnitTests/PostgreSQLTests.cpp
diffstat 6 files changed, 267 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Plugins/OrthancCppDatabasePlugin.h	Thu Jan 03 14:04:46 2019 +0100
+++ b/Framework/Plugins/OrthancCppDatabasePlugin.h	Fri Jan 04 13:51:52 2019 +0100
@@ -491,12 +491,25 @@
 
     virtual void ClearMainDicomTags(int64_t internalId) = 0;
 
+    virtual bool HasCreateInstance() const = 0;
+
 #if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1
     virtual void LookupResources(const std::vector<Orthanc::DatabaseConstraint>& lookup,
                                  OrthancPluginResourceType queryLevel,
                                  uint32_t limit,
                                  bool requestSomeInstance) = 0;
 #endif
+
+#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1
+    virtual void CreateInstance(OrthancPluginCreateInstanceResult& result,
+                                const char* hashPatient,
+                                const char* hashStudy,
+                                const char* hashSeries,
+                                const char* hashInstance)
+    {
+      throw std::runtime_error("Not implemented");
+    }
+#endif
   };
 
 
@@ -1503,6 +1516,26 @@
     }
 #endif
 
+
+#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1
+    static OrthancPluginErrorCode CreateInstance(OrthancPluginCreateInstanceResult* output,
+                                                 void* payload,
+                                                 const char* hashPatient,
+                                                 const char* hashStudy,
+                                                 const char* hashSeries,
+                                                 const char* hashInstance)
+    {
+      IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(payload);
+
+      try
+      {
+        backend->CreateInstance(*output, hashPatient, hashStudy, hashSeries, hashInstance);
+        return OrthancPluginErrorCode_Success;
+      }
+      ORTHANC_PLUGINS_DATABASE_CATCH      
+    }
+#endif
+
     
   public:
     /**
@@ -1585,6 +1618,12 @@
 
 #if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1
       extensions.lookupResources = LookupResources;   // New in Orthanc 1.5.2 (fast lookup)
+
+      if (backend.HasCreateInstance())
+      {
+        extensions.createInstance = CreateInstance;   // New in Orthanc 1.5.2 (fast create)
+      }
+      
       performanceWarning = false;
 #endif      
 
--- a/PostgreSQL/CMakeLists.txt	Thu Jan 03 14:04:46 2019 +0100
+++ b/PostgreSQL/CMakeLists.txt	Fri Jan 04 13:51:52 2019 +0100
@@ -52,7 +52,8 @@
 
 
 EmbedResources(
-  POSTGRESQL_PREPARE_INDEX ${CMAKE_SOURCE_DIR}/Plugins/PrepareIndex.sql
+  POSTGRESQL_PREPARE_INDEX    ${CMAKE_SOURCE_DIR}/Plugins/PrepareIndex.sql
+  POSTGRESQL_CREATE_INSTANCE  ${CMAKE_SOURCE_DIR}/Plugins/CreateInstance.sql
   )
 
 add_library(OrthancPostgreSQLIndex SHARED
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PostgreSQL/Plugins/CreateInstance.sql	Fri Jan 04 13:51:52 2019 +0100
@@ -0,0 +1,66 @@
+CREATE FUNCTION CreateInstance(
+  IN patient TEXT,
+  IN study TEXT,
+  IN series TEXT,
+  IN instance TEXT,
+  OUT isNewPatient BIGINT,
+  OUT isNewStudy BIGINT,
+  OUT isNewSeries BIGINT,
+  OUT isNewInstance BIGINT,
+  OUT patientKey BIGINT,
+  OUT studyKey BIGINT,
+  OUT seriesKey BIGINT,
+  OUT instanceKey BIGINT) AS $$
+BEGIN
+  SELECT internalId FROM Resources INTO instanceKey WHERE publicId = instance AND resourceType = 3;
+
+  IF NOT (instanceKey IS NULL) THEN
+    -- This instance already exists, stop here
+    isNewInstance := 0;
+  ELSE
+    SELECT internalId FROM Resources INTO patientKey WHERE publicId = patient AND resourceType = 0;
+    SELECT internalId FROM Resources INTO studyKey WHERE publicId = study AND resourceType = 1;
+    SELECT internalId FROM Resources INTO seriesKey WHERE publicId = series AND resourceType = 2;
+
+    IF patientKey IS NULL THEN
+      -- Must create a new patient
+      ASSERT studyKey IS NULL;
+      ASSERT seriesKey IS NULL;
+      ASSERT instanceKey IS NULL;
+      INSERT INTO Resources VALUES (DEFAULT, 0, patient, NULL) RETURNING internalId INTO patientKey;
+      isNewPatient := 1;
+    ELSE
+      isNewPatient := 0;
+    END IF;
+  
+    ASSERT NOT patientKey IS NULL;
+
+    IF studyKey IS NULL THEN
+      -- Must create a new study
+      ASSERT seriesKey IS NULL;
+      ASSERT instanceKey IS NULL;
+      INSERT INTO Resources VALUES (DEFAULT, 1, study, patientKey) RETURNING internalId INTO studyKey;
+      isNewStudy := 1;
+    ELSE
+      isNewStudy := 0;
+    END IF;
+
+    ASSERT NOT studyKey IS NULL;
+    
+    IF seriesKey IS NULL THEN
+      -- Must create a new series
+      ASSERT instanceKey IS NULL;
+      INSERT INTO Resources VALUES (DEFAULT, 2, series, studyKey) RETURNING internalId INTO seriesKey;
+      isNewSeries := 1;
+    ELSE
+      isNewSeries := 0;
+    END IF;
+  
+    ASSERT NOT seriesKey IS NULL;
+    ASSERT instanceKey IS NULL;
+
+    INSERT INTO Resources VALUES (DEFAULT, 3, instance, seriesKey) RETURNING internalId INTO instanceKey;
+    isNewInstance := 1;
+  END IF;  
+END;
+$$ LANGUAGE plpgsql;
--- a/PostgreSQL/Plugins/PostgreSQLIndex.cpp	Thu Jan 03 14:04:46 2019 +0100
+++ b/PostgreSQL/Plugins/PostgreSQLIndex.cpp	Fri Jan 04 13:51:52 2019 +0100
@@ -35,6 +35,7 @@
 {
   // Some aliases for internal properties
   static const GlobalProperty GlobalProperty_HasTrigramIndex = GlobalProperty_DatabaseInternal0;
+  static const GlobalProperty GlobalProperty_HasCreateInstance = GlobalProperty_DatabaseInternal1;
 }
 
 
@@ -126,7 +127,8 @@
       PostgreSQLTransaction t(*db);
 
       int hasTrigram = 0;
-      if (!LookupGlobalIntegerProperty(hasTrigram, *db, t, Orthanc::GlobalProperty_HasTrigramIndex) ||
+      if (!LookupGlobalIntegerProperty(hasTrigram, *db, t,
+                                       Orthanc::GlobalProperty_HasTrigramIndex) ||
           hasTrigram != 1)
       {
         /**
@@ -164,6 +166,27 @@
       }
     }
 
+    {
+      PostgreSQLTransaction t(*db);
+
+      int hasCreateInstance = 0;
+      if (!LookupGlobalIntegerProperty(hasCreateInstance, *db, t,
+                                       Orthanc::GlobalProperty_HasCreateInstance) ||
+          hasCreateInstance != 1)
+      {
+        LOG(INFO) << "Installing the CreateInstance extension";
+
+        std::string query;
+        Orthanc::EmbeddedResources::GetFileResource
+          (query, Orthanc::EmbeddedResources::POSTGRESQL_CREATE_INSTANCE);
+        db->Execute(query);
+
+        SetGlobalIntegerProperty(*db, t, Orthanc::GlobalProperty_HasCreateInstance, 1);
+
+        t.Commit();
+      }
+    }
+
     return db.release();
   }
 
@@ -195,4 +218,53 @@
 
     return ReadInteger64(statement, 0);
   }
+
+
+  void PostgreSQLIndex::CreateInstance(OrthancPluginCreateInstanceResult& result,
+                                       const char* hashPatient,
+                                       const char* hashStudy,
+                                       const char* hashSeries,
+                                       const char* hashInstance)
+  {
+    DatabaseManager::CachedStatement statement(
+      STATEMENT_FROM_HERE, GetManager(),
+      "SELECT * FROM CreateInstance(${patient}, ${study}, ${series}, ${instance})");
+
+    statement.SetParameterType("patient", ValueType_Utf8String);
+    statement.SetParameterType("study", ValueType_Utf8String);
+    statement.SetParameterType("series", ValueType_Utf8String);
+    statement.SetParameterType("instance", ValueType_Utf8String);
+
+    Dictionary args;
+    args.SetUtf8Value("patient", hashPatient);
+    args.SetUtf8Value("study", hashStudy);
+    args.SetUtf8Value("series", hashSeries);
+    args.SetUtf8Value("instance", hashInstance);
+    
+    statement.Execute(args);
+
+    if (statement.IsDone() ||
+        statement.GetResultFieldsCount() != 8)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_Database);
+    }
+
+    for (size_t i = 0; i < 8; i++)
+    {
+      statement.SetResultFieldType(i, ValueType_Integer64);
+    }
+
+    result.isNewInstance = (ReadInteger64(statement, 3) == 1);
+    result.instanceId = ReadInteger64(statement, 7);
+
+    if (result.isNewInstance)
+    {
+      result.isNewPatient = (ReadInteger64(statement, 0) == 1);
+      result.isNewStudy = (ReadInteger64(statement, 1) == 1);
+      result.isNewSeries = (ReadInteger64(statement, 2) == 1);
+      result.patientId = ReadInteger64(statement, 4);
+      result.studyId = ReadInteger64(statement, 5);
+      result.seriesId = ReadInteger64(statement, 6);
+    }
+  }
 }
--- a/PostgreSQL/Plugins/PostgreSQLIndex.h	Thu Jan 03 14:04:46 2019 +0100
+++ b/PostgreSQL/Plugins/PostgreSQLIndex.h	Fri Jan 04 13:51:52 2019 +0100
@@ -72,5 +72,18 @@
 
     virtual int64_t CreateResource(const char* publicId,
                                    OrthancPluginResourceType type);
+
+    virtual bool HasCreateInstance() const
+    {
+      return true;
+    }
+
+#if ORTHANC_PLUGINS_HAS_DATABASE_CONSTRAINT == 1
+    virtual void CreateInstance(OrthancPluginCreateInstanceResult& result,
+                                const char* hashPatient,
+                                const char* hashStudy,
+                                const char* hashSeries,
+                                const char* hashInstance);
+#endif
   };
 }
--- a/PostgreSQL/UnitTests/PostgreSQLTests.cpp	Thu Jan 03 14:04:46 2019 +0100
+++ b/PostgreSQL/UnitTests/PostgreSQLTests.cpp	Fri Jan 04 13:51:52 2019 +0100
@@ -34,6 +34,7 @@
 #  undef S_IXOTH
 #endif
 
+#include "../Plugins/PostgreSQLIndex.h"
 #include "../Plugins/PostgreSQLStorageArea.h"
 #include "../../Framework/PostgreSQL/PostgreSQLTransaction.h"
 #include "../../Framework/PostgreSQL/PostgreSQLResult.h"
@@ -437,3 +438,76 @@
   ASSERT_TRUE(db->DoesTableExist("test2"));
 }
 
+
+TEST(PostgreSQLIndex, CreateInstance)
+{
+  OrthancDatabases::PostgreSQLIndex db(globalParameters_);
+  db.SetClearAll(true);
+  db.Open();
+
+  std::string s;
+  ASSERT_TRUE(db.LookupGlobalProperty(s, Orthanc::GlobalProperty_DatabaseInternal1));
+  ASSERT_EQ("1", s);
+
+  OrthancPluginCreateInstanceResult r1, r2;
+  
+  memset(&r1, 0, sizeof(r1));
+  db.CreateInstance(r1, "a", "b", "c", "d");
+  ASSERT_TRUE(r1.isNewInstance);
+  ASSERT_TRUE(r1.isNewSeries);
+  ASSERT_TRUE(r1.isNewStudy);
+  ASSERT_TRUE(r1.isNewPatient);
+
+  memset(&r2, 0, sizeof(r2));
+  db.CreateInstance(r2, "a", "b", "c", "d");
+  ASSERT_FALSE(r2.isNewInstance);
+  ASSERT_EQ(r1.instanceId, r2.instanceId);
+
+  // Breaking the hierarchy
+  memset(&r2, 0, sizeof(r2));
+  ASSERT_THROW(db.CreateInstance(r2, "a", "e", "c", "f"), Orthanc::OrthancException);
+
+  memset(&r2, 0, sizeof(r2));
+  db.CreateInstance(r2, "a", "b", "c", "e");
+  ASSERT_TRUE(r2.isNewInstance);
+  ASSERT_FALSE(r2.isNewSeries);
+  ASSERT_FALSE(r2.isNewStudy);
+  ASSERT_FALSE(r2.isNewPatient);
+  ASSERT_EQ(r1.patientId, r2.patientId);
+  ASSERT_EQ(r1.studyId, r2.studyId);
+  ASSERT_EQ(r1.seriesId, r2.seriesId);
+  ASSERT_NE(r1.instanceId, r2.instanceId);
+
+  memset(&r2, 0, sizeof(r2));
+  db.CreateInstance(r2, "a", "b", "f", "g");
+  ASSERT_TRUE(r2.isNewInstance);
+  ASSERT_TRUE(r2.isNewSeries);
+  ASSERT_FALSE(r2.isNewStudy);
+  ASSERT_FALSE(r2.isNewPatient);
+  ASSERT_EQ(r1.patientId, r2.patientId);
+  ASSERT_EQ(r1.studyId, r2.studyId);
+  ASSERT_NE(r1.seriesId, r2.seriesId);
+  ASSERT_NE(r1.instanceId, r2.instanceId);
+
+  memset(&r2, 0, sizeof(r2));
+  db.CreateInstance(r2, "a", "h", "i", "j");
+  ASSERT_TRUE(r2.isNewInstance);
+  ASSERT_TRUE(r2.isNewSeries);
+  ASSERT_TRUE(r2.isNewStudy);
+  ASSERT_FALSE(r2.isNewPatient);
+  ASSERT_EQ(r1.patientId, r2.patientId);
+  ASSERT_NE(r1.studyId, r2.studyId);
+  ASSERT_NE(r1.seriesId, r2.seriesId);
+  ASSERT_NE(r1.instanceId, r2.instanceId);
+
+  memset(&r2, 0, sizeof(r2));
+  db.CreateInstance(r2, "k", "l", "m", "n");
+  ASSERT_TRUE(r2.isNewInstance);
+  ASSERT_TRUE(r2.isNewSeries);
+  ASSERT_TRUE(r2.isNewStudy);
+  ASSERT_TRUE(r2.isNewPatient);
+  ASSERT_NE(r1.patientId, r2.patientId);
+  ASSERT_NE(r1.studyId, r2.studyId);
+  ASSERT_NE(r1.seriesId, r2.seriesId);
+  ASSERT_NE(r1.instanceId, r2.instanceId);
+}