changeset 262:2354560daf2f

primitives for recycling patients
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 07 Dec 2012 12:56:27 +0100
parents f1fb9d1aa444
children 5b8e8b74bc8b
files OrthancServer/DatabaseWrapper.cpp OrthancServer/DatabaseWrapper.h OrthancServer/PrepareDatabase.sql UnitTests/ServerIndex.cpp
diffstat 4 files changed, 212 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancServer/DatabaseWrapper.cpp	Wed Dec 05 13:56:53 2012 +0100
+++ b/OrthancServer/DatabaseWrapper.cpp	Fri Dec 07 12:56:27 2012 +0100
@@ -743,9 +743,9 @@
       LOG(INFO) << "Version of the Orthanc database: " << version;
       unsigned int v = boost::lexical_cast<unsigned int>(version);
 
-      // This version of Orthanc is only compatible with version 2 of
-      // the DB schema (since Orthanc 0.3.1)
-      ok = (v == 2); 
+      // This version of Orthanc is only compatible with version 3 of
+      // the DB schema (since Orthanc 0.3.2)
+      ok = (v == 3); 
     }
     catch (boost::bad_lexical_cast&)
     {
@@ -777,4 +777,50 @@
 
     return c;
   }
+
+  bool DatabaseWrapper::SelectPatientToRecycle(int64_t& internalId)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE,
+                        "SELECT patientId FROM PatientRecyclingOrder ORDER BY seq ASC LIMIT 1");
+   
+    if (!s.Step())
+    {
+      // No patient remaining or all the patients are protected
+      return false;
+    }
+    else
+    {
+      internalId = s.ColumnInt(0);
+      return true;
+    }    
+  }
+
+  bool DatabaseWrapper::IsProtectedPatient(int64_t internalId)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE,
+                        "SELECT * FROM PatientRecyclingOrder WHERE patientId = ?");
+    s.BindInt(0, internalId);
+    return !s.Step();
+  }
+
+  void DatabaseWrapper::SetProtectedPatient(int64_t internalId, 
+                                            bool isProtected)
+  {
+    if (isProtected)
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM PatientRecyclingOrder WHERE patientId=?");
+      s.BindInt(0, internalId);
+      s.Run();
+    }
+    else if (IsProtectedPatient(internalId))
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)");
+      s.BindInt(0, internalId);
+      s.Run();
+    }
+    else
+    {
+      // Nothing to do: The patient is already unprotected
+    }
+  }
 }
--- a/OrthancServer/DatabaseWrapper.h	Wed Dec 05 13:56:53 2012 +0100
+++ b/OrthancServer/DatabaseWrapper.h	Fri Dec 07 12:56:27 2012 +0100
@@ -179,6 +179,13 @@
     void GetAllPublicIds(Json::Value& target,
                          ResourceType resourceType);
 
+    bool SelectPatientToRecycle(int64_t& internalId);
+
+    bool IsProtectedPatient(int64_t internalId);
+
+    void SetProtectedPatient(int64_t internalId, 
+                             bool isProtected);
+
     DatabaseWrapper(const std::string& path,
                     IServerIndexListener& listener);
 
--- a/OrthancServer/PrepareDatabase.sql	Wed Dec 05 13:56:53 2012 +0100
+++ b/OrthancServer/PrepareDatabase.sql	Fri Dec 07 12:56:27 2012 +0100
@@ -55,9 +55,15 @@
        date TEXT
        ); 
 
+CREATE TABLE PatientRecyclingOrder(
+       seq INTEGER PRIMARY KEY AUTOINCREMENT,
+       patientId INTEGER REFERENCES Resources(internalId) ON DELETE CASCADE
+       );
+
 CREATE INDEX ChildrenIndex ON Resources(parentId);
 CREATE INDEX PublicIndex ON Resources(publicId);
 CREATE INDEX ResourceTypeIndex ON Resources(resourceType);
+CREATE INDEX PatientRecyclingIndex ON PatientRecyclingOrder(patientId);
 
 CREATE INDEX MainDicomTagsIndex1 ON MainDicomTags(id);
 CREATE INDEX MainDicomTagsIndex2 ON MainDicomTags(tagGroup, tagElement);
@@ -86,6 +92,14 @@
   DELETE FROM Resources WHERE internalId = old.parentId;
 END;
 
+CREATE TRIGGER PatientAdded
+AFTER INSERT ON Resources
+FOR EACH ROW WHEN new.resourceType = 1  -- "1" corresponds to "ResourceType_Patient" in C++
+BEGIN
+  INSERT INTO PatientRecyclingOrder VALUES (NULL, new.internalId);
+END;
+
+
 -- Set the version of the database schema
 -- The "1" corresponds to the "GlobalProperty_DatabaseSchemaVersion" enumeration
-INSERT INTO GlobalProperties VALUES (1, "2");
+INSERT INTO GlobalProperties VALUES (1, "3");
--- a/UnitTests/ServerIndex.cpp	Wed Dec 05 13:56:53 2012 +0100
+++ b/UnitTests/ServerIndex.cpp	Fri Dec 07 12:56:27 2012 +0100
@@ -1,6 +1,7 @@
 #include "gtest/gtest.h"
 
 #include "../OrthancServer/DatabaseWrapper.h"
+#include "../Core/Toolbox.h"
 
 #include <ctype.h>
 #include <glog/logging.h>
@@ -12,7 +13,7 @@
   class ServerIndexListener : public IServerIndexListener
   {
   public:
-    std::set<std::string> deletedFiles_;
+    std::vector<std::string> deletedFiles_;
     std::string ancestorId_;
     ResourceType ancestorType_;
 
@@ -31,7 +32,7 @@
 
     virtual void SignalFileDeleted(const std::string& fileUuid)
     {
-      deletedFiles_.insert(fileUuid);
+      deletedFiles_.push_back(fileUuid);
       LOG(INFO) << "A file must be removed: " << fileUuid;
     }                                
   };
@@ -170,8 +171,12 @@
   index.DeleteResource(a[0]);
 
   ASSERT_EQ(2u, listener.deletedFiles_.size());
-  ASSERT_FALSE(listener.deletedFiles_.find("my json file") == listener.deletedFiles_.end());
-  ASSERT_FALSE(listener.deletedFiles_.find("my dicom file") == listener.deletedFiles_.end());
+  ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), 
+                         listener.deletedFiles_.end(),
+                         "my json file") == listener.deletedFiles_.end());
+  ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), 
+                         listener.deletedFiles_.end(),
+                         "my dicom file") == listener.deletedFiles_.end());
 
   ASSERT_EQ(2u, index.GetTableRecordCount("Resources"));
   ASSERT_EQ(0u, index.GetTableRecordCount("Metadata"));
@@ -183,7 +188,9 @@
   ASSERT_EQ(2u, index.GetTableRecordCount("GlobalProperties"));
 
   ASSERT_EQ(3u, listener.deletedFiles_.size());
-  ASSERT_FALSE(listener.deletedFiles_.find("world") == listener.deletedFiles_.end());
+  ASSERT_FALSE(std::find(listener.deletedFiles_.begin(), 
+                         listener.deletedFiles_.end(),
+                         "world") == listener.deletedFiles_.end());
 }
 
 
@@ -256,3 +263,132 @@
   index.DeleteResource(a[6]);
   ASSERT_EQ("", listener.ancestorId_);  // No more ancestor
 }
+
+
+TEST(DatabaseWrapper, PatientRecycling)
+{
+  ServerIndexListener listener;
+  DatabaseWrapper index(listener);
+
+  std::vector<int64_t> patients;
+  for (int i = 0; i < 10; i++)
+  {
+    std::string p = "Patient " + boost::lexical_cast<std::string>(i);
+    patients.push_back(index.CreateResource(p, ResourceType_Patient));
+    index.AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10));
+    ASSERT_FALSE(index.IsProtectedPatient(patients[i]));
+  }
+
+  ASSERT_EQ(10u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(10u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+
+  listener.Reset();
+
+  index.DeleteResource(patients[5]);
+  index.DeleteResource(patients[0]);
+  ASSERT_EQ(8u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(8u, index.GetTableRecordCount("PatientRecyclingOrder"));
+
+  ASSERT_EQ(2u, listener.deletedFiles_.size());
+  ASSERT_EQ("Patient 5", listener.deletedFiles_[0]);
+  ASSERT_EQ("Patient 0", listener.deletedFiles_[1]);
+
+  int64_t p;
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[6]);
+  index.DeleteResource(p);
+  index.DeleteResource(patients[8]);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[7]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[9]);
+  index.DeleteResource(p);
+  ASSERT_FALSE(index.SelectPatientToRecycle(p));
+
+  ASSERT_EQ(10u, listener.deletedFiles_.size());
+  ASSERT_EQ(0u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+}
+
+
+TEST(DatabaseWrapper, PatientProtection)
+{
+  Toolbox::RemoveFile("hi.sqlite");
+  ServerIndexListener listener;
+  DatabaseWrapper index("hi.sqlite", listener);
+
+  std::vector<int64_t> patients;
+  for (int i = 0; i < 5; i++)
+  {
+    std::string p = "Patient " + boost::lexical_cast<std::string>(i);
+    patients.push_back(index.CreateResource(p, ResourceType_Patient));
+    index.AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10));
+    ASSERT_FALSE(index.IsProtectedPatient(patients[i]));
+  }
+
+  ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+
+  ASSERT_FALSE(index.IsProtectedPatient(patients[2]));
+  index.SetProtectedPatient(patients[2], true);
+  ASSERT_TRUE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder"));
+  ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); 
+
+  index.SetProtectedPatient(patients[2], true);
+  ASSERT_TRUE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+  index.SetProtectedPatient(patients[2], false);
+  ASSERT_FALSE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+  index.SetProtectedPatient(patients[2], false);
+  ASSERT_FALSE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+
+  ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+  index.SetProtectedPatient(patients[2], true);
+  ASSERT_TRUE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder"));
+  index.SetProtectedPatient(patients[2], false);
+  ASSERT_FALSE(index.IsProtectedPatient(patients[2]));
+  ASSERT_EQ(5u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+  index.SetProtectedPatient(patients[3], true);
+  ASSERT_TRUE(index.IsProtectedPatient(patients[3]));
+  ASSERT_EQ(4u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+
+  ASSERT_EQ(5u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(0u, listener.deletedFiles_.size());
+
+  // Unprotecting a patient puts it at the last position in the recycling queue
+  int64_t p;
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[0]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]);
+  index.DeleteResource(p);
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]);
+  index.DeleteResource(p);
+  // "patients[3]" is still protected
+  ASSERT_FALSE(index.SelectPatientToRecycle(p));
+
+  ASSERT_EQ(4u, listener.deletedFiles_.size());
+  ASSERT_EQ(1u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+
+  index.SetProtectedPatient(patients[3], false);
+  ASSERT_EQ(1u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+  ASSERT_TRUE(index.SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]);
+  index.DeleteResource(p);
+
+  ASSERT_EQ(5u, listener.deletedFiles_.size());
+  ASSERT_EQ(0u, index.GetTableRecordCount("Resources")); 
+  ASSERT_EQ(0u, index.GetTableRecordCount("PatientRecyclingOrder")); 
+}