changeset 4591:ff8170d17d90 db-changes

moving all accesses to databases from IDatabaseWrapper to ITransaction
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 15 Mar 2021 15:30:42 +0100
parents 4a0bf1019335
children 36bbf3169a27
files OrthancServer/CMakeLists.txt OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp OrthancServer/Plugins/Engine/OrthancPluginDatabase.h OrthancServer/Plugins/Engine/OrthancPlugins.cpp OrthancServer/Resources/RunCppCheck.sh OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp OrthancServer/Sources/Database/Compatibility/DatabaseLookup.h OrthancServer/Sources/Database/Compatibility/ILookupResources.cpp OrthancServer/Sources/Database/Compatibility/ILookupResources.h OrthancServer/Sources/Database/Compatibility/SetOfResources.cpp OrthancServer/Sources/Database/Compatibility/SetOfResources.h OrthancServer/Sources/Database/IDatabaseListener.h OrthancServer/Sources/Database/IDatabaseWrapper.h OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp OrthancServer/Sources/Database/SQLiteDatabaseWrapper.h OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp OrthancServer/Sources/Database/StatelessDatabaseOperations.h OrthancServer/Sources/Database/VoidDatabaseListener.cpp OrthancServer/Sources/Database/VoidDatabaseListener.h OrthancServer/Sources/ServerIndex.cpp OrthancServer/Sources/ServerJobs/ArchiveJob.cpp OrthancServer/Sources/ServerToolbox.cpp OrthancServer/Sources/ServerToolbox.h OrthancServer/UnitTestsSources/ServerIndexTests.cpp
diffstat 24 files changed, 2651 insertions(+), 2834 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancServer/CMakeLists.txt	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/CMakeLists.txt	Mon Mar 15 15:30:42 2021 +0100
@@ -99,6 +99,7 @@
   ${CMAKE_SOURCE_DIR}/Sources/Database/ResourcesContent.cpp
   ${CMAKE_SOURCE_DIR}/Sources/Database/SQLiteDatabaseWrapper.cpp
   ${CMAKE_SOURCE_DIR}/Sources/Database/StatelessDatabaseOperations.cpp
+  ${CMAKE_SOURCE_DIR}/Sources/Database/VoidDatabaseListener.cpp
   ${CMAKE_SOURCE_DIR}/Sources/DicomInstanceOrigin.cpp
   ${CMAKE_SOURCE_DIR}/Sources/DicomInstanceToStore.cpp
   ${CMAKE_SOURCE_DIR}/Sources/EmbeddedResourceHttpHandler.cpp
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp	Mon Mar 15 15:30:42 2021 +0100
@@ -41,13 +41,20 @@
 
 #include "../../../OrthancFramework/Sources/Logging.h"
 #include "../../../OrthancFramework/Sources/OrthancException.h"
+#include "../../Sources/Database/VoidDatabaseListener.h"
 #include "PluginsEnumerations.h"
 
 #include <cassert>
 
 namespace Orthanc
 {
-  class OrthancPluginDatabase::Transaction : public IDatabaseWrapper::ITransaction
+  class OrthancPluginDatabase::Transaction :
+    public IDatabaseWrapper::ITransaction,
+    public Compatibility::ICreateInstance,
+    public Compatibility::IGetChildrenMetadata,
+    public Compatibility::ILookupResources,
+    public Compatibility::ILookupResourceAndParent,
+    public Compatibility::ISetResourcesContent
   {
   private:
     OrthancPluginDatabase&  that_;
@@ -101,7 +108,7 @@
 
         uint64_t newDiskSize = (that_.currentDiskSize_ + diskSizeDelta);
 
-        assert(newDiskSize == that_.GetTotalCompressedSize());
+        assert(newDiskSize == GetTotalCompressedSize());
 
         CheckSuccess(that_.backend_.commitTransaction(that_.payload_));
 
@@ -109,6 +116,871 @@
         that_.currentDiskSize_ = newDiskSize;
       }
     }
+
+
+    // From the "ILookupResources" interface
+    void GetAllInternalIds(std::list<int64_t>& target,
+                           ResourceType resourceType) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.getAllInternalIds == NULL)
+      {
+        throw OrthancException(ErrorCode_DatabasePlugin,
+                               "The database plugin does not implement the mandatory GetAllInternalIds() extension");
+      }
+
+      that_.ResetAnswers();
+      CheckSuccess(that_.extensions_.getAllInternalIds(that_.GetContext(), that_.payload_, Plugins::Convert(resourceType)));
+      that_.ForwardAnswers(target);
+    }
+
+
+
+    // From the "ILookupResources" interface
+    void LookupIdentifier(std::list<int64_t>& result,
+                          ResourceType level,
+                          const DicomTag& tag,
+                          Compatibility::IdentifierConstraintType type,
+                          const std::string& value) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.lookupIdentifier3 == NULL)
+      {
+        throw OrthancException(ErrorCode_DatabasePlugin,
+                               "The database plugin does not implement the mandatory LookupIdentifier3() extension");
+      }
+
+      OrthancPluginDicomTag tmp;
+      tmp.group = tag.GetGroup();
+      tmp.element = tag.GetElement();
+      tmp.value = value.c_str();
+
+      that_.ResetAnswers();
+      CheckSuccess(that_.extensions_.lookupIdentifier3(that_.GetContext(), that_.payload_, Plugins::Convert(level),
+                                                 &tmp, Compatibility::Convert(type)));
+      that_.ForwardAnswers(result);
+    }
+
+    
+
+    /**
+     * Implementation of "ITransaction"
+     **/
+    
+    virtual void ApplyLookupResources(std::list<std::string>& resourcesId,
+                                      std::list<std::string>* instancesId,
+                                      const std::vector<DatabaseConstraint>& lookup,
+                                      ResourceType queryLevel,
+                                      size_t limit) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.lookupResources == NULL)
+      {
+        // Fallback to compatibility mode
+        ILookupResources::Apply
+          (*this, *this, resourcesId, instancesId, lookup, queryLevel, limit);
+      }
+      else
+      {
+        std::vector<OrthancPluginDatabaseConstraint> constraints;
+        std::vector< std::vector<const char*> > constraintsValues;
+
+        constraints.resize(lookup.size());
+        constraintsValues.resize(lookup.size());
+
+        for (size_t i = 0; i < lookup.size(); i++)
+        {
+          lookup[i].EncodeForPlugins(constraints[i], constraintsValues[i]);
+        }
+
+        that_.ResetAnswers();
+        that_.answerMatchingResources_ = &resourcesId;
+        that_.answerMatchingInstances_ = instancesId;
+      
+        CheckSuccess(that_.extensions_.lookupResources(that_.GetContext(), that_.payload_, lookup.size(),
+                                                 (lookup.empty() ? NULL : &constraints[0]),
+                                                 Plugins::Convert(queryLevel),
+                                                 limit, (instancesId == NULL ? 0 : 1)));
+      }
+    }
+
+
+    bool CreateInstance(IDatabaseWrapper::CreateInstanceResult& result,
+                        int64_t& instanceId,
+                        const std::string& patient,
+                        const std::string& study,
+                        const std::string& series,
+                        const std::string& instance) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.createInstance == NULL)
+      {
+        // Fallback to compatibility mode
+        return ICreateInstance::Apply
+          (*this, result, instanceId, patient, study, series, instance);
+      }
+      else
+      {
+        OrthancPluginCreateInstanceResult output;
+        memset(&output, 0, sizeof(output));
+
+        CheckSuccess(that_.extensions_.createInstance(&output, that_.payload_, patient.c_str(),
+                                                study.c_str(), series.c_str(), instance.c_str()));
+
+        instanceId = output.instanceId;
+      
+        if (output.isNewInstance)
+        {
+          result.isNewPatient_ = output.isNewPatient;
+          result.isNewStudy_ = output.isNewStudy;
+          result.isNewSeries_ = output.isNewSeries;
+          result.patientId_ = output.patientId;
+          result.studyId_ = output.studyId;
+          result.seriesId_ = output.seriesId;
+          return true;
+        }
+        else
+        {
+          return false;
+        }
+      }
+    }
+
+
+    virtual int64_t CreateResource(const std::string& publicId,
+                                   ResourceType type) ORTHANC_OVERRIDE
+    {
+      int64_t id;
+      CheckSuccess(that_.backend_.createResource(&id, that_.payload_, publicId.c_str(), Plugins::Convert(type)));
+      return id;
+    }
+    
+
+    virtual void AddAttachment(int64_t id,
+                               const FileInfo& attachment) ORTHANC_OVERRIDE
+    {
+      OrthancPluginAttachment tmp;
+      tmp.uuid = attachment.GetUuid().c_str();
+      tmp.contentType = static_cast<int32_t>(attachment.GetContentType());
+      tmp.uncompressedSize = attachment.GetUncompressedSize();
+      tmp.uncompressedHash = attachment.GetUncompressedMD5().c_str();
+      tmp.compressionType = static_cast<int32_t>(attachment.GetCompressionType());
+      tmp.compressedSize = attachment.GetCompressedSize();
+      tmp.compressedHash = attachment.GetCompressedMD5().c_str();
+
+      CheckSuccess(that_.backend_.addAttachment(that_.payload_, id, &tmp));
+    }
+
+
+    virtual void AttachChild(int64_t parent,
+                             int64_t child) ORTHANC_OVERRIDE
+    {
+      CheckSuccess(that_.backend_.attachChild(that_.payload_, parent, child));
+    }
+
+
+    virtual void ClearChanges() ORTHANC_OVERRIDE
+    {
+      CheckSuccess(that_.backend_.clearChanges(that_.payload_));
+    }
+
+
+    virtual void ClearExportedResources() ORTHANC_OVERRIDE
+    {
+      CheckSuccess(that_.backend_.clearExportedResources(that_.payload_));
+    }
+
+
+    virtual void ClearMainDicomTags(int64_t id) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.clearMainDicomTags == NULL)
+      {
+        throw OrthancException(ErrorCode_DatabasePlugin,
+                               "Your custom index plugin does not implement the mandatory ClearMainDicomTags() extension");
+      }
+
+      CheckSuccess(that_.extensions_.clearMainDicomTags(that_.payload_, id));
+    }
+
+
+    virtual void DeleteAttachment(int64_t id,
+                                  FileContentType attachment) ORTHANC_OVERRIDE
+    {
+      CheckSuccess(that_.backend_.deleteAttachment(that_.payload_, id, static_cast<int32_t>(attachment)));
+    }
+
+
+    virtual void DeleteMetadata(int64_t id,
+                                MetadataType type) ORTHANC_OVERRIDE
+    {
+      CheckSuccess(that_.backend_.deleteMetadata(that_.payload_, id, static_cast<int32_t>(type)));
+    }
+
+
+    virtual void DeleteResource(int64_t id) ORTHANC_OVERRIDE
+    {
+      CheckSuccess(that_.backend_.deleteResource(that_.payload_, id));
+    }
+
+
+    virtual void GetAllMetadata(std::map<MetadataType, std::string>& target,
+                                int64_t id) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.getAllMetadata == NULL)
+      {
+        // Fallback implementation if extension is missing
+        target.clear();
+
+        that_.ResetAnswers();
+        CheckSuccess(that_.backend_.listAvailableMetadata(that_.GetContext(), that_.payload_, id));
+
+        if (that_.type_ != _OrthancPluginDatabaseAnswerType_None &&
+            that_.type_ != _OrthancPluginDatabaseAnswerType_Int32)
+        {
+          throw OrthancException(ErrorCode_DatabasePlugin);
+        }
+
+        target.clear();
+
+        if (that_.type_ == _OrthancPluginDatabaseAnswerType_Int32)
+        {
+          for (std::list<int32_t>::const_iterator 
+                 it = that_.answerInt32_.begin(); it != that_.answerInt32_.end(); ++it)
+          {
+            MetadataType type = static_cast<MetadataType>(*it);
+
+            std::string value;
+            if (LookupMetadata(value, id, type))
+            {
+              target[type] = value;
+            }
+          }
+        }
+      }
+      else
+      {
+        that_.ResetAnswers();
+
+        that_.answerMetadata_ = &target;
+        target.clear();
+      
+        CheckSuccess(that_.extensions_.getAllMetadata(that_.GetContext(), that_.payload_, id));
+
+        if (that_.type_ != _OrthancPluginDatabaseAnswerType_None &&
+            that_.type_ != _OrthancPluginDatabaseAnswerType_Metadata)
+        {
+          throw OrthancException(ErrorCode_DatabasePlugin);
+        }
+      }
+    }
+
+
+    virtual void GetAllPublicIds(std::list<std::string>& target,
+                                 ResourceType resourceType) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      CheckSuccess(that_.backend_.getAllPublicIds(that_.GetContext(), that_.payload_, Plugins::Convert(resourceType)));
+      that_.ForwardAnswers(target);
+    }
+
+
+    virtual void GetAllPublicIds(std::list<std::string>& target,
+                                 ResourceType resourceType,
+                                 size_t since,
+                                 size_t limit) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.getAllPublicIdsWithLimit != NULL)
+      {
+        // This extension is available since Orthanc 0.9.4
+        that_.ResetAnswers();
+        CheckSuccess(that_.extensions_.getAllPublicIdsWithLimit
+                     (that_.GetContext(), that_.payload_, Plugins::Convert(resourceType), since, limit));
+        that_.ForwardAnswers(target);
+      }
+      else
+      {
+        // The extension is not available in the database plugin, use a
+        // fallback implementation
+        target.clear();
+
+        if (limit == 0)
+        {
+          return;
+        }
+
+        std::list<std::string> tmp;
+        GetAllPublicIds(tmp, resourceType);
+    
+        if (tmp.size() <= since)
+        {
+          // Not enough results => empty answer
+          return;
+        }
+
+        std::list<std::string>::iterator current = tmp.begin();
+        std::advance(current, since);
+
+        while (limit > 0 && current != tmp.end())
+        {
+          target.push_back(*current);
+          --limit;
+          ++current;
+        }
+      }
+    }
+
+
+    virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/,
+                            bool& done /*out*/,
+                            int64_t since,
+                            uint32_t maxResults) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      that_.answerChanges_ = &target;
+      that_.answerDone_ = &done;
+      done = false;
+
+      CheckSuccess(that_.backend_.getChanges(that_.GetContext(), that_.payload_, since, maxResults));
+    }
+
+
+    virtual void GetChildrenInternalId(std::list<int64_t>& target,
+                                       int64_t id) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      CheckSuccess(that_.backend_.getChildrenInternalId(that_.GetContext(), that_.payload_, id));
+      that_.ForwardAnswers(target);
+    }
+
+
+    virtual void GetChildrenMetadata(std::list<std::string>& target,
+                                     int64_t resourceId,
+                                     MetadataType metadata) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.getChildrenMetadata == NULL)
+      {
+        IGetChildrenMetadata::Apply(*this, target, resourceId, metadata);
+      }
+      else
+      {
+        that_.ResetAnswers();
+        CheckSuccess(that_.extensions_.getChildrenMetadata
+                     (that_.GetContext(), that_.payload_, resourceId, static_cast<int32_t>(metadata)));
+        that_.ForwardAnswers(target);
+      }
+    }
+
+
+    virtual void GetChildrenPublicId(std::list<std::string>& target,
+                                     int64_t id) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      CheckSuccess(that_.backend_.getChildrenPublicId(that_.GetContext(), that_.payload_, id));
+      that_.ForwardAnswers(target);
+    }
+
+
+    virtual void GetExportedResources(std::list<ExportedResource>& target /*out*/,
+                                      bool& done /*out*/,
+                                      int64_t since,
+                                      uint32_t maxResults) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      that_.answerExportedResources_ = &target;
+      that_.answerDone_ = &done;
+      done = false;
+
+      CheckSuccess(that_.backend_.getExportedResources(that_.GetContext(), that_.payload_, since, maxResults));
+    }
+
+
+    virtual void GetLastChange(std::list<ServerIndexChange>& target /*out*/) ORTHANC_OVERRIDE
+    {
+      that_.answerDoneIgnored_ = false;
+
+      that_.ResetAnswers();
+      that_.answerChanges_ = &target;
+      that_.answerDone_ = &that_.answerDoneIgnored_;
+
+      CheckSuccess(that_.backend_.getLastChange(that_.GetContext(), that_.payload_));
+    }
+
+
+    int64_t GetLastChangeIndex() ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.getLastChangeIndex == NULL)
+      {
+        // This was the default behavior in Orthanc <= 1.5.1
+        // https://groups.google.com/d/msg/orthanc-users/QhzB6vxYeZ0/YxabgqpfBAAJ
+        return 0;
+      }
+      else
+      {
+        int64_t result = 0;
+        CheckSuccess(that_.extensions_.getLastChangeIndex(&result, that_.payload_));
+        return result;
+      }
+    }
+
+  
+    virtual void GetLastExportedResource(std::list<ExportedResource>& target /*out*/) ORTHANC_OVERRIDE
+    {
+      that_.answerDoneIgnored_ = false;
+
+      that_.ResetAnswers();
+      that_.answerExportedResources_ = &target;
+      that_.answerDone_ = &that_.answerDoneIgnored_;
+
+      CheckSuccess(that_.backend_.getLastExportedResource(that_.GetContext(), that_.payload_));
+    }
+
+
+    virtual void GetMainDicomTags(DicomMap& map,
+                                  int64_t id) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      that_.answerDicomMap_ = &map;
+
+      CheckSuccess(that_.backend_.getMainDicomTags(that_.GetContext(), that_.payload_, id));
+    }
+
+
+    virtual std::string GetPublicId(int64_t resourceId) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      std::string s;
+
+      CheckSuccess(that_.backend_.getPublicId(that_.GetContext(), that_.payload_, resourceId));
+
+      if (!that_.ForwardSingleAnswer(s))
+      {
+        throw OrthancException(ErrorCode_DatabasePlugin);
+      }
+
+      return s;
+    }
+
+
+    virtual uint64_t GetResourceCount(ResourceType resourceType) ORTHANC_OVERRIDE
+    {
+      uint64_t count;
+      CheckSuccess(that_.backend_.getResourceCount(&count, that_.payload_, Plugins::Convert(resourceType)));
+      return count;
+    }
+
+
+    virtual ResourceType GetResourceType(int64_t resourceId) ORTHANC_OVERRIDE
+    {
+      OrthancPluginResourceType type;
+      CheckSuccess(that_.backend_.getResourceType(&type, that_.payload_, resourceId));
+      return Plugins::Convert(type);
+    }
+
+
+    virtual uint64_t GetTotalCompressedSize() ORTHANC_OVERRIDE
+    {
+      uint64_t size;
+      CheckSuccess(that_.backend_.getTotalCompressedSize(&size, that_.payload_));
+      return size;
+    }
+
+    
+    virtual uint64_t GetTotalUncompressedSize() ORTHANC_OVERRIDE
+    {
+      uint64_t size;
+      CheckSuccess(that_.backend_.getTotalUncompressedSize(&size, that_.payload_));
+      return size;
+    }
+    
+
+    virtual bool IsDiskSizeAbove(uint64_t threshold) ORTHANC_OVERRIDE
+    {
+      if (that_.fastGetTotalSize_)
+      {
+        return GetTotalCompressedSize() > threshold;
+      }
+      else
+      {
+        assert(GetTotalCompressedSize() == that_.currentDiskSize_);
+        return that_.currentDiskSize_ > threshold;
+      }      
+    }
+
+
+    virtual bool IsExistingResource(int64_t internalId) ORTHANC_OVERRIDE
+    {
+      int32_t existing;
+      CheckSuccess(that_.backend_.isExistingResource(&existing, that_.payload_, internalId));
+      return (existing != 0);
+    }
+
+
+    virtual bool IsProtectedPatient(int64_t internalId) ORTHANC_OVERRIDE
+    {
+      int32_t isProtected;
+      CheckSuccess(that_.backend_.isProtectedPatient(&isProtected, that_.payload_, internalId));
+      return (isProtected != 0);
+    }
+
+
+    virtual void ListAvailableAttachments(std::set<FileContentType>& target,
+                                          int64_t id) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+
+      CheckSuccess(that_.backend_.listAvailableAttachments(that_.GetContext(), that_.payload_, id));
+
+      if (that_.type_ != _OrthancPluginDatabaseAnswerType_None &&
+          that_.type_ != _OrthancPluginDatabaseAnswerType_Int32)
+      {
+        throw OrthancException(ErrorCode_DatabasePlugin);
+      }
+
+      target.clear();
+
+      if (that_.type_ == _OrthancPluginDatabaseAnswerType_Int32)
+      {
+        for (std::list<int32_t>::const_iterator 
+               it = that_.answerInt32_.begin(); it != that_.answerInt32_.end(); ++it)
+        {
+          target.insert(static_cast<FileContentType>(*it));
+        }
+      }
+    }
+
+
+    virtual void LogChange(int64_t internalId,
+                           const ServerIndexChange& change) ORTHANC_OVERRIDE
+    {
+      OrthancPluginChange tmp;
+      tmp.seq = change.GetSeq();
+      tmp.changeType = static_cast<int32_t>(change.GetChangeType());
+      tmp.resourceType = Plugins::Convert(change.GetResourceType());
+      tmp.publicId = change.GetPublicId().c_str();
+      tmp.date = change.GetDate().c_str();
+
+      CheckSuccess(that_.backend_.logChange(that_.payload_, &tmp));
+    }
+
+
+    virtual void LogExportedResource(const ExportedResource& resource) ORTHANC_OVERRIDE
+    {
+      OrthancPluginExportedResource tmp;
+      tmp.seq = resource.GetSeq();
+      tmp.resourceType = Plugins::Convert(resource.GetResourceType());
+      tmp.publicId = resource.GetPublicId().c_str();
+      tmp.modality = resource.GetModality().c_str();
+      tmp.date = resource.GetDate().c_str();
+      tmp.patientId = resource.GetPatientId().c_str();
+      tmp.studyInstanceUid = resource.GetStudyInstanceUid().c_str();
+      tmp.seriesInstanceUid = resource.GetSeriesInstanceUid().c_str();
+      tmp.sopInstanceUid = resource.GetSopInstanceUid().c_str();
+
+      CheckSuccess(that_.backend_.logExportedResource(that_.payload_, &tmp));
+    }
+
+    
+    virtual bool LookupAttachment(FileInfo& attachment,
+                                  int64_t id,
+                                  FileContentType contentType) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+
+      CheckSuccess(that_.backend_.lookupAttachment
+                   (that_.GetContext(), that_.payload_, id, static_cast<int32_t>(contentType)));
+
+      if (that_.type_ == _OrthancPluginDatabaseAnswerType_None)
+      {
+        return false;
+      }
+      else if (that_.type_ == _OrthancPluginDatabaseAnswerType_Attachment &&
+               that_.answerAttachments_.size() == 1)
+      {
+        attachment = that_.answerAttachments_.front();
+        return true; 
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_DatabasePlugin);
+      }
+    }
+
+
+    virtual bool LookupGlobalProperty(std::string& target,
+                                      GlobalProperty property) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+
+      CheckSuccess(that_.backend_.lookupGlobalProperty
+                   (that_.GetContext(), that_.payload_, static_cast<int32_t>(property)));
+
+      return that_.ForwardSingleAnswer(target);
+    }
+
+
+    virtual void LookupIdentifierRange(std::list<int64_t>& result,
+                                       ResourceType level,
+                                       const DicomTag& tag,
+                                       const std::string& start,
+                                       const std::string& end) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.lookupIdentifierRange == NULL)
+      {
+        // Default implementation, for plugins using Orthanc SDK <= 1.3.2
+
+        LookupIdentifier(result, level, tag, Compatibility::IdentifierConstraintType_GreaterOrEqual, start);
+
+        std::list<int64_t> b;
+        LookupIdentifier(result, level, tag, Compatibility::IdentifierConstraintType_SmallerOrEqual, end);
+
+        result.splice(result.end(), b);
+      }
+      else
+      {
+        that_.ResetAnswers();
+        CheckSuccess(that_.extensions_.lookupIdentifierRange(that_.GetContext(), that_.payload_, Plugins::Convert(level),
+                                                       tag.GetGroup(), tag.GetElement(),
+                                                       start.c_str(), end.c_str()));
+        that_.ForwardAnswers(result);
+      }
+    }
+
+
+    virtual bool LookupMetadata(std::string& target,
+                                int64_t id,
+                                MetadataType type) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      CheckSuccess(that_.backend_.lookupMetadata(that_.GetContext(), that_.payload_, id, static_cast<int32_t>(type)));
+      return that_.ForwardSingleAnswer(target);
+    }
+
+
+    virtual bool LookupParent(int64_t& parentId,
+                              int64_t resourceId) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      CheckSuccess(that_.backend_.lookupParent(that_.GetContext(), that_.payload_, resourceId));
+      return that_.ForwardSingleAnswer(parentId);
+    }
+
+
+    virtual bool LookupResource(int64_t& id,
+                                ResourceType& type,
+                                const std::string& publicId) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+
+      CheckSuccess(that_.backend_.lookupResource(that_.GetContext(), that_.payload_, publicId.c_str()));
+
+      if (that_.type_ == _OrthancPluginDatabaseAnswerType_None)
+      {
+        return false;
+      }
+      else if (that_.type_ == _OrthancPluginDatabaseAnswerType_Resource &&
+               that_.answerResources_.size() == 1)
+      {
+        id = that_.answerResources_.front().first;
+        type = that_.answerResources_.front().second;
+        return true; 
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_DatabasePlugin);
+      }
+    }
+
+
+    virtual bool LookupResourceAndParent(int64_t& id,
+                                         ResourceType& type,
+                                         std::string& parentPublicId,
+                                         const std::string& publicId) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.lookupResourceAndParent == NULL)
+      {
+        return ILookupResourceAndParent::Apply(*this, id, type, parentPublicId, publicId);
+      }
+      else
+      {
+        std::list<std::string> parent;
+
+        uint8_t isExisting;
+        OrthancPluginResourceType pluginType = OrthancPluginResourceType_Patient;
+      
+        that_.ResetAnswers();
+        CheckSuccess(that_.extensions_.lookupResourceAndParent
+                     (that_.GetContext(), &isExisting, &id, &pluginType, that_.payload_, publicId.c_str()));
+        that_.ForwardAnswers(parent);
+
+        if (isExisting)
+        {
+          type = Plugins::Convert(pluginType);
+
+          if (parent.empty())
+          {
+            if (type != ResourceType_Patient)
+            {
+              throw OrthancException(ErrorCode_DatabasePlugin);
+            }
+          }
+          else if (parent.size() == 1)
+          {
+            if ((type != ResourceType_Study &&
+                 type != ResourceType_Series &&
+                 type != ResourceType_Instance) ||
+                parent.front().empty())
+            {
+              throw OrthancException(ErrorCode_DatabasePlugin);
+            }
+
+            parentPublicId = parent.front();
+          }
+          else
+          {
+            throw OrthancException(ErrorCode_DatabasePlugin);
+          }
+
+          return true;
+        }
+        else
+        {
+          return false;
+        }
+      }
+    }
+
+
+    virtual bool SelectPatientToRecycle(int64_t& internalId) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      CheckSuccess(that_.backend_.selectPatientToRecycle(that_.GetContext(), that_.payload_));
+      return that_.ForwardSingleAnswer(internalId);
+    }
+
+
+    virtual bool SelectPatientToRecycle(int64_t& internalId,
+                                        int64_t patientIdToAvoid) ORTHANC_OVERRIDE
+    {
+      that_.ResetAnswers();
+      CheckSuccess(that_.backend_.selectPatientToRecycle2(that_.GetContext(), that_.payload_, patientIdToAvoid));
+      return that_.ForwardSingleAnswer(internalId);
+    }
+
+
+    virtual void SetGlobalProperty(GlobalProperty property,
+                                   const std::string& value) ORTHANC_OVERRIDE
+    {
+      CheckSuccess(that_.backend_.setGlobalProperty
+                   (that_.payload_, static_cast<int32_t>(property), value.c_str()));
+    }
+
+
+    virtual void SetIdentifierTag(int64_t id,
+                                  const DicomTag& tag,
+                                  const std::string& value) ORTHANC_OVERRIDE
+    {
+      OrthancPluginDicomTag tmp;
+      tmp.group = tag.GetGroup();
+      tmp.element = tag.GetElement();
+      tmp.value = value.c_str();
+
+      CheckSuccess(that_.backend_.setIdentifierTag(that_.payload_, id, &tmp));
+    }
+
+
+    virtual void SetMainDicomTag(int64_t id,
+                                 const DicomTag& tag,
+                                 const std::string& value) ORTHANC_OVERRIDE
+    {
+      OrthancPluginDicomTag tmp;
+      tmp.group = tag.GetGroup();
+      tmp.element = tag.GetElement();
+      tmp.value = value.c_str();
+
+      CheckSuccess(that_.backend_.setMainDicomTag(that_.payload_, id, &tmp));
+    }
+
+
+    virtual void SetMetadata(int64_t id,
+                             MetadataType type,
+                             const std::string& value) ORTHANC_OVERRIDE
+    {
+      CheckSuccess(that_.backend_.setMetadata
+                   (that_.payload_, id, static_cast<int32_t>(type), value.c_str()));
+    }
+
+
+    virtual void SetProtectedPatient(int64_t internalId, 
+                                     bool isProtected) ORTHANC_OVERRIDE
+    {
+      CheckSuccess(that_.backend_.setProtectedPatient(that_.payload_, internalId, isProtected));
+    }
+
+
+    virtual void SetResourcesContent(const Orthanc::ResourcesContent& content) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.setResourcesContent == NULL)
+      {
+        ISetResourcesContent::Apply(*this, content);
+      }
+      else
+      {
+        std::vector<OrthancPluginResourcesContentTags> identifierTags;
+        std::vector<OrthancPluginResourcesContentTags> mainDicomTags;
+        std::vector<OrthancPluginResourcesContentMetadata> metadata;
+
+        identifierTags.reserve(content.GetListTags().size());
+        mainDicomTags.reserve(content.GetListTags().size());
+        metadata.reserve(content.GetListMetadata().size());
+
+        for (ResourcesContent::ListTags::const_iterator
+               it = content.GetListTags().begin(); it != content.GetListTags().end(); ++it)
+        {
+          OrthancPluginResourcesContentTags tmp;
+          tmp.resource = it->resourceId_;
+          tmp.group = it->tag_.GetGroup();
+          tmp.element = it->tag_.GetElement();
+          tmp.value = it->value_.c_str();
+
+          if (it->isIdentifier_)
+          {
+            identifierTags.push_back(tmp);
+          }
+          else
+          {
+            mainDicomTags.push_back(tmp);
+          }
+        }
+
+        for (ResourcesContent::ListMetadata::const_iterator
+               it = content.GetListMetadata().begin(); it != content.GetListMetadata().end(); ++it)
+        {
+          OrthancPluginResourcesContentMetadata tmp;
+          tmp.resource = it->resourceId_;
+          tmp.metadata = it->metadata_;
+          tmp.value = it->value_.c_str();
+          metadata.push_back(tmp);
+        }
+
+        assert(identifierTags.size() + mainDicomTags.size() == content.GetListTags().size() &&
+               metadata.size() == content.GetListMetadata().size());
+       
+        CheckSuccess(that_.extensions_.setResourcesContent(
+                       that_.payload_,
+                       identifierTags.size(),
+                       (identifierTags.empty() ? NULL : &identifierTags[0]),
+                       mainDicomTags.size(),
+                       (mainDicomTags.empty() ? NULL : &mainDicomTags[0]),
+                       metadata.size(),
+                       (metadata.empty() ? NULL : &metadata[0])));
+      }
+    }
+
+
+    virtual void TagMostRecentPatient(int64_t patient) ORTHANC_OVERRIDE
+    {
+      if (that_.extensions_.tagMostRecentPatient != NULL)
+      {
+        CheckSuccess(that_.extensions_.tagMostRecentPatient(that_.payload_, patient));
+      }
+    }
+
   };
 
 
@@ -319,36 +1191,11 @@
   }
 
 
-  namespace
-  {
-    class VoidListener : public IDatabaseListener
-    {
-    public:
-      virtual void SignalRemainingAncestor(ResourceType parentType,
-                                           const std::string& publicId)
-      {
-        throw OrthancException(ErrorCode_InternalError);  // Should be read-only transaction
-      }
-      
-      virtual void SignalAttachmentDeleted(const FileInfo& info)
-      {
-        throw OrthancException(ErrorCode_InternalError);  // Should be read-only transaction
-      }
-
-      virtual void SignalResourceDeleted(ResourceType type,
-                                         const std::string& publicId)
-      {
-        throw OrthancException(ErrorCode_InternalError);  // Should be read-only transaction
-      }      
-    };
-  }
-
-
   void OrthancPluginDatabase::Open()
   {
     CheckSuccess(backend_.open(payload_));
 
-    VoidListener listener;
+    VoidDatabaseListener listener;
     
     {
       Transaction transaction(*this, listener);
@@ -356,7 +1203,7 @@
 
       std::string tmp;
       fastGetTotalSize_ =
-        (LookupGlobalProperty(tmp, GlobalProperty_GetTotalSizeIsFast) &&
+        (transaction.LookupGlobalProperty(tmp, GlobalProperty_GetTotalSizeIsFast) &&
          tmp == "1");
       
       if (fastGetTotalSize_)
@@ -367,7 +1214,7 @@
       {
         // This is the case of database plugins using Orthanc SDK <= 1.5.2
         LOG(WARNING) << "Your database index plugin is not compatible with multiple Orthanc writers";
-        currentDiskSize_ = GetTotalCompressedSize();
+        currentDiskSize_ = transaction.GetTotalCompressedSize();
       }
 
       transaction.Commit(0);
@@ -375,556 +1222,6 @@
   }
 
 
-  void OrthancPluginDatabase::AddAttachment(int64_t id,
-                                            const FileInfo& attachment)
-  {
-    OrthancPluginAttachment tmp;
-    tmp.uuid = attachment.GetUuid().c_str();
-    tmp.contentType = static_cast<int32_t>(attachment.GetContentType());
-    tmp.uncompressedSize = attachment.GetUncompressedSize();
-    tmp.uncompressedHash = attachment.GetUncompressedMD5().c_str();
-    tmp.compressionType = static_cast<int32_t>(attachment.GetCompressionType());
-    tmp.compressedSize = attachment.GetCompressedSize();
-    tmp.compressedHash = attachment.GetCompressedMD5().c_str();
-
-    CheckSuccess(backend_.addAttachment(payload_, id, &tmp));
-  }
-
-
-  void OrthancPluginDatabase::AttachChild(int64_t parent,
-                                          int64_t child)
-  {
-    CheckSuccess(backend_.attachChild(payload_, parent, child));
-  }
-
-
-  void OrthancPluginDatabase::ClearChanges()
-  {
-    CheckSuccess(backend_.clearChanges(payload_));
-  }
-
-
-  void OrthancPluginDatabase::ClearExportedResources()
-  {
-    CheckSuccess(backend_.clearExportedResources(payload_));
-  }
-
-
-  int64_t OrthancPluginDatabase::CreateResource(const std::string& publicId,
-                                                ResourceType type)
-  {
-    int64_t id;
-    CheckSuccess(backend_.createResource(&id, payload_, publicId.c_str(), Plugins::Convert(type)));
-    return id;
-  }
-
-
-  void OrthancPluginDatabase::DeleteAttachment(int64_t id,
-                                               FileContentType attachment)
-  {
-    CheckSuccess(backend_.deleteAttachment(payload_, id, static_cast<int32_t>(attachment)));
-  }
-
-
-  void OrthancPluginDatabase::DeleteMetadata(int64_t id,
-                                             MetadataType type)
-  {
-    CheckSuccess(backend_.deleteMetadata(payload_, id, static_cast<int32_t>(type)));
-  }
-
-
-  void OrthancPluginDatabase::DeleteResource(int64_t id)
-  {
-    CheckSuccess(backend_.deleteResource(payload_, id));
-  }
-
-
-  void OrthancPluginDatabase::GetAllMetadata(std::map<MetadataType, std::string>& target,
-                                             int64_t id)
-  {
-    if (extensions_.getAllMetadata == NULL)
-    {
-      // Fallback implementation if extension is missing
-      target.clear();
-
-      ResetAnswers();
-      CheckSuccess(backend_.listAvailableMetadata(GetContext(), payload_, id));
-
-      if (type_ != _OrthancPluginDatabaseAnswerType_None &&
-          type_ != _OrthancPluginDatabaseAnswerType_Int32)
-      {
-        throw OrthancException(ErrorCode_DatabasePlugin);
-      }
-
-      target.clear();
-
-      if (type_ == _OrthancPluginDatabaseAnswerType_Int32)
-      {
-        for (std::list<int32_t>::const_iterator 
-               it = answerInt32_.begin(); it != answerInt32_.end(); ++it)
-        {
-          MetadataType type = static_cast<MetadataType>(*it);
-
-          std::string value;
-          if (LookupMetadata(value, id, type))
-          {
-            target[type] = value;
-          }
-        }
-      }
-    }
-    else
-    {
-      ResetAnswers();
-
-      answerMetadata_ = &target;
-      target.clear();
-      
-      CheckSuccess(extensions_.getAllMetadata(GetContext(), payload_, id));
-
-      if (type_ != _OrthancPluginDatabaseAnswerType_None &&
-          type_ != _OrthancPluginDatabaseAnswerType_Metadata)
-      {
-        throw OrthancException(ErrorCode_DatabasePlugin);
-      }
-    }
-  }
-
-
-  void OrthancPluginDatabase::GetAllInternalIds(std::list<int64_t>& target,
-                                                ResourceType resourceType)
-  {
-    if (extensions_.getAllInternalIds == NULL)
-    {
-      throw OrthancException(ErrorCode_DatabasePlugin,
-                             "The database plugin does not implement the mandatory GetAllInternalIds() extension");
-    }
-
-    ResetAnswers();
-    CheckSuccess(extensions_.getAllInternalIds(GetContext(), payload_, Plugins::Convert(resourceType)));
-    ForwardAnswers(target);
-  }
-
-
-  void OrthancPluginDatabase::GetAllPublicIds(std::list<std::string>& target,
-                                              ResourceType resourceType)
-  {
-    ResetAnswers();
-    CheckSuccess(backend_.getAllPublicIds(GetContext(), payload_, Plugins::Convert(resourceType)));
-    ForwardAnswers(target);
-  }
-
-
-  void OrthancPluginDatabase::GetAllPublicIds(std::list<std::string>& target,
-                                              ResourceType resourceType,
-                                              size_t since,
-                                              size_t limit)
-  {
-    if (extensions_.getAllPublicIdsWithLimit != NULL)
-    {
-      // This extension is available since Orthanc 0.9.4
-      ResetAnswers();
-      CheckSuccess(extensions_.getAllPublicIdsWithLimit
-                   (GetContext(), payload_, Plugins::Convert(resourceType), since, limit));
-      ForwardAnswers(target);
-    }
-    else
-    {
-      // The extension is not available in the database plugin, use a
-      // fallback implementation
-      target.clear();
-
-      if (limit == 0)
-      {
-        return;
-      }
-
-      std::list<std::string> tmp;
-      GetAllPublicIds(tmp, resourceType);
-    
-      if (tmp.size() <= since)
-      {
-        // Not enough results => empty answer
-        return;
-      }
-
-      std::list<std::string>::iterator current = tmp.begin();
-      std::advance(current, since);
-
-      while (limit > 0 && current != tmp.end())
-      {
-        target.push_back(*current);
-        --limit;
-        ++current;
-      }
-    }
-  }
-
-
-
-  void OrthancPluginDatabase::GetChanges(std::list<ServerIndexChange>& target /*out*/,
-                                         bool& done /*out*/,
-                                         int64_t since,
-                                         uint32_t maxResults)
-  {
-    ResetAnswers();
-    answerChanges_ = &target;
-    answerDone_ = &done;
-    done = false;
-
-    CheckSuccess(backend_.getChanges(GetContext(), payload_, since, maxResults));
-  }
-
-
-  void OrthancPluginDatabase::GetChildrenInternalId(std::list<int64_t>& target,
-                                                    int64_t id)
-  {
-    ResetAnswers();
-    CheckSuccess(backend_.getChildrenInternalId(GetContext(), payload_, id));
-    ForwardAnswers(target);
-  }
-
-
-  void OrthancPluginDatabase::GetChildrenPublicId(std::list<std::string>& target,
-                                                  int64_t id)
-  {
-    ResetAnswers();
-    CheckSuccess(backend_.getChildrenPublicId(GetContext(), payload_, id));
-    ForwardAnswers(target);
-  }
-
-
-  void OrthancPluginDatabase::GetExportedResources(std::list<ExportedResource>& target /*out*/,
-                                                   bool& done /*out*/,
-                                                   int64_t since,
-                                                   uint32_t maxResults)
-  {
-    ResetAnswers();
-    answerExportedResources_ = &target;
-    answerDone_ = &done;
-    done = false;
-
-    CheckSuccess(backend_.getExportedResources(GetContext(), payload_, since, maxResults));
-  }
-
-
-  void OrthancPluginDatabase::GetLastChange(std::list<ServerIndexChange>& target /*out*/)
-  {
-    answerDoneIgnored_ = false;
-
-    ResetAnswers();
-    answerChanges_ = &target;
-    answerDone_ = &answerDoneIgnored_;
-
-    CheckSuccess(backend_.getLastChange(GetContext(), payload_));
-  }
-
-
-  void OrthancPluginDatabase::GetLastExportedResource(std::list<ExportedResource>& target /*out*/)
-  {
-    answerDoneIgnored_ = false;
-
-    ResetAnswers();
-    answerExportedResources_ = &target;
-    answerDone_ = &answerDoneIgnored_;
-
-    CheckSuccess(backend_.getLastExportedResource(GetContext(), payload_));
-  }
-
-
-  void OrthancPluginDatabase::GetMainDicomTags(DicomMap& map,
-                                               int64_t id)
-  {
-    ResetAnswers();
-    answerDicomMap_ = &map;
-
-    CheckSuccess(backend_.getMainDicomTags(GetContext(), payload_, id));
-  }
-
-
-  std::string OrthancPluginDatabase::GetPublicId(int64_t resourceId)
-  {
-    ResetAnswers();
-    std::string s;
-
-    CheckSuccess(backend_.getPublicId(GetContext(), payload_, resourceId));
-
-    if (!ForwardSingleAnswer(s))
-    {
-      throw OrthancException(ErrorCode_DatabasePlugin);
-    }
-
-    return s;
-  }
-
-
-  uint64_t OrthancPluginDatabase::GetResourceCount(ResourceType resourceType)
-  {
-    uint64_t count;
-    CheckSuccess(backend_.getResourceCount(&count, payload_, Plugins::Convert(resourceType)));
-    return count;
-  }
-
-
-  ResourceType OrthancPluginDatabase::GetResourceType(int64_t resourceId)
-  {
-    OrthancPluginResourceType type;
-    CheckSuccess(backend_.getResourceType(&type, payload_, resourceId));
-    return Plugins::Convert(type);
-  }
-
-
-  uint64_t OrthancPluginDatabase::GetTotalCompressedSize()
-  {
-    uint64_t size;
-    CheckSuccess(backend_.getTotalCompressedSize(&size, payload_));
-    return size;
-  }
-
-    
-  uint64_t OrthancPluginDatabase::GetTotalUncompressedSize()
-  {
-    uint64_t size;
-    CheckSuccess(backend_.getTotalUncompressedSize(&size, payload_));
-    return size;
-  }
-
-
-  bool OrthancPluginDatabase::IsExistingResource(int64_t internalId)
-  {
-    int32_t existing;
-    CheckSuccess(backend_.isExistingResource(&existing, payload_, internalId));
-    return (existing != 0);
-  }
-
-
-  bool OrthancPluginDatabase::IsProtectedPatient(int64_t internalId)
-  {
-    int32_t isProtected;
-    CheckSuccess(backend_.isProtectedPatient(&isProtected, payload_, internalId));
-    return (isProtected != 0);
-  }
-
-
-  void OrthancPluginDatabase::ListAvailableAttachments(std::set<FileContentType>& target,
-                                                       int64_t id)
-  {
-    ResetAnswers();
-
-    CheckSuccess(backend_.listAvailableAttachments(GetContext(), payload_, id));
-
-    if (type_ != _OrthancPluginDatabaseAnswerType_None &&
-        type_ != _OrthancPluginDatabaseAnswerType_Int32)
-    {
-      throw OrthancException(ErrorCode_DatabasePlugin);
-    }
-
-    target.clear();
-
-    if (type_ == _OrthancPluginDatabaseAnswerType_Int32)
-    {
-      for (std::list<int32_t>::const_iterator 
-             it = answerInt32_.begin(); it != answerInt32_.end(); ++it)
-      {
-        target.insert(static_cast<FileContentType>(*it));
-      }
-    }
-  }
-
-
-  void OrthancPluginDatabase::LogChange(int64_t internalId,
-                                        const ServerIndexChange& change)
-  {
-    OrthancPluginChange tmp;
-    tmp.seq = change.GetSeq();
-    tmp.changeType = static_cast<int32_t>(change.GetChangeType());
-    tmp.resourceType = Plugins::Convert(change.GetResourceType());
-    tmp.publicId = change.GetPublicId().c_str();
-    tmp.date = change.GetDate().c_str();
-
-    CheckSuccess(backend_.logChange(payload_, &tmp));
-  }
-
-
-  void OrthancPluginDatabase::LogExportedResource(const ExportedResource& resource)
-  {
-    OrthancPluginExportedResource tmp;
-    tmp.seq = resource.GetSeq();
-    tmp.resourceType = Plugins::Convert(resource.GetResourceType());
-    tmp.publicId = resource.GetPublicId().c_str();
-    tmp.modality = resource.GetModality().c_str();
-    tmp.date = resource.GetDate().c_str();
-    tmp.patientId = resource.GetPatientId().c_str();
-    tmp.studyInstanceUid = resource.GetStudyInstanceUid().c_str();
-    tmp.seriesInstanceUid = resource.GetSeriesInstanceUid().c_str();
-    tmp.sopInstanceUid = resource.GetSopInstanceUid().c_str();
-
-    CheckSuccess(backend_.logExportedResource(payload_, &tmp));
-  }
-
-    
-  bool OrthancPluginDatabase::LookupAttachment(FileInfo& attachment,
-                                               int64_t id,
-                                               FileContentType contentType)
-  {
-    ResetAnswers();
-
-    CheckSuccess(backend_.lookupAttachment
-                 (GetContext(), payload_, id, static_cast<int32_t>(contentType)));
-
-    if (type_ == _OrthancPluginDatabaseAnswerType_None)
-    {
-      return false;
-    }
-    else if (type_ == _OrthancPluginDatabaseAnswerType_Attachment &&
-             answerAttachments_.size() == 1)
-    {
-      attachment = answerAttachments_.front();
-      return true; 
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_DatabasePlugin);
-    }
-  }
-
-
-  bool OrthancPluginDatabase::LookupGlobalProperty(std::string& target,
-                                                   GlobalProperty property)
-  {
-    ResetAnswers();
-
-    CheckSuccess(backend_.lookupGlobalProperty
-                 (GetContext(), payload_, static_cast<int32_t>(property)));
-
-    return ForwardSingleAnswer(target);
-  }
-
-
-  bool OrthancPluginDatabase::LookupMetadata(std::string& target,
-                                             int64_t id,
-                                             MetadataType type)
-  {
-    ResetAnswers();
-    CheckSuccess(backend_.lookupMetadata(GetContext(), payload_, id, static_cast<int32_t>(type)));
-    return ForwardSingleAnswer(target);
-  }
-
-
-  bool OrthancPluginDatabase::LookupParent(int64_t& parentId,
-                                           int64_t resourceId)
-  {
-    ResetAnswers();
-    CheckSuccess(backend_.lookupParent(GetContext(), payload_, resourceId));
-    return ForwardSingleAnswer(parentId);
-  }
-
-
-  bool OrthancPluginDatabase::LookupResource(int64_t& id,
-                                             ResourceType& type,
-                                             const std::string& publicId)
-  {
-    ResetAnswers();
-
-    CheckSuccess(backend_.lookupResource(GetContext(), payload_, publicId.c_str()));
-
-    if (type_ == _OrthancPluginDatabaseAnswerType_None)
-    {
-      return false;
-    }
-    else if (type_ == _OrthancPluginDatabaseAnswerType_Resource &&
-             answerResources_.size() == 1)
-    {
-      id = answerResources_.front().first;
-      type = answerResources_.front().second;
-      return true; 
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_DatabasePlugin);
-    }
-  }
-
-
-  bool OrthancPluginDatabase::SelectPatientToRecycle(int64_t& internalId)
-  {
-    ResetAnswers();
-    CheckSuccess(backend_.selectPatientToRecycle(GetContext(), payload_));
-    return ForwardSingleAnswer(internalId);
-  }
-
-
-  bool OrthancPluginDatabase::SelectPatientToRecycle(int64_t& internalId,
-                                                     int64_t patientIdToAvoid)
-  {
-    ResetAnswers();
-    CheckSuccess(backend_.selectPatientToRecycle2(GetContext(), payload_, patientIdToAvoid));
-    return ForwardSingleAnswer(internalId);
-  }
-
-
-  void OrthancPluginDatabase::SetGlobalProperty(GlobalProperty property,
-                                                const std::string& value)
-  {
-    CheckSuccess(backend_.setGlobalProperty
-                 (payload_, static_cast<int32_t>(property), value.c_str()));
-  }
-
-
-  void OrthancPluginDatabase::ClearMainDicomTags(int64_t id)
-  {
-    if (extensions_.clearMainDicomTags == NULL)
-    {
-      throw OrthancException(ErrorCode_DatabasePlugin,
-                             "Your custom index plugin does not implement the mandatory ClearMainDicomTags() extension");
-    }
-
-    CheckSuccess(extensions_.clearMainDicomTags(payload_, id));
-  }
-
-
-  void OrthancPluginDatabase::SetMainDicomTag(int64_t id,
-                                              const DicomTag& tag,
-                                              const std::string& value)
-  {
-    OrthancPluginDicomTag tmp;
-    tmp.group = tag.GetGroup();
-    tmp.element = tag.GetElement();
-    tmp.value = value.c_str();
-
-    CheckSuccess(backend_.setMainDicomTag(payload_, id, &tmp));
-  }
-
-
-  void OrthancPluginDatabase::SetIdentifierTag(int64_t id,
-                                               const DicomTag& tag,
-                                               const std::string& value)
-  {
-    OrthancPluginDicomTag tmp;
-    tmp.group = tag.GetGroup();
-    tmp.element = tag.GetElement();
-    tmp.value = value.c_str();
-
-    CheckSuccess(backend_.setIdentifierTag(payload_, id, &tmp));
-  }
-
-
-  void OrthancPluginDatabase::SetMetadata(int64_t id,
-                                          MetadataType type,
-                                          const std::string& value)
-  {
-    CheckSuccess(backend_.setMetadata
-                 (payload_, id, static_cast<int32_t>(type), value.c_str()));
-  }
-
-
-  void OrthancPluginDatabase::SetProtectedPatient(int64_t internalId, 
-                                                  bool isProtected)
-  {
-    CheckSuccess(backend_.setProtectedPatient(payload_, internalId, isProtected));
-  }
-
-
   IDatabaseWrapper::ITransaction* OrthancPluginDatabase::StartTransaction(TransactionType type,
                                                                           IDatabaseListener& listener)
   {
@@ -990,7 +1287,7 @@
   void OrthancPluginDatabase::Upgrade(unsigned int targetVersion,
                                       IStorageArea& storageArea)
   {
-    VoidListener listener;
+    VoidDatabaseListener listener;
     
     if (extensions_.upgradeDatabase != NULL)
     {
@@ -1267,313 +1564,4 @@
                                boost::lexical_cast<std::string>(answer.type));
     }
   }
-
-    
-  bool OrthancPluginDatabase::IsDiskSizeAbove(uint64_t threshold)
-  {
-    if (fastGetTotalSize_)
-    {
-      return GetTotalCompressedSize() > threshold;
-    }
-    else
-    {
-      assert(GetTotalCompressedSize() == currentDiskSize_);
-      return currentDiskSize_ > threshold;
-    }      
-  }
-
-
-  void OrthancPluginDatabase::ApplyLookupResources(std::list<std::string>& resourcesId,
-                                                   std::list<std::string>* instancesId,
-                                                   const std::vector<DatabaseConstraint>& lookup,
-                                                   ResourceType queryLevel,
-                                                   size_t limit)
-  {
-    if (extensions_.lookupResources == NULL)
-    {
-      // Fallback to compatibility mode
-      ILookupResources::Apply
-        (*this, *this, resourcesId, instancesId, lookup, queryLevel, limit);
-    }
-    else
-    {
-      std::vector<OrthancPluginDatabaseConstraint> constraints;
-      std::vector< std::vector<const char*> > constraintsValues;
-
-      constraints.resize(lookup.size());
-      constraintsValues.resize(lookup.size());
-
-      for (size_t i = 0; i < lookup.size(); i++)
-      {
-        lookup[i].EncodeForPlugins(constraints[i], constraintsValues[i]);
-      }
-
-      ResetAnswers();
-      answerMatchingResources_ = &resourcesId;
-      answerMatchingInstances_ = instancesId;
-      
-      CheckSuccess(extensions_.lookupResources(GetContext(), payload_, lookup.size(),
-                                               (lookup.empty() ? NULL : &constraints[0]),
-                                               Plugins::Convert(queryLevel),
-                                               limit, (instancesId == NULL ? 0 : 1)));
-    }
-  }
-
-
-  bool OrthancPluginDatabase::CreateInstance(
-    IDatabaseWrapper::CreateInstanceResult& result,
-    int64_t& instanceId,
-    const std::string& patient,
-    const std::string& study,
-    const std::string& series,
-    const std::string& instance)
-  {
-    if (extensions_.createInstance == NULL)
-    {
-      // Fallback to compatibility mode
-      return ICreateInstance::Apply
-        (*this, result, instanceId, patient, study, series, instance);
-    }
-    else
-    {
-      OrthancPluginCreateInstanceResult output;
-      memset(&output, 0, sizeof(output));
-
-      CheckSuccess(extensions_.createInstance(&output, payload_, patient.c_str(),
-                                              study.c_str(), series.c_str(), instance.c_str()));
-
-      instanceId = output.instanceId;
-      
-      if (output.isNewInstance)
-      {
-        result.isNewPatient_ = output.isNewPatient;
-        result.isNewStudy_ = output.isNewStudy;
-        result.isNewSeries_ = output.isNewSeries;
-        result.patientId_ = output.patientId;
-        result.studyId_ = output.studyId;
-        result.seriesId_ = output.seriesId;
-        return true;
-      }
-      else
-      {
-        return false;
-      }
-    }
-  }
-
-
-  void OrthancPluginDatabase::LookupIdentifier(std::list<int64_t>& result,
-                                               ResourceType level,
-                                               const DicomTag& tag,
-                                               Compatibility::IdentifierConstraintType type,
-                                               const std::string& value)
-  {
-    if (extensions_.lookupIdentifier3 == NULL)
-    {
-      throw OrthancException(ErrorCode_DatabasePlugin,
-                             "The database plugin does not implement the mandatory LookupIdentifier3() extension");
-    }
-
-    OrthancPluginDicomTag tmp;
-    tmp.group = tag.GetGroup();
-    tmp.element = tag.GetElement();
-    tmp.value = value.c_str();
-
-    ResetAnswers();
-    CheckSuccess(extensions_.lookupIdentifier3(GetContext(), payload_, Plugins::Convert(level),
-                                               &tmp, Compatibility::Convert(type)));
-    ForwardAnswers(result);
-  }
-
-
-  void OrthancPluginDatabase::LookupIdentifierRange(std::list<int64_t>& result,
-                                                    ResourceType level,
-                                                    const DicomTag& tag,
-                                                    const std::string& start,
-                                                    const std::string& end)
-  {
-    if (extensions_.lookupIdentifierRange == NULL)
-    {
-      // Default implementation, for plugins using Orthanc SDK <= 1.3.2
-
-      LookupIdentifier(result, level, tag, Compatibility::IdentifierConstraintType_GreaterOrEqual, start);
-
-      std::list<int64_t> b;
-      LookupIdentifier(result, level, tag, Compatibility::IdentifierConstraintType_SmallerOrEqual, end);
-
-      result.splice(result.end(), b);
-    }
-    else
-    {
-      ResetAnswers();
-      CheckSuccess(extensions_.lookupIdentifierRange(GetContext(), payload_, Plugins::Convert(level),
-                                                     tag.GetGroup(), tag.GetElement(),
-                                                     start.c_str(), end.c_str()));
-      ForwardAnswers(result);
-    }
-  }
-
-
-  void OrthancPluginDatabase::SetResourcesContent(const Orthanc::ResourcesContent& content)
-  {
-    if (extensions_.setResourcesContent == NULL)
-    {
-      ISetResourcesContent::Apply(*this, content);
-    }
-    else
-    {
-      std::vector<OrthancPluginResourcesContentTags> identifierTags;
-      std::vector<OrthancPluginResourcesContentTags> mainDicomTags;
-      std::vector<OrthancPluginResourcesContentMetadata> metadata;
-
-      identifierTags.reserve(content.GetListTags().size());
-      mainDicomTags.reserve(content.GetListTags().size());
-      metadata.reserve(content.GetListMetadata().size());
-
-      for (ResourcesContent::ListTags::const_iterator
-             it = content.GetListTags().begin(); it != content.GetListTags().end(); ++it)
-      {
-        OrthancPluginResourcesContentTags tmp;
-        tmp.resource = it->resourceId_;
-        tmp.group = it->tag_.GetGroup();
-        tmp.element = it->tag_.GetElement();
-        tmp.value = it->value_.c_str();
-
-        if (it->isIdentifier_)
-        {
-          identifierTags.push_back(tmp);
-        }
-        else
-        {
-          mainDicomTags.push_back(tmp);
-        }
-      }
-
-      for (ResourcesContent::ListMetadata::const_iterator
-             it = content.GetListMetadata().begin(); it != content.GetListMetadata().end(); ++it)
-      {
-        OrthancPluginResourcesContentMetadata tmp;
-        tmp.resource = it->resourceId_;
-        tmp.metadata = it->metadata_;
-        tmp.value = it->value_.c_str();
-        metadata.push_back(tmp);
-      }
-
-      assert(identifierTags.size() + mainDicomTags.size() == content.GetListTags().size() &&
-             metadata.size() == content.GetListMetadata().size());
-       
-      CheckSuccess(extensions_.setResourcesContent(
-                     payload_,
-                     identifierTags.size(),
-                     (identifierTags.empty() ? NULL : &identifierTags[0]),
-                     mainDicomTags.size(),
-                     (mainDicomTags.empty() ? NULL : &mainDicomTags[0]),
-                     metadata.size(),
-                     (metadata.empty() ? NULL : &metadata[0])));
-    }
-  }
-
-
-
-  void OrthancPluginDatabase::GetChildrenMetadata(std::list<std::string>& target,
-                                                  int64_t resourceId,
-                                                  MetadataType metadata)
-  {
-    if (extensions_.getChildrenMetadata == NULL)
-    {
-      IGetChildrenMetadata::Apply(*this, target, resourceId, metadata);
-    }
-    else
-    {
-      ResetAnswers();
-      CheckSuccess(extensions_.getChildrenMetadata
-                   (GetContext(), payload_, resourceId, static_cast<int32_t>(metadata)));
-      ForwardAnswers(target);
-    }
-  }
-
-
-  int64_t OrthancPluginDatabase::GetLastChangeIndex()
-  {
-    if (extensions_.getLastChangeIndex == NULL)
-    {
-      // This was the default behavior in Orthanc <= 1.5.1
-      // https://groups.google.com/d/msg/orthanc-users/QhzB6vxYeZ0/YxabgqpfBAAJ
-      return 0;
-    }
-    else
-    {
-      int64_t result = 0;
-      CheckSuccess(extensions_.getLastChangeIndex(&result, payload_));
-      return result;
-    }
-  }
-
-  
-  void OrthancPluginDatabase::TagMostRecentPatient(int64_t patient)
-  {
-    if (extensions_.tagMostRecentPatient != NULL)
-    {
-      CheckSuccess(extensions_.tagMostRecentPatient(payload_, patient));
-    }
-  }
-
-
-  bool OrthancPluginDatabase::LookupResourceAndParent(int64_t& id,
-                                                      ResourceType& type,
-                                                      std::string& parentPublicId,
-                                                      const std::string& publicId)
-  {
-    if (extensions_.lookupResourceAndParent == NULL)
-    {
-      return ILookupResourceAndParent::Apply(*this, id, type, parentPublicId, publicId);
-    }
-    else
-    {
-      std::list<std::string> parent;
-
-      uint8_t isExisting;
-      OrthancPluginResourceType pluginType = OrthancPluginResourceType_Patient;
-      
-      ResetAnswers();
-      CheckSuccess(extensions_.lookupResourceAndParent
-                   (GetContext(), &isExisting, &id, &pluginType, payload_, publicId.c_str()));
-      ForwardAnswers(parent);
-
-      if (isExisting)
-      {
-        type = Plugins::Convert(pluginType);
-
-        if (parent.empty())
-        {
-          if (type != ResourceType_Patient)
-          {
-            throw OrthancException(ErrorCode_DatabasePlugin);
-          }
-        }
-        else if (parent.size() == 1)
-        {
-          if ((type != ResourceType_Study &&
-               type != ResourceType_Series &&
-               type != ResourceType_Instance) ||
-              parent.front().empty())
-          {
-            throw OrthancException(ErrorCode_DatabasePlugin);
-          }
-
-          parentPublicId = parent.front();
-        }
-        else
-        {
-          throw OrthancException(ErrorCode_DatabasePlugin);
-        }
-
-        return true;
-      }
-      else
-      {
-        return false;
-      }
-    }
-  }
 }
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabase.h	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabase.h	Mon Mar 15 15:30:42 2021 +0100
@@ -47,12 +47,7 @@
 namespace Orthanc
 {
   class OrthancPluginDatabase :
-    public IDatabaseWrapper,
-    public Compatibility::ICreateInstance,
-    public Compatibility::IGetChildrenMetadata,
-    public Compatibility::ILookupResources,
-    public Compatibility::ILookupResourceAndParent,
-    public Compatibility::ISetResourcesContent
+    public IDatabaseWrapper
   {
   private:
     class Transaction;
@@ -111,11 +106,9 @@
                           size_t extensionsSize,
                           void *payload);
 
-    virtual void Open() 
-      ORTHANC_OVERRIDE;
+    virtual void Open() ORTHANC_OVERRIDE;
 
-    virtual void Close() 
-      ORTHANC_OVERRIDE
+    virtual void Close() ORTHANC_OVERRIDE
     {
       CheckSuccess(backend_.close(payload_));
     }
@@ -125,247 +118,25 @@
       return library_;
     }
 
-    virtual void AddAttachment(int64_t id,
-                               const FileInfo& attachment) 
-      ORTHANC_OVERRIDE;
-
-    virtual void AttachChild(int64_t parent,
-                             int64_t child) 
-      ORTHANC_OVERRIDE;
-
-    virtual void ClearChanges() 
-      ORTHANC_OVERRIDE;
-
-    virtual void ClearExportedResources() 
-      ORTHANC_OVERRIDE;
-
-    virtual int64_t CreateResource(const std::string& publicId,
-                                   ResourceType type) 
-      ORTHANC_OVERRIDE;
-
-    virtual void DeleteAttachment(int64_t id,
-                                  FileContentType attachment) 
-      ORTHANC_OVERRIDE;
-
-    virtual void DeleteMetadata(int64_t id,
-                                MetadataType type) 
-      ORTHANC_OVERRIDE;
-
-    virtual void DeleteResource(int64_t id) 
-      ORTHANC_OVERRIDE;
-
-    virtual void FlushToDisk() 
-      ORTHANC_OVERRIDE
+    virtual void FlushToDisk() ORTHANC_OVERRIDE
     {
     }
 
-    virtual bool HasFlushToDisk() const 
-      ORTHANC_OVERRIDE
+    virtual bool HasFlushToDisk() const ORTHANC_OVERRIDE
     {
       return false;
     }
 
-    virtual void GetAllMetadata(std::map<MetadataType, std::string>& target,
-                                int64_t id) 
-      ORTHANC_OVERRIDE;
-
-    virtual void GetAllPublicIds(std::list<std::string>& target,
-                                 ResourceType resourceType) 
-      ORTHANC_OVERRIDE;
-
-    virtual void GetAllPublicIds(std::list<std::string>& target,
-                                 ResourceType resourceType,
-                                 size_t since,
-                                 size_t limit) 
-      ORTHANC_OVERRIDE;
-
-    virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/,
-                            bool& done /*out*/,
-                            int64_t since,
-                            uint32_t maxResults) 
-      ORTHANC_OVERRIDE;
-
-    virtual void GetChildrenInternalId(std::list<int64_t>& target,
-                                       int64_t id) 
-      ORTHANC_OVERRIDE;
-
-    virtual void GetChildrenPublicId(std::list<std::string>& target,
-                                     int64_t id) 
-      ORTHANC_OVERRIDE;
-
-    virtual void GetExportedResources(std::list<ExportedResource>& target /*out*/,
-                                      bool& done /*out*/,
-                                      int64_t since,
-                                      uint32_t maxResults) 
-      ORTHANC_OVERRIDE;
-
-    virtual void GetLastChange(std::list<ServerIndexChange>& target /*out*/) 
-      ORTHANC_OVERRIDE;
-
-    virtual void GetLastExportedResource(std::list<ExportedResource>& target /*out*/) 
-      ORTHANC_OVERRIDE;
-
-    virtual void GetMainDicomTags(DicomMap& map,
-                                  int64_t id) 
-      ORTHANC_OVERRIDE;
-
-    virtual std::string GetPublicId(int64_t resourceId) 
-      ORTHANC_OVERRIDE;
-
-    virtual uint64_t GetResourceCount(ResourceType resourceType) 
-      ORTHANC_OVERRIDE;
-
-    virtual ResourceType GetResourceType(int64_t resourceId) 
-      ORTHANC_OVERRIDE;
-
-    virtual uint64_t GetTotalCompressedSize() 
-      ORTHANC_OVERRIDE;
-    
-    virtual uint64_t GetTotalUncompressedSize() 
-      ORTHANC_OVERRIDE;
-
-    virtual bool IsExistingResource(int64_t internalId) 
-      ORTHANC_OVERRIDE;
-
-    virtual bool IsProtectedPatient(int64_t internalId) 
-      ORTHANC_OVERRIDE;
-
-    virtual void ListAvailableAttachments(std::set<FileContentType>& target,
-                                          int64_t id) 
-      ORTHANC_OVERRIDE;
-
-    virtual void LogChange(int64_t internalId,
-                           const ServerIndexChange& change) 
-      ORTHANC_OVERRIDE;
-
-    virtual void LogExportedResource(const ExportedResource& resource) 
-      ORTHANC_OVERRIDE;
-    
-    virtual bool LookupAttachment(FileInfo& attachment,
-                                  int64_t id,
-                                  FileContentType contentType) 
-      ORTHANC_OVERRIDE;
-
-    virtual bool LookupGlobalProperty(std::string& target,
-                                      GlobalProperty property) 
-      ORTHANC_OVERRIDE;
-
-    virtual bool LookupMetadata(std::string& target,
-                                int64_t id,
-                                MetadataType type) 
-      ORTHANC_OVERRIDE;
-
-    virtual bool LookupParent(int64_t& parentId,
-                              int64_t resourceId) 
-      ORTHANC_OVERRIDE;
-
-    virtual bool LookupResource(int64_t& id,
-                                ResourceType& type,
-                                const std::string& publicId) 
-      ORTHANC_OVERRIDE;
-
-    virtual bool SelectPatientToRecycle(int64_t& internalId) 
-      ORTHANC_OVERRIDE;
-
-    virtual bool SelectPatientToRecycle(int64_t& internalId,
-                                        int64_t patientIdToAvoid) 
-      ORTHANC_OVERRIDE;
-
-    virtual void SetGlobalProperty(GlobalProperty property,
-                                   const std::string& value) 
-      ORTHANC_OVERRIDE;
-
-    virtual void ClearMainDicomTags(int64_t id) 
-      ORTHANC_OVERRIDE;
-
-    virtual void SetMainDicomTag(int64_t id,
-                                 const DicomTag& tag,
-                                 const std::string& value) 
-      ORTHANC_OVERRIDE;
-
-    virtual void SetIdentifierTag(int64_t id,
-                                  const DicomTag& tag,
-                                  const std::string& value) 
-      ORTHANC_OVERRIDE;
-
-    virtual void SetMetadata(int64_t id,
-                             MetadataType type,
-                             const std::string& value) 
-      ORTHANC_OVERRIDE;
-
-    virtual void SetProtectedPatient(int64_t internalId, 
-                                     bool isProtected) 
-      ORTHANC_OVERRIDE;
-
     virtual IDatabaseWrapper::ITransaction* StartTransaction(TransactionType type,
                                                              IDatabaseListener& listener)
       ORTHANC_OVERRIDE;
 
-    virtual unsigned int GetDatabaseVersion() 
-      ORTHANC_OVERRIDE;
+    virtual unsigned int GetDatabaseVersion() ORTHANC_OVERRIDE;
 
     virtual void Upgrade(unsigned int targetVersion,
-                         IStorageArea& storageArea) 
-      ORTHANC_OVERRIDE;
+                         IStorageArea& storageArea) ORTHANC_OVERRIDE;    
 
     void AnswerReceived(const _OrthancPluginDatabaseAnswer& answer);
-
-    virtual bool IsDiskSizeAbove(uint64_t threshold) 
-      ORTHANC_OVERRIDE;
-
-    virtual void ApplyLookupResources(std::list<std::string>& resourcesId,
-                                      std::list<std::string>* instancesId,
-                                      const std::vector<DatabaseConstraint>& lookup,
-                                      ResourceType queryLevel,
-                                      size_t limit)
-      ORTHANC_OVERRIDE;
-
-    virtual bool CreateInstance(CreateInstanceResult& result,
-                                int64_t& instanceId,
-                                const std::string& patient,
-                                const std::string& study,
-                                const std::string& series,
-                                const std::string& instance)
-      ORTHANC_OVERRIDE;
-
-    // From the "ILookupResources" interface
-    virtual void GetAllInternalIds(std::list<int64_t>& target,
-                                   ResourceType resourceType) 
-      ORTHANC_OVERRIDE;
-
-    // From the "ILookupResources" interface
-    virtual void LookupIdentifier(std::list<int64_t>& result,
-                                  ResourceType level,
-                                  const DicomTag& tag,
-                                  Compatibility::IdentifierConstraintType type,
-                                  const std::string& value)
-      ORTHANC_OVERRIDE;
-    
-    // From the "ILookupResources" interface
-    virtual void LookupIdentifierRange(std::list<int64_t>& result,
-                                       ResourceType level,
-                                       const DicomTag& tag,
-                                       const std::string& start,
-                                       const std::string& end)
-      ORTHANC_OVERRIDE;
-
-    virtual void SetResourcesContent(const Orthanc::ResourcesContent& content)
-      ORTHANC_OVERRIDE;
-
-    virtual void GetChildrenMetadata(std::list<std::string>& target,
-                                     int64_t resourceId,
-                                     MetadataType metadata)
-      ORTHANC_OVERRIDE;
-
-    virtual int64_t GetLastChangeIndex() ORTHANC_OVERRIDE;
-  
-    virtual void TagMostRecentPatient(int64_t patient) ORTHANC_OVERRIDE;
-
-    virtual bool LookupResourceAndParent(int64_t& id,
-                                         ResourceType& type,
-                                         std::string& parentPublicId,
-                                         const std::string& publicId)
-      ORTHANC_OVERRIDE;
   };
 }
 
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Mon Mar 15 15:30:42 2021 +0100
@@ -65,6 +65,7 @@
 #include "../../../OrthancFramework/Sources/SerializationToolbox.h"
 #include "../../../OrthancFramework/Sources/StringMemoryBuffer.h"
 #include "../../../OrthancFramework/Sources/Toolbox.h"
+#include "../../Sources/Database/VoidDatabaseListener.h"
 #include "../../Sources/OrthancConfiguration.h"
 #include "../../Sources/OrthancFindRequestHandler.h"
 #include "../../Sources/Search/HierarchicalMatcher.h"
@@ -4970,8 +4971,16 @@
                                  "The service ReconstructMainDicomTags can only be invoked by custom database plugins");
         }
 
-        IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea);
-        ServerToolbox::ReconstructMainDicomTags(*pimpl_->database_, storage, Plugins::Convert(p.level));
+        VoidDatabaseListener listener;
+        
+        {
+          IStorageArea& storage = *reinterpret_cast<IStorageArea*>(p.storageArea);
+
+          std::unique_ptr<IDatabaseWrapper::ITransaction> transaction(
+            pimpl_->database_->StartTransaction(TransactionType_ReadWrite, listener));
+          ServerToolbox::ReconstructMainDicomTags(*transaction, storage, Plugins::Convert(p.level));
+          transaction->Commit(0);
+        }
 
         return true;
       }
--- a/OrthancServer/Resources/RunCppCheck.sh	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/Resources/RunCppCheck.sh	Mon Mar 15 15:30:42 2021 +0100
@@ -17,8 +17,8 @@
 stlFindInsert:../../OrthancFramework/Sources/DicomFormat/DicomMap.cpp:1194
 stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:164
 stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:72
-stlFindInsert:../../OrthancServer/Sources/OrthancWebDav.cpp:385
-stlFindInsert:../../OrthancServer/Sources/ServerIndex.cpp:399
+stlFindInsert:../../OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp:383
+stlFindInsert:../../OrthancServer/Sources/OrthancWebDav.cpp:384
 syntaxError:../../OrthancFramework/Sources/SQLite/FunctionContext.h:50
 syntaxError:../../OrthancFramework/UnitTestsSources/ZipTests.cpp:130
 syntaxError:../../OrthancServer/UnitTestsSources/UnitTestsMain.cpp:321
--- a/OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp	Mon Mar 15 15:30:42 2021 +0100
@@ -161,7 +161,7 @@
 
     
     static void ApplyLevel(SetOfResources& candidates,
-                           IDatabaseWrapper& database,
+                           IDatabaseWrapper::ITransaction& transaction,
                            ILookupResources& compatibility,
                            const std::vector<DatabaseConstraint>& lookup,
                            ResourceType level)
@@ -267,7 +267,7 @@
              candidate != source.end(); ++candidate)
         {
           DicomMap tags;
-          database.GetMainDicomTags(tags, *candidate);
+          transaction.GetMainDicomTags(tags, *candidate);
 
           bool match = true;
 
@@ -291,7 +291,7 @@
     }
 
 
-    static std::string GetOneInstance(IDatabaseWrapper& compatibility,
+    static std::string GetOneInstance(IDatabaseWrapper::ITransaction& compatibility,
                                       int64_t resource,
                                       ResourceType level)
     {
@@ -348,11 +348,11 @@
       assert(upperLevel <= queryLevel &&
              queryLevel <= lowerLevel);
 
-      SetOfResources candidates(database_, upperLevel);
+      SetOfResources candidates(transaction_, upperLevel);
 
       for (int level = upperLevel; level <= lowerLevel; level++)
       {
-        ApplyLevel(candidates, database_, compatibility_, lookup, static_cast<ResourceType>(level));
+        ApplyLevel(candidates, transaction_, compatibility_, lookup, static_cast<ResourceType>(level));
 
         if (level != lowerLevel)
         {
@@ -372,7 +372,7 @@
                it = resources.begin(); it != resources.end(); ++it)
         {
           int64_t parent;
-          if (database_.LookupParent(parent, *it))
+          if (transaction_.LookupParent(parent, *it))
           {
             parents.push_back(parent);
           }
@@ -396,9 +396,9 @@
       for (std::list<int64_t>::const_iterator
              it = resources.begin(); it != resources.end(); ++it, pos++)
       {
-        assert(database_.GetResourceType(*it) == queryLevel);
+        assert(transaction_.GetResourceType(*it) == queryLevel);
 
-        const std::string resource = database_.GetPublicId(*it);
+        const std::string resource = transaction_.GetPublicId(*it);
         resourcesId.push_back(resource);
 
         if (instancesId != NULL)
@@ -411,7 +411,7 @@
           else
           {
             // Collect one child instance for each of the selected resources
-            instancesId->push_back(GetOneInstance(database_, *it, queryLevel));
+            instancesId->push_back(GetOneInstance(transaction_, *it, queryLevel));
           }
         }
       }
--- a/OrthancServer/Sources/Database/Compatibility/DatabaseLookup.h	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/Sources/Database/Compatibility/DatabaseLookup.h	Mon Mar 15 15:30:42 2021 +0100
@@ -43,13 +43,13 @@
     class DatabaseLookup : public boost::noncopyable
     {
     private:
-      IDatabaseWrapper&  database_;
+      IDatabaseWrapper::ITransaction&  transaction_;
       ILookupResources&  compatibility_;
 
     public:
-      DatabaseLookup(IDatabaseWrapper& database,
+      DatabaseLookup(IDatabaseWrapper::ITransaction& transaction,
                      ILookupResources& compatibility) :
-        database_(database),
+        transaction_(transaction),
         compatibility_(compatibility)
       {
       }
--- a/OrthancServer/Sources/Database/Compatibility/ILookupResources.cpp	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/Sources/Database/Compatibility/ILookupResources.cpp	Mon Mar 15 15:30:42 2021 +0100
@@ -41,7 +41,7 @@
   namespace Compatibility
   {
     void ILookupResources::Apply(
-      IDatabaseWrapper& database,
+      IDatabaseWrapper::ITransaction& transaction,
       ILookupResources& compatibility,
       std::list<std::string>& resourcesId,
       std::list<std::string>* instancesId,
@@ -49,7 +49,7 @@
       ResourceType queryLevel,
       size_t limit)
     {
-      Compatibility::DatabaseLookup compat(database, compatibility);
+      Compatibility::DatabaseLookup compat(transaction, compatibility);
       compat.ApplyLookupResources(resourcesId, instancesId, lookup, queryLevel, limit);
     }
   }
--- a/OrthancServer/Sources/Database/Compatibility/ILookupResources.h	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/Sources/Database/Compatibility/ILookupResources.h	Mon Mar 15 15:30:42 2021 +0100
@@ -66,7 +66,7 @@
                                          const std::string& start,
                                          const std::string& end) = 0;
 
-      static void Apply(IDatabaseWrapper& database,
+      static void Apply(IDatabaseWrapper::ITransaction& transaction,
                         ILookupResources& compatibility,
                         std::list<std::string>& resourcesId,
                         std::list<std::string>* instancesId,
--- a/OrthancServer/Sources/Database/Compatibility/SetOfResources.cpp	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/Sources/Database/Compatibility/SetOfResources.cpp	Mon Mar 15 15:30:42 2021 +0100
@@ -90,7 +90,7 @@
              it != resources_->end(); ++it)
         {
           std::list<int64_t> tmp;
-          database_.GetChildrenInternalId(tmp, *it);
+          transaction_.GetChildrenInternalId(tmp, *it);
 
           for (std::list<int64_t>::const_iterator
                  child = tmp.begin(); child != tmp.end(); ++child)
@@ -133,14 +133,14 @@
       if (resources_.get() == NULL)
       {
         // All the resources of this level are part of the filter
-        database_.GetAllPublicIds(result, level_);
+        transaction_.GetAllPublicIds(result, level_);
       }
       else
       {
         for (Resources::const_iterator it = resources_->begin(); 
              it != resources_->end(); ++it)
         {
-          result.push_back(database_.GetPublicId(*it));
+          result.push_back(transaction_.GetPublicId(*it));
         }
       }
     }
--- a/OrthancServer/Sources/Database/Compatibility/SetOfResources.h	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/Sources/Database/Compatibility/SetOfResources.h	Mon Mar 15 15:30:42 2021 +0100
@@ -49,14 +49,14 @@
     private:
       typedef std::set<int64_t>  Resources;
 
-      IDatabaseWrapper&           database_;
+      IDatabaseWrapper::ITransaction&  transaction_;
       ResourceType                level_;
       std::unique_ptr<Resources>  resources_;
     
     public:
-      SetOfResources(IDatabaseWrapper& database,
+      SetOfResources(IDatabaseWrapper::ITransaction& transaction,
                      ResourceType level) : 
-        database_(database),
+        transaction_(transaction),
         level_(level)
       {
       }
--- a/OrthancServer/Sources/Database/IDatabaseListener.h	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/Sources/Database/IDatabaseListener.h	Mon Mar 15 15:30:42 2021 +0100
@@ -36,6 +36,7 @@
 #include "../../../OrthancFramework/Sources/FileStorage/FileInfo.h"
 #include "../ServerEnumerations.h"
 
+#include <boost/noncopyable.hpp>
 #include <string>
 
 namespace Orthanc
--- a/OrthancServer/Sources/Database/IDatabaseWrapper.h	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/Sources/Database/IDatabaseWrapper.h	Mon Mar 15 15:30:42 2021 +0100
@@ -55,6 +55,17 @@
   class IDatabaseWrapper : public boost::noncopyable
   {
   public:
+    struct CreateInstanceResult
+    {
+      bool     isNewPatient_;
+      bool     isNewStudy_;
+      bool     isNewSeries_;
+      int64_t  patientId_;
+      int64_t  studyId_;
+      int64_t  seriesId_;
+    };
+
+
     class ITransaction : public boost::noncopyable
     {
     public:
@@ -68,19 +79,163 @@
       // have no fast way to compute the size of all the stored
       // attachments (cf. "fastGetTotalSize_")
       virtual void Commit(int64_t fileSizeDelta) = 0;
-    };
+
+      virtual void AddAttachment(int64_t id,
+                                 const FileInfo& attachment) = 0;
+
+      virtual void ClearChanges() = 0;
+
+      virtual void ClearExportedResources() = 0;
+
+      virtual void DeleteAttachment(int64_t id,
+                                    FileContentType attachment) = 0;
+
+      virtual void DeleteMetadata(int64_t id,
+                                  MetadataType type) = 0;
+
+      virtual void DeleteResource(int64_t id) = 0;
+
+      virtual void GetAllMetadata(std::map<MetadataType, std::string>& target,
+                                  int64_t id) = 0;
+
+      virtual void GetAllPublicIds(std::list<std::string>& target,
+                                   ResourceType resourceType) = 0;
+
+      virtual void GetAllPublicIds(std::list<std::string>& target,
+                                   ResourceType resourceType,
+                                   size_t since,
+                                   size_t limit) = 0;
+
+      virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/,
+                              bool& done /*out*/,
+                              int64_t since,
+                              uint32_t maxResults) = 0;
+
+      virtual void GetChildrenInternalId(std::list<int64_t>& target,
+                                         int64_t id) = 0;
+
+      virtual void GetChildrenPublicId(std::list<std::string>& target,
+                                       int64_t id) = 0;
+
+      virtual void GetExportedResources(std::list<ExportedResource>& target /*out*/,
+                                        bool& done /*out*/,
+                                        int64_t since,
+                                        uint32_t maxResults) = 0;
+
+      virtual void GetLastChange(std::list<ServerIndexChange>& target /*out*/) = 0;
+
+      virtual void GetLastExportedResource(std::list<ExportedResource>& target /*out*/) = 0;
+
+      virtual void GetMainDicomTags(DicomMap& map,
+                                    int64_t id) = 0;
+
+      virtual std::string GetPublicId(int64_t resourceId) = 0;
+
+      virtual uint64_t GetResourceCount(ResourceType resourceType) = 0;
+
+      virtual ResourceType GetResourceType(int64_t resourceId) = 0;
+
+      virtual uint64_t GetTotalCompressedSize() = 0;
+    
+      virtual uint64_t GetTotalUncompressedSize() = 0;
+
+      virtual bool IsExistingResource(int64_t internalId) = 0;
+
+      virtual bool IsProtectedPatient(int64_t internalId) = 0;
+
+      virtual void ListAvailableAttachments(std::set<FileContentType>& target,
+                                            int64_t id) = 0;
+
+      virtual void LogChange(int64_t internalId,
+                             const ServerIndexChange& change) = 0;
+
+      virtual void LogExportedResource(const ExportedResource& resource) = 0;
+    
+      virtual bool LookupAttachment(FileInfo& attachment,
+                                    int64_t id,
+                                    FileContentType contentType) = 0;
+
+      virtual bool LookupGlobalProperty(std::string& target,
+                                        GlobalProperty property) = 0;
+
+      virtual bool LookupMetadata(std::string& target,
+                                  int64_t id,
+                                  MetadataType type) = 0;
+
+      virtual bool LookupParent(int64_t& parentId,
+                                int64_t resourceId) = 0;
+
+      virtual bool LookupResource(int64_t& id,
+                                  ResourceType& type,
+                                  const std::string& publicId) = 0;
+
+      virtual bool SelectPatientToRecycle(int64_t& internalId) = 0;
+
+      virtual bool SelectPatientToRecycle(int64_t& internalId,
+                                          int64_t patientIdToAvoid) = 0;
+
+      virtual void SetGlobalProperty(GlobalProperty property,
+                                     const std::string& value) = 0;
+
+      virtual void ClearMainDicomTags(int64_t id) = 0;
+
+      virtual void SetMetadata(int64_t id,
+                               MetadataType type,
+                               const std::string& value) = 0;
+
+      virtual void SetProtectedPatient(int64_t internalId, 
+                                       bool isProtected) = 0;
 
 
-    struct CreateInstanceResult
-    {
-      bool     isNewPatient_;
-      bool     isNewStudy_;
-      bool     isNewSeries_;
-      int64_t  patientId_;
-      int64_t  studyId_;
-      int64_t  seriesId_;
+      /**
+       * Primitives introduced in Orthanc 1.5.2
+       **/
+    
+      virtual bool IsDiskSizeAbove(uint64_t threshold) = 0;
+    
+      virtual void ApplyLookupResources(std::list<std::string>& resourcesId,
+                                        std::list<std::string>* instancesId, // Can be NULL if not needed
+                                        const std::vector<DatabaseConstraint>& lookup,
+                                        ResourceType queryLevel,
+                                        size_t limit) = 0;
+
+      // Returns "true" iff. the instance is new and has been inserted
+      // into the database. If "false" is returned, the content of
+      // "result" is undefined, but "instanceId" must be properly
+      // set. This method must also tag the parent patient as the most
+      // recent in the patient recycling order if it is not protected
+      // (so as to fix issue #58).
+      virtual bool CreateInstance(CreateInstanceResult& result, /* out */
+                                  int64_t& instanceId,          /* out */
+                                  const std::string& patient,
+                                  const std::string& study,
+                                  const std::string& series,
+                                  const std::string& instance) = 0;
+
+      // It is guaranteed that the resources to be modified have no main
+      // DICOM tags, and no DICOM identifiers associated with
+      // them. However, some metadata might be already existing, and
+      // have to be overwritten.
+      virtual void SetResourcesContent(const ResourcesContent& content) = 0;
+
+      virtual void GetChildrenMetadata(std::list<std::string>& target,
+                                       int64_t resourceId,
+                                       MetadataType metadata) = 0;
+
+      virtual int64_t GetLastChangeIndex() = 0;
+
+
+      /**
+       * Primitives introduced in Orthanc 1.5.4
+       **/
+
+      virtual bool LookupResourceAndParent(int64_t& id,
+                                           ResourceType& type,
+                                           std::string& parentPublicId,
+                                           const std::string& publicId) = 0;
     };
 
+
     virtual ~IDatabaseWrapper()
     {
     }
@@ -89,116 +244,10 @@
 
     virtual void Close() = 0;
 
-    virtual void AddAttachment(int64_t id,
-                               const FileInfo& attachment) = 0;
-
-    virtual void ClearChanges() = 0;
-
-    virtual void ClearExportedResources() = 0;
-
-    virtual void DeleteAttachment(int64_t id,
-                                  FileContentType attachment) = 0;
-
-    virtual void DeleteMetadata(int64_t id,
-                                MetadataType type) = 0;
-
-    virtual void DeleteResource(int64_t id) = 0;
-
     virtual void FlushToDisk() = 0;
 
     virtual bool HasFlushToDisk() const = 0;
 
-    virtual void GetAllMetadata(std::map<MetadataType, std::string>& target,
-                                int64_t id) = 0;
-
-    virtual void GetAllPublicIds(std::list<std::string>& target,
-                                 ResourceType resourceType) = 0;
-
-    virtual void GetAllPublicIds(std::list<std::string>& target,
-                                 ResourceType resourceType,
-                                 size_t since,
-                                 size_t limit) = 0;
-
-    virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/,
-                            bool& done /*out*/,
-                            int64_t since,
-                            uint32_t maxResults) = 0;
-
-    virtual void GetChildrenInternalId(std::list<int64_t>& target,
-                                       int64_t id) = 0;
-
-    virtual void GetChildrenPublicId(std::list<std::string>& target,
-                                     int64_t id) = 0;
-
-    virtual void GetExportedResources(std::list<ExportedResource>& target /*out*/,
-                                      bool& done /*out*/,
-                                      int64_t since,
-                                      uint32_t maxResults) = 0;
-
-    virtual void GetLastChange(std::list<ServerIndexChange>& target /*out*/) = 0;
-
-    virtual void GetLastExportedResource(std::list<ExportedResource>& target /*out*/) = 0;
-
-    virtual void GetMainDicomTags(DicomMap& map,
-                                  int64_t id) = 0;
-
-    virtual std::string GetPublicId(int64_t resourceId) = 0;
-
-    virtual uint64_t GetResourceCount(ResourceType resourceType) = 0;
-
-    virtual ResourceType GetResourceType(int64_t resourceId) = 0;
-
-    virtual uint64_t GetTotalCompressedSize() = 0;
-    
-    virtual uint64_t GetTotalUncompressedSize() = 0;
-
-    virtual bool IsExistingResource(int64_t internalId) = 0;
-
-    virtual bool IsProtectedPatient(int64_t internalId) = 0;
-
-    virtual void ListAvailableAttachments(std::set<FileContentType>& target,
-                                          int64_t id) = 0;
-
-    virtual void LogChange(int64_t internalId,
-                           const ServerIndexChange& change) = 0;
-
-    virtual void LogExportedResource(const ExportedResource& resource) = 0;
-    
-    virtual bool LookupAttachment(FileInfo& attachment,
-                                  int64_t id,
-                                  FileContentType contentType) = 0;
-
-    virtual bool LookupGlobalProperty(std::string& target,
-                                      GlobalProperty property) = 0;
-
-    virtual bool LookupMetadata(std::string& target,
-                                int64_t id,
-                                MetadataType type) = 0;
-
-    virtual bool LookupParent(int64_t& parentId,
-                              int64_t resourceId) = 0;
-
-    virtual bool LookupResource(int64_t& id,
-                                ResourceType& type,
-                                const std::string& publicId) = 0;
-
-    virtual bool SelectPatientToRecycle(int64_t& internalId) = 0;
-
-    virtual bool SelectPatientToRecycle(int64_t& internalId,
-                                        int64_t patientIdToAvoid) = 0;
-
-    virtual void SetGlobalProperty(GlobalProperty property,
-                                   const std::string& value) = 0;
-
-    virtual void ClearMainDicomTags(int64_t id) = 0;
-
-    virtual void SetMetadata(int64_t id,
-                             MetadataType type,
-                             const std::string& value) = 0;
-
-    virtual void SetProtectedPatient(int64_t internalId, 
-                                     bool isProtected) = 0;
-
     virtual ITransaction* StartTransaction(TransactionType type,
                                            IDatabaseListener& listener) = 0;
 
@@ -206,53 +255,5 @@
 
     virtual void Upgrade(unsigned int targetVersion,
                          IStorageArea& storageArea) = 0;
-
-
-    /**
-     * Primitives introduced in Orthanc 1.5.2
-     **/
-    
-    virtual bool IsDiskSizeAbove(uint64_t threshold) = 0;
-    
-    virtual void ApplyLookupResources(std::list<std::string>& resourcesId,
-                                      std::list<std::string>* instancesId, // Can be NULL if not needed
-                                      const std::vector<DatabaseConstraint>& lookup,
-                                      ResourceType queryLevel,
-                                      size_t limit) = 0;
-
-    // Returns "true" iff. the instance is new and has been inserted
-    // into the database. If "false" is returned, the content of
-    // "result" is undefined, but "instanceId" must be properly
-    // set. This method must also tag the parent patient as the most
-    // recent in the patient recycling order if it is not protected
-    // (so as to fix issue #58).
-    virtual bool CreateInstance(CreateInstanceResult& result, /* out */
-                                int64_t& instanceId,          /* out */
-                                const std::string& patient,
-                                const std::string& study,
-                                const std::string& series,
-                                const std::string& instance) = 0;
-
-    // It is guaranteed that the resources to be modified have no main
-    // DICOM tags, and no DICOM identifiers associated with
-    // them. However, some metadata might be already existing, and
-    // have to be overwritten.
-    virtual void SetResourcesContent(const ResourcesContent& content) = 0;
-
-    virtual void GetChildrenMetadata(std::list<std::string>& target,
-                                     int64_t resourceId,
-                                     MetadataType metadata) = 0;
-
-    virtual int64_t GetLastChangeIndex() = 0;
-
-
-    /**
-     * Primitives introduced in Orthanc 1.5.4
-     **/
-
-    virtual bool LookupResourceAndParent(int64_t& id,
-                                         ResourceType& type,
-                                         std::string& parentPublicId,
-                                         const std::string& publicId) = 0;
   };
 }
--- a/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp	Mon Mar 15 15:30:42 2021 +0100
@@ -39,6 +39,11 @@
 #include "../../../OrthancFramework/Sources/SQLite/Transaction.h"
 #include "../Search/ISqlLookupFormatter.h"
 #include "../ServerToolbox.h"
+#include "Compatibility/ICreateInstance.h"
+#include "Compatibility/IGetChildrenMetadata.h"
+#include "Compatibility/ILookupResourceAndParent.h"
+#include "Compatibility/ISetResourcesContent.h"
+#include "VoidDatabaseListener.h"
 
 #include <OrthancServerResources.h>
 
@@ -46,85 +51,37 @@
 #include <boost/lexical_cast.hpp>
 
 namespace Orthanc
-{
-  class SQLiteDatabaseWrapper::SignalFileDeleted : public SQLite::IScalarFunction
+{  
+  class SQLiteDatabaseWrapper::LookupFormatter : public ISqlLookupFormatter
   {
   private:
-    SQLiteDatabaseWrapper& sqlite_;
+    std::list<std::string>  values_;
 
   public:
-    SignalFileDeleted(SQLiteDatabaseWrapper& sqlite) :
-      sqlite_(sqlite)
-    {
-    }
-
-    virtual const char* GetName() const ORTHANC_OVERRIDE
+    virtual std::string GenerateParameter(const std::string& value) ORTHANC_OVERRIDE
     {
-      return "SignalFileDeleted";
+      values_.push_back(value);
+      return "?";
     }
-
-    virtual unsigned int GetCardinality() const ORTHANC_OVERRIDE
+    
+    virtual std::string FormatResourceType(ResourceType level) ORTHANC_OVERRIDE
     {
-      return 7;
+      return boost::lexical_cast<std::string>(level);
     }
 
-    virtual void Compute(SQLite::FunctionContext& context) ORTHANC_OVERRIDE
+    virtual std::string FormatWildcardEscape() ORTHANC_OVERRIDE
     {
-      if (sqlite_.listener_ != NULL)
-      {
-        std::string uncompressedMD5, compressedMD5;
-
-        if (!context.IsNullValue(5))
-        {
-          uncompressedMD5 = context.GetStringValue(5);
-        }
-
-        if (!context.IsNullValue(6))
-        {
-          compressedMD5 = context.GetStringValue(6);
-        }
-
-        FileInfo info(context.GetStringValue(0),
-                      static_cast<FileContentType>(context.GetIntValue(1)),
-                      static_cast<uint64_t>(context.GetInt64Value(2)),
-                      uncompressedMD5,
-                      static_cast<CompressionType>(context.GetIntValue(3)),
-                      static_cast<uint64_t>(context.GetInt64Value(4)),
-                      compressedMD5);
-
-        sqlite_.listener_->SignalAttachmentDeleted(info);
-      }
-    }
-  };
-    
-
-  class SQLiteDatabaseWrapper::SignalResourceDeleted : public SQLite::IScalarFunction
-  {
-  private:
-    SQLiteDatabaseWrapper& sqlite_;
-
-  public:
-    SignalResourceDeleted(SQLiteDatabaseWrapper& sqlite) :
-      sqlite_(sqlite)
-    {
+      return "ESCAPE '\\'";
     }
 
-    virtual const char* GetName() const ORTHANC_OVERRIDE
-    {
-      return "SignalResourceDeleted";
-    }
-
-    virtual unsigned int GetCardinality() const ORTHANC_OVERRIDE
+    void Bind(SQLite::Statement& statement) const
     {
-      return 2;
-    }
-
-    virtual void Compute(SQLite::FunctionContext& context) ORTHANC_OVERRIDE
-    {
-      if (sqlite_.listener_ != NULL)
+      size_t pos = 0;
+      
+      for (std::list<std::string>::const_iterator
+             it = values_.begin(); it != values_.end(); ++it, pos++)
       {
-        sqlite_.listener_->SignalResourceDeleted(static_cast<ResourceType>(context.GetIntValue(1)),
-                                                 context.GetStringValue(0));
+        statement.BindString(pos, *it);
       }
     }
   };
@@ -192,147 +149,1100 @@
   };
 
 
-  void SQLiteDatabaseWrapper::GetChangesInternal(std::list<ServerIndexChange>& target,
-                                                 bool& done,
-                                                 SQLite::Statement& s,
-                                                 uint32_t maxResults)
+  class SQLiteDatabaseWrapper::TransactionBase :
+    public SQLiteDatabaseWrapper::UnitTestsTransaction,
+    public Compatibility::ICreateInstance,
+    public Compatibility::IGetChildrenMetadata,
+    public Compatibility::ILookupResourceAndParent,
+    public Compatibility::ISetResourcesContent
   {
-    target.clear();
-
-    while (target.size() < maxResults && s.Step())
+  private:
+    void AnswerLookup(std::list<std::string>& resourcesId,
+                      std::list<std::string>& instancesId,
+                      ResourceType level)
     {
-      int64_t seq = s.ColumnInt64(0);
-      ChangeType changeType = static_cast<ChangeType>(s.ColumnInt(1));
-      ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(3));
-      const std::string& date = s.ColumnString(4);
+      resourcesId.clear();
+      instancesId.clear();
+    
+      std::unique_ptr<SQLite::Statement> statement;
+    
+      switch (level)
+      {
+        case ResourceType_Patient:
+        {
+          statement.reset(
+            new SQLite::Statement(
+              db_, SQLITE_FROM_HERE,
+              "SELECT patients.publicId, instances.publicID FROM Lookup AS patients "
+              "INNER JOIN Resources studies ON patients.internalId=studies.parentId "
+              "INNER JOIN Resources series ON studies.internalId=series.parentId "
+              "INNER JOIN Resources instances ON series.internalId=instances.parentId "
+              "GROUP BY patients.publicId"));
+      
+          break;
+        }
 
-      int64_t internalId = s.ColumnInt64(2);
-      std::string publicId = GetPublicId(internalId);
+        case ResourceType_Study:
+        {
+          statement.reset(
+            new SQLite::Statement(
+              db_, SQLITE_FROM_HERE,
+              "SELECT studies.publicId, instances.publicID FROM Lookup AS studies "
+              "INNER JOIN Resources series ON studies.internalId=series.parentId "
+              "INNER JOIN Resources instances ON series.internalId=instances.parentId "
+              "GROUP BY studies.publicId"));
+      
+          break;
+        }
 
-      target.push_back(ServerIndexChange(seq, changeType, resourceType, publicId, date));
+        case ResourceType_Series:
+        {
+          statement.reset(
+            new SQLite::Statement(
+              db_, SQLITE_FROM_HERE,
+              "SELECT series.publicId, instances.publicID FROM Lookup AS series "
+              "INNER JOIN Resources instances ON series.internalId=instances.parentId "
+              "GROUP BY series.publicId"));
+      
+          break;
+        }
+
+        case ResourceType_Instance:
+        {
+          statement.reset(
+            new SQLite::Statement(
+              db_, SQLITE_FROM_HERE, "SELECT publicId, publicId FROM Lookup"));
+        
+          break;
+        }
+      
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+
+      assert(statement.get() != NULL);
+      
+      while (statement->Step())
+      {
+        resourcesId.push_back(statement->ColumnString(0));
+        instancesId.push_back(statement->ColumnString(1));
+      }
     }
 
-    done = !(target.size() == maxResults && s.Step());
-  }
+
+    void ClearTable(const std::string& tableName)
+    {
+      db_.Execute("DELETE FROM " + tableName);    
+    }
+
+
+    void GetChangesInternal(std::list<ServerIndexChange>& target,
+                            bool& done,
+                            SQLite::Statement& s,
+                            uint32_t maxResults)
+    {
+      target.clear();
+
+      while (target.size() < maxResults && s.Step())
+      {
+        int64_t seq = s.ColumnInt64(0);
+        ChangeType changeType = static_cast<ChangeType>(s.ColumnInt(1));
+        ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(3));
+        const std::string& date = s.ColumnString(4);
+
+        int64_t internalId = s.ColumnInt64(2);
+        std::string publicId = GetPublicId(internalId);
+
+        target.push_back(ServerIndexChange(seq, changeType, resourceType, publicId, date));
+      }
+
+      done = !(target.size() == maxResults && s.Step());
+    }
 
 
-  void SQLiteDatabaseWrapper::GetExportedResourcesInternal(std::list<ExportedResource>& target,
-                                                           bool& done,
-                                                           SQLite::Statement& s,
-                                                           uint32_t maxResults)
-  {
-    target.clear();
-
-    while (target.size() < maxResults && s.Step())
+    void GetExportedResourcesInternal(std::list<ExportedResource>& target,
+                                      bool& done,
+                                      SQLite::Statement& s,
+                                      uint32_t maxResults)
     {
-      int64_t seq = s.ColumnInt64(0);
-      ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(1));
-      std::string publicId = s.ColumnString(2);
+      target.clear();
+
+      while (target.size() < maxResults && s.Step())
+      {
+        int64_t seq = s.ColumnInt64(0);
+        ResourceType resourceType = static_cast<ResourceType>(s.ColumnInt(1));
+        std::string publicId = s.ColumnString(2);
+
+        ExportedResource resource(seq, 
+                                  resourceType,
+                                  publicId,
+                                  s.ColumnString(3),  // modality
+                                  s.ColumnString(8),  // date
+                                  s.ColumnString(4),  // patient ID
+                                  s.ColumnString(5),  // study instance UID
+                                  s.ColumnString(6),  // series instance UID
+                                  s.ColumnString(7)); // sop instance UID
+
+        target.push_back(resource);
+      }
+
+      done = !(target.size() == maxResults && s.Step());
+    }
+
 
-      ExportedResource resource(seq, 
-                                resourceType,
-                                publicId,
-                                s.ColumnString(3),  // modality
-                                s.ColumnString(8),  // date
-                                s.ColumnString(4),  // patient ID
-                                s.ColumnString(5),  // study instance UID
-                                s.ColumnString(6),  // series instance UID
-                                s.ColumnString(7)); // sop instance UID
+    void GetChildren(std::list<std::string>& childrenPublicIds,
+                     int64_t id)
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE parentId=?");
+      s.BindInt64(0, id);
+
+      childrenPublicIds.clear();
+      while (s.Step())
+      {
+        childrenPublicIds.push_back(s.ColumnString(0));
+      }
+    }
 
-      target.push_back(resource);
+    IDatabaseListener&        listener_;
+    SignalRemainingAncestor&  signalRemainingAncestor_;
+
+  public:
+    TransactionBase(SQLite::Connection& db,
+                    IDatabaseListener& listener,
+                    SignalRemainingAncestor& signalRemainingAncestor) :
+      UnitTestsTransaction(db),
+      listener_(listener),
+      signalRemainingAncestor_(signalRemainingAncestor)
+    {
+    }
+
+    IDatabaseListener& GetListener() const
+    {
+      return listener_;
     }
 
-    done = !(target.size() == maxResults && s.Step());
-  }
+    
+    virtual void AddAttachment(int64_t id,
+                               const FileInfo& attachment) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?, ?, ?)");
+      s.BindInt64(0, id);
+      s.BindInt(1, attachment.GetContentType());
+      s.BindString(2, attachment.GetUuid());
+      s.BindInt64(3, attachment.GetCompressedSize());
+      s.BindInt64(4, attachment.GetUncompressedSize());
+      s.BindInt(5, attachment.GetCompressionType());
+      s.BindString(6, attachment.GetUncompressedMD5());
+      s.BindString(7, attachment.GetCompressedMD5());
+      s.Run();
+    }
+
+
+    virtual void ApplyLookupResources(std::list<std::string>& resourcesId,
+                                      std::list<std::string>* instancesId,
+                                      const std::vector<DatabaseConstraint>& lookup,
+                                      ResourceType queryLevel,
+                                      size_t limit) ORTHANC_OVERRIDE
+    {
+      LookupFormatter formatter;
+
+      std::string sql;
+      LookupFormatter::Apply(sql, formatter, lookup, queryLevel, limit);
+
+      sql = "CREATE TEMPORARY TABLE Lookup AS " + sql;
+    
+      {
+        SQLite::Statement s(db_, SQLITE_FROM_HERE, "DROP TABLE IF EXISTS Lookup");
+        s.Run();
+      }
+
+      {
+        SQLite::Statement statement(db_, sql);
+        formatter.Bind(statement);
+        statement.Run();
+      }
+
+      if (instancesId != NULL)
+      {
+        AnswerLookup(resourcesId, *instancesId, queryLevel);
+      }
+      else
+      {
+        resourcesId.clear();
+    
+        SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Lookup");
+        
+        while (s.Step())
+        {
+          resourcesId.push_back(s.ColumnString(0));
+        }
+      }
+    }
+
+
+    virtual void AttachChild(int64_t parent,
+                             int64_t child) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Resources SET parentId = ? WHERE internalId = ?");
+      s.BindInt64(0, parent);
+      s.BindInt64(1, child);
+      s.Run();
+    }
+
+
+    virtual void ClearChanges() ORTHANC_OVERRIDE
+    {
+      ClearTable("Changes");
+    }
+
+    virtual void ClearExportedResources() ORTHANC_OVERRIDE
+    {
+      ClearTable("ExportedResources");
+    }
 
 
-  void SQLiteDatabaseWrapper::GetChildren(std::list<std::string>& childrenPublicIds,
-                                          int64_t id)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE parentId=?");
-    s.BindInt64(0, id);
+    virtual void ClearMainDicomTags(int64_t id) ORTHANC_OVERRIDE
+    {
+      {
+        SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM DicomIdentifiers WHERE id=?");
+        s.BindInt64(0, id);
+        s.Run();
+      }
+
+      {
+        SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM MainDicomTags WHERE id=?");
+        s.BindInt64(0, id);
+        s.Run();
+      }
+    }
+
+
+    virtual bool CreateInstance(CreateInstanceResult& result,
+                                int64_t& instanceId,
+                                const std::string& patient,
+                                const std::string& study,
+                                const std::string& series,
+                                const std::string& instance) ORTHANC_OVERRIDE
+    {
+      return ICreateInstance::Apply
+        (*this, result, instanceId, patient, study, series, instance);
+    }
+
 
-    childrenPublicIds.clear();
-    while (s.Step())
+    virtual int64_t CreateResource(const std::string& publicId,
+                                   ResourceType type) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(NULL, ?, ?, NULL)");
+      s.BindInt(0, type);
+      s.BindString(1, publicId);
+      s.Run();
+      return db_.GetLastInsertRowId();
+    }
+
+
+    virtual void DeleteAttachment(int64_t id,
+                                  FileContentType attachment) ORTHANC_OVERRIDE
     {
-      childrenPublicIds.push_back(s.ColumnString(0));
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM AttachedFiles WHERE id=? AND fileType=?");
+      s.BindInt64(0, id);
+      s.BindInt(1, attachment);
+      s.Run();
     }
-  }
+
+
+    virtual void DeleteMetadata(int64_t id,
+                                MetadataType type) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Metadata WHERE id=? and type=?");
+      s.BindInt64(0, id);
+      s.BindInt(1, type);
+      s.Run();
+    }
 
 
-  void SQLiteDatabaseWrapper::DeleteResource(int64_t id)
-  {
-    signalRemainingAncestor_->Reset();
+    virtual void DeleteResource(int64_t id) ORTHANC_OVERRIDE
+    {
+      signalRemainingAncestor_.Reset();
+
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Resources WHERE internalId=?");
+      s.BindInt64(0, id);
+      s.Run();
+
+      if (signalRemainingAncestor_.HasRemainingAncestor())
+      {
+        listener_.SignalRemainingAncestor(signalRemainingAncestor_.GetRemainingAncestorType(),
+                                          signalRemainingAncestor_.GetRemainingAncestorId());
+      }
+    }
+
 
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Resources WHERE internalId=?");
-    s.BindInt64(0, id);
-    s.Run();
+    virtual void GetAllMetadata(std::map<MetadataType, std::string>& target,
+                                int64_t id) ORTHANC_OVERRIDE
+    {
+      target.clear();
+
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type, value FROM Metadata WHERE id=?");
+      s.BindInt64(0, id);
+
+      while (s.Step())
+      {
+        MetadataType key = static_cast<MetadataType>(s.ColumnInt(0));
+        target[key] = s.ColumnString(1);
+      }
+    }
+
 
-    if (signalRemainingAncestor_->HasRemainingAncestor() &&
-        listener_ != NULL)
+    virtual void GetAllPublicIds(std::list<std::string>& target,
+                                 ResourceType resourceType) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=?");
+      s.BindInt(0, resourceType);
+
+      target.clear();
+      while (s.Step())
+      {
+        target.push_back(s.ColumnString(0));
+      }
+    }
+
+
+    virtual void GetAllPublicIds(std::list<std::string>& target,
+                                 ResourceType resourceType,
+                                 size_t since,
+                                 size_t limit) ORTHANC_OVERRIDE
     {
-      listener_->SignalRemainingAncestor(signalRemainingAncestor_->GetRemainingAncestorType(),
-                                         signalRemainingAncestor_->GetRemainingAncestorId());
+      if (limit == 0)
+      {
+        target.clear();
+        return;
+      }
+
+      SQLite::Statement s(db_, SQLITE_FROM_HERE,
+                          "SELECT publicId FROM Resources WHERE "
+                          "resourceType=? LIMIT ? OFFSET ?");
+      s.BindInt(0, resourceType);
+      s.BindInt64(1, limit);
+      s.BindInt64(2, since);
+
+      target.clear();
+      while (s.Step())
+      {
+        target.push_back(s.ColumnString(0));
+      }
     }
-  }
 
 
-  bool SQLiteDatabaseWrapper::GetParentPublicId(std::string& target,
-                                                int64_t id)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b "
-                        "WHERE a.internalId = b.parentId AND b.internalId = ?");     
-    s.BindInt64(0, id);
+    virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/,
+                            bool& done /*out*/,
+                            int64_t since,
+                            uint32_t maxResults) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? ORDER BY seq LIMIT ?");
+      s.BindInt64(0, since);
+      s.BindInt(1, maxResults + 1);
+      GetChangesInternal(target, done, s, maxResults);
+    }
 
-    if (s.Step())
+
+    virtual void GetChildrenMetadata(std::list<std::string>& target,
+                                     int64_t resourceId,
+                                     MetadataType metadata) ORTHANC_OVERRIDE
+    {
+      IGetChildrenMetadata::Apply(*this, target, resourceId, metadata);
+    }
+
+
+    virtual void GetChildrenInternalId(std::list<int64_t>& target,
+                                       int64_t id) ORTHANC_OVERRIDE
     {
-      target = s.ColumnString(0);
-      return true;
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.internalId FROM Resources AS a, Resources AS b  "
+                          "WHERE a.parentId = b.internalId AND b.internalId = ?");     
+      s.BindInt64(0, id);
+
+      target.clear();
+
+      while (s.Step())
+      {
+        target.push_back(s.ColumnInt64(0));
+      }
     }
-    else
+
+
+    virtual void GetChildrenPublicId(std::list<std::string>& target,
+                                     int64_t id) ORTHANC_OVERRIDE
     {
-      return false;
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b  "
+                          "WHERE a.parentId = b.internalId AND b.internalId = ?");     
+      s.BindInt64(0, id);
+
+      target.clear();
+
+      while (s.Step())
+      {
+        target.push_back(s.ColumnString(0));
+      }
     }
-  }
+
+
+    virtual void GetExportedResources(std::list<ExportedResource>& target,
+                                      bool& done,
+                                      int64_t since,
+                                      uint32_t maxResults) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                          "SELECT * FROM ExportedResources WHERE seq>? ORDER BY seq LIMIT ?");
+      s.BindInt64(0, since);
+      s.BindInt(1, maxResults + 1);
+      GetExportedResourcesInternal(target, done, s, maxResults);
+    }
+
+
+    virtual void GetLastChange(std::list<ServerIndexChange>& target /*out*/) ORTHANC_OVERRIDE
+    {
+      bool done;  // Ignored
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1");
+      GetChangesInternal(target, done, s, 1);
+    }
 
 
-  int64_t SQLiteDatabaseWrapper::GetTableRecordCount(const std::string& table)
-  {
-    /**
-     * "Generally one cannot use SQL parameters/placeholders for
-     * database identifiers (tables, columns, views, schemas, etc.) or
-     * database functions (e.g., CURRENT_DATE), but instead only for
-     * binding literal values." => To avoid any SQL injection, we
-     * check that the "table" parameter has only alphabetic
-     * characters.
-     * https://stackoverflow.com/a/1274764/881731
-     **/
-    for (size_t i = 0; i < table.size(); i++)
+    int64_t GetLastChangeIndex() ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                          "SELECT seq FROM sqlite_sequence WHERE name='Changes'");
+
+      if (s.Step())
+      {
+        int64_t c = s.ColumnInt(0);
+        assert(!s.Step());
+        return c;
+      }
+      else
+      {
+        // No change has been recorded so far in the database
+        return 0;
+      }
+    }
+
+    
+    virtual void GetLastExportedResource(std::list<ExportedResource>& target) ORTHANC_OVERRIDE
+    {
+      bool done;  // Ignored
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                          "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1");
+      GetExportedResourcesInternal(target, done, s, 1);
+    }
+
+
+    virtual void GetMainDicomTags(DicomMap& map,
+                                  int64_t id) ORTHANC_OVERRIDE
     {
-      if (!isalpha(table[i]))
+      map.Clear();
+
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM MainDicomTags WHERE id=?");
+      s.BindInt64(0, id);
+      while (s.Step())
       {
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
+        map.SetValue(s.ColumnInt(1),
+                     s.ColumnInt(2),
+                     s.ColumnString(3), false);
+      }
+    }
+
+
+    virtual std::string GetPublicId(int64_t resourceId) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                          "SELECT publicId FROM Resources WHERE internalId=?");
+      s.BindInt64(0, resourceId);
+    
+      if (s.Step())
+      { 
+        return s.ColumnString(0);
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_UnknownResource);
+      }
+    }
+
+
+    virtual uint64_t GetResourceCount(ResourceType resourceType) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                          "SELECT COUNT(*) FROM Resources WHERE resourceType=?");
+      s.BindInt(0, resourceType);
+    
+      if (!s.Step())
+      {
+        return 0;
+      }
+      else
+      {
+        int64_t c = s.ColumnInt(0);
+        assert(!s.Step());
+        return c;
       }
     }
 
-    // Don't use "SQLITE_FROM_HERE", otherwise "table" would be cached
-    SQLite::Statement s(db_, "SELECT COUNT(*) FROM " + table);
 
-    if (s.Step())
+    virtual ResourceType GetResourceType(int64_t resourceId) ORTHANC_OVERRIDE
     {
-      int64_t c = s.ColumnInt(0);
-      assert(!s.Step());
-      return c;
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                          "SELECT resourceType FROM Resources WHERE internalId=?");
+      s.BindInt64(0, resourceId);
+    
+      if (s.Step())
+      {
+        return static_cast<ResourceType>(s.ColumnInt(0));
+      }
+      else
+      { 
+        throw OrthancException(ErrorCode_UnknownResource);
+      }
     }
-    else
+
+
+    virtual uint64_t GetTotalCompressedSize() ORTHANC_OVERRIDE
     {
-      throw OrthancException(ErrorCode_InternalError);
+      // Old SQL query that was used in Orthanc <= 1.5.0:
+      // SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(compressedSize) FROM AttachedFiles");
+
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT value FROM GlobalIntegers WHERE key=0");
+      s.Run();
+      return static_cast<uint64_t>(s.ColumnInt64(0));
     }
-  }
 
     
+    virtual uint64_t GetTotalUncompressedSize() ORTHANC_OVERRIDE
+    {
+      // Old SQL query that was used in Orthanc <= 1.5.0:
+      // SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(uncompressedSize) FROM AttachedFiles");
+
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT value FROM GlobalIntegers WHERE key=1");
+      s.Run();
+      return static_cast<uint64_t>(s.ColumnInt64(0));
+    }
+
+
+    virtual bool IsDiskSizeAbove(uint64_t threshold) ORTHANC_OVERRIDE
+    {
+      return GetTotalCompressedSize() > threshold;
+    }
+
+
+    virtual bool IsExistingResource(int64_t internalId) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                          "SELECT * FROM Resources WHERE internalId=?");
+      s.BindInt64(0, internalId);
+      return s.Step();
+    }
+
+
+    virtual bool IsProtectedPatient(int64_t internalId) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE,
+                          "SELECT * FROM PatientRecyclingOrder WHERE patientId = ?");
+      s.BindInt64(0, internalId);
+      return !s.Step();
+    }
+
+
+    virtual void ListAvailableAttachments(std::set<FileContentType>& target,
+                                          int64_t id) ORTHANC_OVERRIDE
+    {
+      target.clear();
+
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                          "SELECT fileType FROM AttachedFiles WHERE id=?");
+      s.BindInt64(0, id);
+
+      while (s.Step())
+      {
+        target.insert(static_cast<FileContentType>(s.ColumnInt(0)));
+      }
+    }
+
+
+    virtual void LogChange(int64_t internalId,
+                           const ServerIndexChange& change) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)");
+      s.BindInt(0, change.GetChangeType());
+      s.BindInt64(1, internalId);
+      s.BindInt(2, change.GetResourceType());
+      s.BindString(3, change.GetDate());
+      s.Run();
+    }
+
+
+    virtual void LogExportedResource(const ExportedResource& resource) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                          "INSERT INTO ExportedResources VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?)");
+
+      s.BindInt(0, resource.GetResourceType());
+      s.BindString(1, resource.GetPublicId());
+      s.BindString(2, resource.GetModality());
+      s.BindString(3, resource.GetPatientId());
+      s.BindString(4, resource.GetStudyInstanceUid());
+      s.BindString(5, resource.GetSeriesInstanceUid());
+      s.BindString(6, resource.GetSopInstanceUid());
+      s.BindString(7, resource.GetDate());
+      s.Run();      
+    }
+
+
+    virtual bool LookupAttachment(FileInfo& attachment,
+                                  int64_t id,
+                                  FileContentType contentType) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                          "SELECT uuid, uncompressedSize, compressionType, compressedSize, "
+                          "uncompressedMD5, compressedMD5 FROM AttachedFiles WHERE id=? AND fileType=?");
+      s.BindInt64(0, id);
+      s.BindInt(1, contentType);
+
+      if (!s.Step())
+      {
+        return false;
+      }
+      else
+      {
+        attachment = FileInfo(s.ColumnString(0),
+                              contentType,
+                              s.ColumnInt64(1),
+                              s.ColumnString(4),
+                              static_cast<CompressionType>(s.ColumnInt(2)),
+                              s.ColumnInt64(3),
+                              s.ColumnString(5));
+        return true;
+      }
+    }
+
+
+    virtual bool LookupGlobalProperty(std::string& target,
+                                      GlobalProperty property) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                          "SELECT value FROM GlobalProperties WHERE property=?");
+      s.BindInt(0, property);
+
+      if (!s.Step())
+      {
+        return false;
+      }
+      else
+      {
+        target = s.ColumnString(0);
+        return true;
+      }
+    }
+
+
+    virtual bool LookupMetadata(std::string& target,
+                                int64_t id,
+                                MetadataType type) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                          "SELECT value FROM Metadata WHERE id=? AND type=?");
+      s.BindInt64(0, id);
+      s.BindInt(1, type);
+
+      if (!s.Step())
+      {
+        return false;
+      }
+      else
+      {
+        target = s.ColumnString(0);
+        return true;
+      }
+    }
+
+
+    virtual bool LookupParent(int64_t& parentId,
+                              int64_t resourceId) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                          "SELECT parentId FROM Resources WHERE internalId=?");
+      s.BindInt64(0, resourceId);
+
+      if (!s.Step())
+      {
+        throw OrthancException(ErrorCode_UnknownResource);
+      }
+
+      if (s.ColumnIsNull(0))
+      {
+        return false;
+      }
+      else
+      {
+        parentId = s.ColumnInt(0);
+        return true;
+      }
+    }
+
+
+    virtual bool LookupResourceAndParent(int64_t& id,
+                                         ResourceType& type,
+                                         std::string& parentPublicId,
+                                         const std::string& publicId) ORTHANC_OVERRIDE
+    {
+      return ILookupResourceAndParent::Apply(*this, id, type, parentPublicId, publicId);
+    }
+
+
+    virtual bool LookupResource(int64_t& id,
+                                ResourceType& type,
+                                const std::string& publicId) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                          "SELECT internalId, resourceType FROM Resources WHERE publicId=?");
+      s.BindString(0, publicId);
+
+      if (!s.Step())
+      {
+        return false;
+      }
+      else
+      {
+        id = s.ColumnInt(0);
+        type = static_cast<ResourceType>(s.ColumnInt(1));
+
+        // Check whether there is a single resource with this public id
+        assert(!s.Step());
+
+        return true;
+      }
+    }
+
+
+    virtual bool SelectPatientToRecycle(int64_t& internalId) ORTHANC_OVERRIDE
+    {
+      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;
+      }    
+    }
+
+
+    virtual bool SelectPatientToRecycle(int64_t& internalId,
+                                        int64_t patientIdToAvoid) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE,
+                          "SELECT patientId FROM PatientRecyclingOrder "
+                          "WHERE patientId != ? ORDER BY seq ASC LIMIT 1");
+      s.BindInt64(0, patientIdToAvoid);
+
+      if (!s.Step())
+      {
+        // No patient remaining or all the patients are protected
+        return false;
+      }
+      else
+      {
+        internalId = s.ColumnInt(0);
+        return true;
+      }   
+    }
+
+
+    virtual void SetGlobalProperty(GlobalProperty property,
+                                   const std::string& value) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO GlobalProperties VALUES(?, ?)");
+      s.BindInt(0, property);
+      s.BindString(1, value);
+      s.Run();
+    }
+
+
+    virtual void SetIdentifierTag(int64_t id,
+                                  const DicomTag& tag,
+                                  const std::string& value) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers VALUES(?, ?, ?, ?)");
+      s.BindInt64(0, id);
+      s.BindInt(1, tag.GetGroup());
+      s.BindInt(2, tag.GetElement());
+      s.BindString(3, value);
+      s.Run();
+    }
+
+
+    virtual void SetProtectedPatient(int64_t internalId, 
+                                     bool isProtected) ORTHANC_OVERRIDE
+    {
+      if (isProtected)
+      {
+        SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM PatientRecyclingOrder WHERE patientId=?");
+        s.BindInt64(0, internalId);
+        s.Run();
+      }
+      else if (IsProtectedPatient(internalId))
+      {
+        SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)");
+        s.BindInt64(0, internalId);
+        s.Run();
+      }
+      else
+      {
+        // Nothing to do: The patient is already unprotected
+      }
+    }
+
+
+    virtual void SetMainDicomTag(int64_t id,
+                                 const DicomTag& tag,
+                                 const std::string& value) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)");
+      s.BindInt64(0, id);
+      s.BindInt(1, tag.GetGroup());
+      s.BindInt(2, tag.GetElement());
+      s.BindString(3, value);
+      s.Run();
+    }
+
+
+    virtual void SetMetadata(int64_t id,
+                             MetadataType type,
+                             const std::string& value) ORTHANC_OVERRIDE
+    {
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO Metadata VALUES(?, ?, ?)");
+      s.BindInt64(0, id);
+      s.BindInt(1, type);
+      s.BindString(2, value);
+      s.Run();
+    }
+
+
+    virtual void SetResourcesContent(const Orthanc::ResourcesContent& content) ORTHANC_OVERRIDE
+    {
+      ISetResourcesContent::Apply(*this, content);
+    }
+
+
+    virtual void TagMostRecentPatient(int64_t patient) ORTHANC_OVERRIDE
+    {
+      {
+        SQLite::Statement s(db_, SQLITE_FROM_HERE,
+                            "DELETE FROM PatientRecyclingOrder WHERE patientId=?");
+        s.BindInt64(0, patient);
+        s.Run();
+
+        assert(db_.GetLastChangeCount() == 0 ||
+               db_.GetLastChangeCount() == 1);
+      
+        if (db_.GetLastChangeCount() == 0)
+        {
+          // The patient was protected, there was nothing to delete from the recycling order
+          return;
+        }
+      }
+
+      {
+        SQLite::Statement s(db_, SQLITE_FROM_HERE,
+                            "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)");
+        s.BindInt64(0, patient);
+        s.Run();
+      }
+    }
+  };
+
+
+  class SQLiteDatabaseWrapper::SignalFileDeleted : public SQLite::IScalarFunction
+  {
+  private:
+    SQLiteDatabaseWrapper& sqlite_;
+
+  public:
+    SignalFileDeleted(SQLiteDatabaseWrapper& sqlite) :
+      sqlite_(sqlite)
+    {
+    }
+
+    virtual const char* GetName() const ORTHANC_OVERRIDE
+    {
+      return "SignalFileDeleted";
+    }
+
+    virtual unsigned int GetCardinality() const ORTHANC_OVERRIDE
+    {
+      return 7;
+    }
+
+    virtual void Compute(SQLite::FunctionContext& context) ORTHANC_OVERRIDE
+    {
+      if (sqlite_.activeTransaction_ != NULL)
+      {
+        std::string uncompressedMD5, compressedMD5;
+
+        if (!context.IsNullValue(5))
+        {
+          uncompressedMD5 = context.GetStringValue(5);
+        }
+
+        if (!context.IsNullValue(6))
+        {
+          compressedMD5 = context.GetStringValue(6);
+        }
+
+        FileInfo info(context.GetStringValue(0),
+                      static_cast<FileContentType>(context.GetIntValue(1)),
+                      static_cast<uint64_t>(context.GetInt64Value(2)),
+                      uncompressedMD5,
+                      static_cast<CompressionType>(context.GetIntValue(3)),
+                      static_cast<uint64_t>(context.GetInt64Value(4)),
+                      compressedMD5);
+
+        sqlite_.activeTransaction_->GetListener().SignalAttachmentDeleted(info);
+      }
+    }
+  };
+    
+
+  class SQLiteDatabaseWrapper::SignalResourceDeleted : public SQLite::IScalarFunction
+  {
+  private:
+    SQLiteDatabaseWrapper& sqlite_;
+
+  public:
+    SignalResourceDeleted(SQLiteDatabaseWrapper& sqlite) :
+      sqlite_(sqlite)
+    {
+    }
+
+    virtual const char* GetName() const ORTHANC_OVERRIDE
+    {
+      return "SignalResourceDeleted";
+    }
+
+    virtual unsigned int GetCardinality() const ORTHANC_OVERRIDE
+    {
+      return 2;
+    }
+
+    virtual void Compute(SQLite::FunctionContext& context) ORTHANC_OVERRIDE
+    {
+      if (sqlite_.activeTransaction_ != NULL)
+      {
+        sqlite_.activeTransaction_->GetListener().
+          SignalResourceDeleted(static_cast<ResourceType>(context.GetIntValue(1)),
+                                context.GetStringValue(0));
+      }
+    }
+  };
+
+  
+  class SQLiteDatabaseWrapper::ReadWriteTransaction : public SQLiteDatabaseWrapper::TransactionBase
+  {
+  private:
+    SQLiteDatabaseWrapper&                that_;
+    std::unique_ptr<SQLite::Transaction>  transaction_;
+    int64_t                               initialDiskSize_;
+
+  public:
+    ReadWriteTransaction(SQLiteDatabaseWrapper& that,
+                         IDatabaseListener& listener) :
+      TransactionBase(that.db_, listener, *that.signalRemainingAncestor_),
+      that_(that),
+      transaction_(new SQLite::Transaction(that_.db_))
+    {
+      if (that_.activeTransaction_ != NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+      
+      that_.activeTransaction_ = this;
+
+#if defined(NDEBUG)
+      // Release mode
+      initialDiskSize_ = 0;
+#else
+      // Debug mode
+      initialDiskSize_ = static_cast<int64_t>(GetTotalCompressedSize());
+#endif
+    }
+
+    virtual ~ReadWriteTransaction()
+    {
+      assert(that_.activeTransaction_ != NULL);    
+      that_.activeTransaction_ = NULL;
+    }
+
+    void Begin()
+    {
+      transaction_->Begin();
+    }
+
+    virtual void Rollback() ORTHANC_OVERRIDE
+    {
+      transaction_->Rollback();
+    }
+
+    virtual void Commit(int64_t fileSizeDelta /* only used in debug */) ORTHANC_OVERRIDE
+    {
+      transaction_->Commit();
+
+      assert(initialDiskSize_ + fileSizeDelta >= 0 &&
+             initialDiskSize_ + fileSizeDelta == static_cast<int64_t>(GetTotalCompressedSize()));
+    }
+  };
+
+
+  class SQLiteDatabaseWrapper::ReadOnlyTransaction : public SQLiteDatabaseWrapper::TransactionBase
+  {
+  private:
+    SQLiteDatabaseWrapper&  that_;
+    
+  public:
+    ReadOnlyTransaction(SQLiteDatabaseWrapper& that,
+                        IDatabaseListener& listener) :
+      TransactionBase(that.db_, listener, *that.signalRemainingAncestor_),
+      that_(that)
+    {
+      if (that_.activeTransaction_ != NULL)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+      
+      that_.activeTransaction_ = this;
+    }
+
+    virtual ~ReadOnlyTransaction()
+    {
+      assert(that_.activeTransaction_ != NULL);    
+      that_.activeTransaction_ = NULL;
+    }
+
+    virtual void Rollback() ORTHANC_OVERRIDE
+    {
+    }
+
+    virtual void Commit(int64_t fileSizeDelta /* only used in debug */) ORTHANC_OVERRIDE
+    {
+      if (fileSizeDelta != 0)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+  };
+  
+
   SQLiteDatabaseWrapper::SQLiteDatabaseWrapper(const std::string& path) : 
-    listener_(NULL), 
+    activeTransaction_(NULL), 
     signalRemainingAncestor_(NULL),
     version_(0)
   {
@@ -341,41 +1251,33 @@
 
 
   SQLiteDatabaseWrapper::SQLiteDatabaseWrapper() : 
-    listener_(NULL), 
+    activeTransaction_(NULL), 
     signalRemainingAncestor_(NULL),
     version_(0)
   {
     db_.OpenInMemory();
   }
 
-
-  int SQLiteDatabaseWrapper::GetGlobalIntegerProperty(GlobalProperty property,
-                                                      int defaultValue)
+  SQLiteDatabaseWrapper::~SQLiteDatabaseWrapper()
   {
-    std::string tmp;
-
-    if (!LookupGlobalProperty(tmp, GlobalProperty_DatabasePatchLevel))
+    if (activeTransaction_ != NULL)
     {
-      return defaultValue;
-    }
-    else
-    {
-      try
-      {
-        return boost::lexical_cast<int>(tmp);
-      }
-      catch (boost::bad_lexical_cast&)
-      {
-        throw OrthancException(ErrorCode_ParameterOutOfRange,
-                               "Global property " + boost::lexical_cast<std::string>(property) +
-                               " should be an integer, but found: " + tmp);
-      }
+      LOG(ERROR) << "A SQLite transaction is still active in the SQLiteDatabaseWrapper destructor: Expect a crash";
     }
   }
 
 
   void SQLiteDatabaseWrapper::Open()
   {
+    if (signalRemainingAncestor_ != NULL)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);  // Cannot open twice
+    }
+    
+    signalRemainingAncestor_ = dynamic_cast<SignalRemainingAncestor*>(db_.Register(new SignalRemainingAncestor));
+    db_.Register(new SignalFileDeleted(*this));
+    db_.Register(new SignalResourceDeleted(*this));
+    
     db_.Execute("PRAGMA ENCODING=\"UTF-8\";");
 
     // Performance tuning of SQLite with PRAGMAs
@@ -388,10 +1290,11 @@
 
     // Make "LIKE" case-sensitive in SQLite 
     db_.Execute("PRAGMA case_sensitive_like = true;");
-    
+
+    VoidDatabaseListener listener;
+      
     {
-      SQLite::Transaction t(db_);
-      t.Begin();
+      std::unique_ptr<ITransaction> transaction(StartTransaction(TransactionType_ReadOnly, listener));
 
       if (!db_.DoesTableExist("GlobalProperties"))
       {
@@ -403,7 +1306,7 @@
 
       // Check the version of the database
       std::string tmp;
-      if (!LookupGlobalProperty(tmp, GlobalProperty_DatabaseSchemaVersion))
+      if (!transaction->LookupGlobalProperty(tmp, GlobalProperty_DatabaseSchemaVersion))
       {
         tmp = "Unknown";
       }
@@ -428,7 +1331,7 @@
       // New in Orthanc 1.5.1
       if (version_ == 6)
       {
-        if (!LookupGlobalProperty(tmp, GlobalProperty_GetTotalSizeIsFast) ||
+        if (!transaction->LookupGlobalProperty(tmp, GlobalProperty_GetTotalSizeIsFast) ||
             tmp != "1")
         {
           LOG(INFO) << "Installing the SQLite triggers to track the size of the attachments";
@@ -438,12 +1341,8 @@
         }
       }
 
-      t.Commit();
+      transaction->Commit(0);
     }
-
-    signalRemainingAncestor_ = dynamic_cast<SignalRemainingAncestor*>(db_.Register(new SignalRemainingAncestor));
-    db_.Register(new SignalFileDeleted(*this));
-    db_.Register(new SignalResourceDeleted(*this));
   }
 
 
@@ -496,188 +1395,25 @@
       // No change in the DB schema, the step from version 5 to 6 only
       // consists in reconstructing the main DICOM tags information
       // (as more tags got included).
-      db_.BeginTransaction();
-      ServerToolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Patient);
-      ServerToolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Study);
-      ServerToolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Series);
-      ServerToolbox::ReconstructMainDicomTags(*this, storageArea, ResourceType_Instance);
-      db_.Execute("UPDATE GlobalProperties SET value=\"6\" WHERE property=" +
-                  boost::lexical_cast<std::string>(GlobalProperty_DatabaseSchemaVersion) + ";");
-      db_.CommitTransaction();
+
+      VoidDatabaseListener listener;
+      
+      {
+        std::unique_ptr<ITransaction> transaction(StartTransaction(TransactionType_ReadWrite, listener));
+        ServerToolbox::ReconstructMainDicomTags(*transaction, storageArea, ResourceType_Patient);
+        ServerToolbox::ReconstructMainDicomTags(*transaction, storageArea, ResourceType_Study);
+        ServerToolbox::ReconstructMainDicomTags(*transaction, storageArea, ResourceType_Series);
+        ServerToolbox::ReconstructMainDicomTags(*transaction, storageArea, ResourceType_Instance);
+        db_.Execute("UPDATE GlobalProperties SET value=\"6\" WHERE property=" +
+                    boost::lexical_cast<std::string>(GlobalProperty_DatabaseSchemaVersion) + ";");
+        transaction->Commit(0);
+      }
+      
       version_ = 6;
     }
   }
 
 
-  void SQLiteDatabaseWrapper::ClearTable(const std::string& tableName)
-  {
-    db_.Execute("DELETE FROM " + tableName);    
-  }
-
-
-  bool SQLiteDatabaseWrapper::LookupParent(int64_t& parentId,
-                                           int64_t resourceId)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT parentId FROM Resources WHERE internalId=?");
-    s.BindInt64(0, resourceId);
-
-    if (!s.Step())
-    {
-      throw OrthancException(ErrorCode_UnknownResource);
-    }
-
-    if (s.ColumnIsNull(0))
-    {
-      return false;
-    }
-    else
-    {
-      parentId = s.ColumnInt(0);
-      return true;
-    }
-  }
-
-
-  ResourceType SQLiteDatabaseWrapper::GetResourceType(int64_t resourceId)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT resourceType FROM Resources WHERE internalId=?");
-    s.BindInt64(0, resourceId);
-    
-    if (s.Step())
-    {
-      return static_cast<ResourceType>(s.ColumnInt(0));
-    }
-    else
-    { 
-      throw OrthancException(ErrorCode_UnknownResource);
-    }
-  }
-
-
-  std::string SQLiteDatabaseWrapper::GetPublicId(int64_t resourceId)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT publicId FROM Resources WHERE internalId=?");
-    s.BindInt64(0, resourceId);
-    
-    if (s.Step())
-    { 
-      return s.ColumnString(0);
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_UnknownResource);
-    }
-  }
-
-
-  void SQLiteDatabaseWrapper::GetChanges(std::list<ServerIndexChange>& target /*out*/,
-                                         bool& done /*out*/,
-                                         int64_t since,
-                                         uint32_t maxResults)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes WHERE seq>? ORDER BY seq LIMIT ?");
-    s.BindInt64(0, since);
-    s.BindInt(1, maxResults + 1);
-    GetChangesInternal(target, done, s, maxResults);
-  }
-
-
-  void SQLiteDatabaseWrapper::GetLastChange(std::list<ServerIndexChange>& target /*out*/)
-  {
-    bool done;  // Ignored
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM Changes ORDER BY seq DESC LIMIT 1");
-    GetChangesInternal(target, done, s, 1);
-  }
-
-
-  class SQLiteDatabaseWrapper::ReadWriteTransaction : public IDatabaseWrapper::ITransaction
-  {
-  private:
-    SQLiteDatabaseWrapper&                that_;
-    std::unique_ptr<SQLite::Transaction>  transaction_;
-    int64_t                               initialDiskSize_;
-
-  public:
-    ReadWriteTransaction(SQLiteDatabaseWrapper& that,
-                         IDatabaseListener& listener) :
-      that_(that),
-      transaction_(new SQLite::Transaction(that_.db_))
-    {
-      assert(that_.listener_ == NULL);
-      that_.listener_ = &listener;   // TODO - STORE IN TRANSACTION
-
-#if defined(NDEBUG)
-      // Release mode
-      initialDiskSize_ = 0;
-#else
-      // Debug mode
-      initialDiskSize_ = static_cast<int64_t>(that_.GetTotalCompressedSize());
-#endif
-    }
-
-    virtual ~ReadWriteTransaction()
-    {
-      assert(that_.listener_ != NULL);    
-      that_.listener_ = NULL;   // TODO - STORE IN TRANSACTION
-    }
-
-    void Begin()
-    {
-      transaction_->Begin();
-    }
-
-    virtual void Rollback() ORTHANC_OVERRIDE
-    {
-      transaction_->Rollback();
-    }
-
-    virtual void Commit(int64_t fileSizeDelta /* only used in debug */) ORTHANC_OVERRIDE
-    {
-      transaction_->Commit();
-
-      assert(initialDiskSize_ + fileSizeDelta >= 0 &&
-             initialDiskSize_ + fileSizeDelta == static_cast<int64_t>(that_.GetTotalCompressedSize()));
-    }
-  };
-
-
-  class SQLiteDatabaseWrapper::ReadOnlyTransaction : public IDatabaseWrapper::ITransaction
-  {
-  private:
-    SQLiteDatabaseWrapper&  that_;
-    
-  public:
-    ReadOnlyTransaction(SQLiteDatabaseWrapper& that,
-                        IDatabaseListener& listener) :
-      that_(that)
-    {
-      assert(that_.listener_ == NULL);
-      that_.listener_ = &listener;
-    }
-
-    virtual ~ReadOnlyTransaction()
-    {
-      assert(that_.listener_ != NULL);    
-      that_.listener_ = NULL;
-    }
-
-    virtual void Rollback() ORTHANC_OVERRIDE
-    {
-    }
-
-    virtual void Commit(int64_t fileSizeDelta /* only used in debug */) ORTHANC_OVERRIDE
-    {
-      if (fileSizeDelta != 0)
-      {
-        throw OrthancException(ErrorCode_InternalError);
-      }
-    }
-  };
-
-
   IDatabaseWrapper::ITransaction* SQLiteDatabaseWrapper::StartTransaction(TransactionType type,
                                                                           IDatabaseListener& listener)
   {
@@ -699,54 +1435,9 @@
     }
   }
 
-
-  void SQLiteDatabaseWrapper::GetAllMetadata(std::map<MetadataType, std::string>& target,
-                                             int64_t id)
-  {
-    target.clear();
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type, value FROM Metadata WHERE id=?");
-    s.BindInt64(0, id);
-
-    while (s.Step())
-    {
-      MetadataType key = static_cast<MetadataType>(s.ColumnInt(0));
-      target[key] = s.ColumnString(1);
-    }
-  }
-
-
-  void SQLiteDatabaseWrapper::SetGlobalProperty(GlobalProperty property,
-                                                const std::string& value)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO GlobalProperties VALUES(?, ?)");
-    s.BindInt(0, property);
-    s.BindString(1, value);
-    s.Run();
-  }
-
-
-  bool SQLiteDatabaseWrapper::LookupGlobalProperty(std::string& target,
-                                                   GlobalProperty property)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT value FROM GlobalProperties WHERE property=?");
-    s.BindInt(0, property);
-
-    if (!s.Step())
-    {
-      return false;
-    }
-    else
-    {
-      target = s.ColumnString(0);
-      return true;
-    }
-  }
-
-
-  int64_t SQLiteDatabaseWrapper::CreateResource(const std::string& publicId,
-                                                ResourceType type)
+  
+  int64_t SQLiteDatabaseWrapper::UnitTestsTransaction::CreateResource(const std::string& publicId,
+                                                                      ResourceType type)
   {
     SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(NULL, ?, ?, NULL)");
     s.BindInt(0, type);
@@ -756,33 +1447,8 @@
   }
 
 
-  bool SQLiteDatabaseWrapper::LookupResource(int64_t& id,
-                                             ResourceType& type,
-                                             const std::string& publicId)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT internalId, resourceType FROM Resources WHERE publicId=?");
-    s.BindString(0, publicId);
-
-    if (!s.Step())
-    {
-      return false;
-    }
-    else
-    {
-      id = s.ColumnInt(0);
-      type = static_cast<ResourceType>(s.ColumnInt(1));
-
-      // Check whether there is a single resource with this public id
-      assert(!s.Step());
-
-      return true;
-    }
-  }
-
-
-  void SQLiteDatabaseWrapper::AttachChild(int64_t parent,
-                                          int64_t child)
+  void SQLiteDatabaseWrapper::UnitTestsTransaction::AttachChild(int64_t parent,
+                                                                int64_t child)
   {
     SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Resources SET parentId = ? WHERE internalId = ?");
     s.BindInt64(0, parent);
@@ -791,150 +1457,9 @@
   }
 
 
-  void SQLiteDatabaseWrapper::SetMetadata(int64_t id,
-                                          MetadataType type,
-                                          const std::string& value)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO Metadata VALUES(?, ?, ?)");
-    s.BindInt64(0, id);
-    s.BindInt(1, type);
-    s.BindString(2, value);
-    s.Run();
-  }
-
-
-  void SQLiteDatabaseWrapper::DeleteMetadata(int64_t id,
-                                             MetadataType type)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Metadata WHERE id=? and type=?");
-    s.BindInt64(0, id);
-    s.BindInt(1, type);
-    s.Run();
-  }
-
-
-  bool SQLiteDatabaseWrapper::LookupMetadata(std::string& target,
-                                             int64_t id,
-                                             MetadataType type)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT value FROM Metadata WHERE id=? AND type=?");
-    s.BindInt64(0, id);
-    s.BindInt(1, type);
-
-    if (!s.Step())
-    {
-      return false;
-    }
-    else
-    {
-      target = s.ColumnString(0);
-      return true;
-    }
-  }
-
-
-  void SQLiteDatabaseWrapper::AddAttachment(int64_t id,
-                                            const FileInfo& attachment)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?, ?, ?)");
-    s.BindInt64(0, id);
-    s.BindInt(1, attachment.GetContentType());
-    s.BindString(2, attachment.GetUuid());
-    s.BindInt64(3, attachment.GetCompressedSize());
-    s.BindInt64(4, attachment.GetUncompressedSize());
-    s.BindInt(5, attachment.GetCompressionType());
-    s.BindString(6, attachment.GetUncompressedMD5());
-    s.BindString(7, attachment.GetCompressedMD5());
-    s.Run();
-  }
-
-
-  void SQLiteDatabaseWrapper::DeleteAttachment(int64_t id,
-                                               FileContentType attachment)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM AttachedFiles WHERE id=? AND fileType=?");
-    s.BindInt64(0, id);
-    s.BindInt(1, attachment);
-    s.Run();
-  }
-
-
-  void SQLiteDatabaseWrapper::ListAvailableAttachments(std::set<FileContentType>& target,
-                                                       int64_t id)
-  {
-    target.clear();
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT fileType FROM AttachedFiles WHERE id=?");
-    s.BindInt64(0, id);
-
-    while (s.Step())
-    {
-      target.insert(static_cast<FileContentType>(s.ColumnInt(0)));
-    }
-  }
-
-  bool SQLiteDatabaseWrapper::LookupAttachment(FileInfo& attachment,
-                                               int64_t id,
-                                               FileContentType contentType)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT uuid, uncompressedSize, compressionType, compressedSize, "
-                        "uncompressedMD5, compressedMD5 FROM AttachedFiles WHERE id=? AND fileType=?");
-    s.BindInt64(0, id);
-    s.BindInt(1, contentType);
-
-    if (!s.Step())
-    {
-      return false;
-    }
-    else
-    {
-      attachment = FileInfo(s.ColumnString(0),
-                            contentType,
-                            s.ColumnInt64(1),
-                            s.ColumnString(4),
-                            static_cast<CompressionType>(s.ColumnInt(2)),
-                            s.ColumnInt64(3),
-                            s.ColumnString(5));
-      return true;
-    }
-  }
-
-
-  void SQLiteDatabaseWrapper::ClearMainDicomTags(int64_t id)
-  {
-    {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM DicomIdentifiers WHERE id=?");
-      s.BindInt64(0, id);
-      s.Run();
-    }
-
-    {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM MainDicomTags WHERE id=?");
-      s.BindInt64(0, id);
-      s.Run();
-    }
-  }
-
-
-  void SQLiteDatabaseWrapper::SetMainDicomTag(int64_t id,
-                                              const DicomTag& tag,
-                                              const std::string& value)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)");
-    s.BindInt64(0, id);
-    s.BindInt(1, tag.GetGroup());
-    s.BindInt(2, tag.GetElement());
-    s.BindString(3, value);
-    s.Run();
-  }
-
-
-  void SQLiteDatabaseWrapper::SetIdentifierTag(int64_t id,
-                                               const DicomTag& tag,
-                                               const std::string& value)
+  void SQLiteDatabaseWrapper::UnitTestsTransaction::SetIdentifierTag(int64_t id,
+                                                                     const DicomTag& tag,
+                                                                     const std::string& value)
   {
     SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers VALUES(?, ?, ?, ?)");
     s.BindInt64(0, id);
@@ -945,427 +1470,40 @@
   }
 
 
-  void SQLiteDatabaseWrapper::GetMainDicomTags(DicomMap& map,
-                                               int64_t id)
+  void SQLiteDatabaseWrapper::UnitTestsTransaction::SetMainDicomTag(int64_t id,
+                                                                    const DicomTag& tag,
+                                                                    const std::string& value)
   {
-    map.Clear();
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT * FROM MainDicomTags WHERE id=?");
-    s.BindInt64(0, id);
-    while (s.Step())
-    {
-      map.SetValue(s.ColumnInt(1),
-                   s.ColumnInt(2),
-                   s.ColumnString(3), false);
-    }
-  }
-
-
-  void SQLiteDatabaseWrapper::GetChildrenPublicId(std::list<std::string>& target,
-                                                  int64_t id)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b  "
-                        "WHERE a.parentId = b.internalId AND b.internalId = ?");     
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)");
     s.BindInt64(0, id);
-
-    target.clear();
-
-    while (s.Step())
-    {
-      target.push_back(s.ColumnString(0));
-    }
-  }
-
-
-  void SQLiteDatabaseWrapper::GetChildrenInternalId(std::list<int64_t>& target,
-                                                    int64_t id)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.internalId FROM Resources AS a, Resources AS b  "
-                        "WHERE a.parentId = b.internalId AND b.internalId = ?");     
-    s.BindInt64(0, id);
-
-    target.clear();
-
-    while (s.Step())
-    {
-      target.push_back(s.ColumnInt64(0));
-    }
-  }
-
-
-  void SQLiteDatabaseWrapper::LogChange(int64_t internalId,
-                                        const ServerIndexChange& change)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)");
-    s.BindInt(0, change.GetChangeType());
-    s.BindInt64(1, internalId);
-    s.BindInt(2, change.GetResourceType());
-    s.BindString(3, change.GetDate());
+    s.BindInt(1, tag.GetGroup());
+    s.BindInt(2, tag.GetElement());
+    s.BindString(3, value);
     s.Run();
   }
 
 
-  void SQLiteDatabaseWrapper::LogExportedResource(const ExportedResource& resource)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "INSERT INTO ExportedResources VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?)");
-
-    s.BindInt(0, resource.GetResourceType());
-    s.BindString(1, resource.GetPublicId());
-    s.BindString(2, resource.GetModality());
-    s.BindString(3, resource.GetPatientId());
-    s.BindString(4, resource.GetStudyInstanceUid());
-    s.BindString(5, resource.GetSeriesInstanceUid());
-    s.BindString(6, resource.GetSopInstanceUid());
-    s.BindString(7, resource.GetDate());
-    s.Run();      
-  }
-
-
-  void SQLiteDatabaseWrapper::GetExportedResources(std::list<ExportedResource>& target,
-                                                   bool& done,
-                                                   int64_t since,
-                                                   uint32_t maxResults)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT * FROM ExportedResources WHERE seq>? ORDER BY seq LIMIT ?");
-    s.BindInt64(0, since);
-    s.BindInt(1, maxResults + 1);
-    GetExportedResourcesInternal(target, done, s, maxResults);
-  }
-
-    
-  void SQLiteDatabaseWrapper::GetLastExportedResource(std::list<ExportedResource>& target)
-  {
-    bool done;  // Ignored
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT * FROM ExportedResources ORDER BY seq DESC LIMIT 1");
-    GetExportedResourcesInternal(target, done, s, 1);
-  }
-
-    
-  uint64_t SQLiteDatabaseWrapper::GetTotalCompressedSize()
-  {
-    // Old SQL query that was used in Orthanc <= 1.5.0:
-    // SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(compressedSize) FROM AttachedFiles");
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT value FROM GlobalIntegers WHERE key=0");
-    s.Run();
-    return static_cast<uint64_t>(s.ColumnInt64(0));
-  }
-
-    
-  uint64_t SQLiteDatabaseWrapper::GetTotalUncompressedSize()
+  int64_t SQLiteDatabaseWrapper::UnitTestsTransaction::GetTableRecordCount(const std::string& table)
   {
-    // Old SQL query that was used in Orthanc <= 1.5.0:
-    // SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT SUM(uncompressedSize) FROM AttachedFiles");
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT value FROM GlobalIntegers WHERE key=1");
-    s.Run();
-    return static_cast<uint64_t>(s.ColumnInt64(0));
-  }
-
-
-  uint64_t SQLiteDatabaseWrapper::GetResourceCount(ResourceType resourceType)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT COUNT(*) FROM Resources WHERE resourceType=?");
-    s.BindInt(0, resourceType);
-    
-    if (!s.Step())
-    {
-      return 0;
-    }
-    else
+    /**
+     * "Generally one cannot use SQL parameters/placeholders for
+     * database identifiers (tables, columns, views, schemas, etc.) or
+     * database functions (e.g., CURRENT_DATE), but instead only for
+     * binding literal values." => To avoid any SQL injection, we
+     * check that the "table" parameter has only alphabetic
+     * characters.
+     * https://stackoverflow.com/a/1274764/881731
+     **/
+    for (size_t i = 0; i < table.size(); i++)
     {
-      int64_t c = s.ColumnInt(0);
-      assert(!s.Step());
-      return c;
-    }
-  }
-
-
-  void SQLiteDatabaseWrapper::GetAllPublicIds(std::list<std::string>& target,
-                                              ResourceType resourceType)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE resourceType=?");
-    s.BindInt(0, resourceType);
-
-    target.clear();
-    while (s.Step())
-    {
-      target.push_back(s.ColumnString(0));
-    }
-  }
-
-
-  void SQLiteDatabaseWrapper::GetAllPublicIds(std::list<std::string>& target,
-                                              ResourceType resourceType,
-                                              size_t since,
-                                              size_t limit)
-  {
-    if (limit == 0)
-    {
-      target.clear();
-      return;
+      if (!isalpha(table[i]))
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
     }
 
-    SQLite::Statement s(db_, SQLITE_FROM_HERE,
-                        "SELECT publicId FROM Resources WHERE "
-                        "resourceType=? LIMIT ? OFFSET ?");
-    s.BindInt(0, resourceType);
-    s.BindInt64(1, limit);
-    s.BindInt64(2, since);
-
-    target.clear();
-    while (s.Step())
-    {
-      target.push_back(s.ColumnString(0));
-    }
-  }
-
-
-  bool SQLiteDatabaseWrapper::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 SQLiteDatabaseWrapper::SelectPatientToRecycle(int64_t& internalId,
-                                                     int64_t patientIdToAvoid)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE,
-                        "SELECT patientId FROM PatientRecyclingOrder "
-                        "WHERE patientId != ? ORDER BY seq ASC LIMIT 1");
-    s.BindInt64(0, patientIdToAvoid);
-
-    if (!s.Step())
-    {
-      // No patient remaining or all the patients are protected
-      return false;
-    }
-    else
-    {
-      internalId = s.ColumnInt(0);
-      return true;
-    }   
-  }
-
-
-  bool SQLiteDatabaseWrapper::IsProtectedPatient(int64_t internalId)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE,
-                        "SELECT * FROM PatientRecyclingOrder WHERE patientId = ?");
-    s.BindInt64(0, internalId);
-    return !s.Step();
-  }
-
-
-  void SQLiteDatabaseWrapper::SetProtectedPatient(int64_t internalId, 
-                                                  bool isProtected)
-  {
-    if (isProtected)
-    {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM PatientRecyclingOrder WHERE patientId=?");
-      s.BindInt64(0, internalId);
-      s.Run();
-    }
-    else if (IsProtectedPatient(internalId))
-    {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)");
-      s.BindInt64(0, internalId);
-      s.Run();
-    }
-    else
-    {
-      // Nothing to do: The patient is already unprotected
-    }
-  }
-
-
-  bool SQLiteDatabaseWrapper::IsExistingResource(int64_t internalId)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT * FROM Resources WHERE internalId=?");
-    s.BindInt64(0, internalId);
-    return s.Step();
-  }
-
-
-  bool SQLiteDatabaseWrapper::IsDiskSizeAbove(uint64_t threshold)
-  {
-    return GetTotalCompressedSize() > threshold;
-  }
-
-
-
-  class SQLiteDatabaseWrapper::LookupFormatter : public ISqlLookupFormatter
-  {
-  private:
-    std::list<std::string>  values_;
-
-  public:
-    virtual std::string GenerateParameter(const std::string& value) ORTHANC_OVERRIDE
-    {
-      values_.push_back(value);
-      return "?";
-    }
-    
-    virtual std::string FormatResourceType(ResourceType level) ORTHANC_OVERRIDE
-    {
-      return boost::lexical_cast<std::string>(level);
-    }
-
-    virtual std::string FormatWildcardEscape() ORTHANC_OVERRIDE
-    {
-      return "ESCAPE '\\'";
-    }
-
-    void Bind(SQLite::Statement& statement) const
-    {
-      size_t pos = 0;
-      
-      for (std::list<std::string>::const_iterator
-             it = values_.begin(); it != values_.end(); ++it, pos++)
-      {
-        statement.BindString(pos, *it);
-      }
-    }
-  };
-
-  
-  static void AnswerLookup(std::list<std::string>& resourcesId,
-                           std::list<std::string>& instancesId,
-                           SQLite::Connection& db,
-                           ResourceType level)
-  {
-    resourcesId.clear();
-    instancesId.clear();
-    
-    std::unique_ptr<SQLite::Statement> statement;
-    
-    switch (level)
-    {
-      case ResourceType_Patient:
-      {
-        statement.reset(
-          new SQLite::Statement(
-            db, SQLITE_FROM_HERE,
-            "SELECT patients.publicId, instances.publicID FROM Lookup AS patients "
-            "INNER JOIN Resources studies ON patients.internalId=studies.parentId "
-            "INNER JOIN Resources series ON studies.internalId=series.parentId "
-            "INNER JOIN Resources instances ON series.internalId=instances.parentId "
-            "GROUP BY patients.publicId"));
-      
-        break;
-      }
-
-      case ResourceType_Study:
-      {
-        statement.reset(
-          new SQLite::Statement(
-            db, SQLITE_FROM_HERE,
-            "SELECT studies.publicId, instances.publicID FROM Lookup AS studies "
-            "INNER JOIN Resources series ON studies.internalId=series.parentId "
-            "INNER JOIN Resources instances ON series.internalId=instances.parentId "
-            "GROUP BY studies.publicId"));
-      
-        break;
-      }
-
-      case ResourceType_Series:
-      {
-        statement.reset(
-          new SQLite::Statement(
-            db, SQLITE_FROM_HERE,
-            "SELECT series.publicId, instances.publicID FROM Lookup AS series "
-            "INNER JOIN Resources instances ON series.internalId=instances.parentId "
-            "GROUP BY series.publicId"));
-      
-        break;
-      }
-
-      case ResourceType_Instance:
-      {
-        statement.reset(
-          new SQLite::Statement(
-            db, SQLITE_FROM_HERE, "SELECT publicId, publicId FROM Lookup"));
-        
-        break;
-      }
-      
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-    }
-
-    assert(statement.get() != NULL);
-      
-    while (statement->Step())
-    {
-      resourcesId.push_back(statement->ColumnString(0));
-      instancesId.push_back(statement->ColumnString(1));
-    }
-  }
-
-
-  void SQLiteDatabaseWrapper::ApplyLookupResources(std::list<std::string>& resourcesId,
-                                                   std::list<std::string>* instancesId,
-                                                   const std::vector<DatabaseConstraint>& lookup,
-                                                   ResourceType queryLevel,
-                                                   size_t limit)
-  {
-    LookupFormatter formatter;
-
-    std::string sql;
-    LookupFormatter::Apply(sql, formatter, lookup, queryLevel, limit);
-
-    sql = "CREATE TEMPORARY TABLE Lookup AS " + sql;
-    
-    {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "DROP TABLE IF EXISTS Lookup");
-      s.Run();
-    }
-
-    {
-      SQLite::Statement statement(db_, sql);
-      formatter.Bind(statement);
-      statement.Run();
-    }
-
-    if (instancesId != NULL)
-    {
-      AnswerLookup(resourcesId, *instancesId, db_, queryLevel);
-    }
-    else
-    {
-      resourcesId.clear();
-    
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Lookup");
-        
-      while (s.Step())
-      {
-        resourcesId.push_back(s.ColumnString(0));
-      }
-    }
-  }
-
-
-  int64_t SQLiteDatabaseWrapper::GetLastChangeIndex()
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                        "SELECT seq FROM sqlite_sequence WHERE name='Changes'");
+    // Don't use "SQLITE_FROM_HERE", otherwise "table" would be cached
+    SQLite::Statement s(db_, "SELECT COUNT(*) FROM " + table);
 
     if (s.Step())
     {
@@ -1375,35 +1513,40 @@
     }
     else
     {
-      // No change has been recorded so far in the database
-      return 0;
+      throw OrthancException(ErrorCode_InternalError);
     }
   }
 
 
-  void SQLiteDatabaseWrapper::TagMostRecentPatient(int64_t patient)
+  bool SQLiteDatabaseWrapper::UnitTestsTransaction::GetParentPublicId(std::string& target,
+                                                                      int64_t id)
   {
-    {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE,
-                          "DELETE FROM PatientRecyclingOrder WHERE patientId=?");
-      s.BindInt64(0, patient);
-      s.Run();
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT a.publicId FROM Resources AS a, Resources AS b "
+                        "WHERE a.internalId = b.parentId AND b.internalId = ?");     
+    s.BindInt64(0, id);
 
-      assert(db_.GetLastChangeCount() == 0 ||
-             db_.GetLastChangeCount() == 1);
-      
-      if (db_.GetLastChangeCount() == 0)
-      {
-        // The patient was protected, there was nothing to delete from the recycling order
-        return;
-      }
+    if (s.Step())
+    {
+      target = s.ColumnString(0);
+      return true;
+    }
+    else
+    {
+      return false;
     }
+  }
 
+
+  void SQLiteDatabaseWrapper::UnitTestsTransaction::GetChildren(std::list<std::string>& childrenPublicIds,
+                                                                int64_t id)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT publicId FROM Resources WHERE parentId=?");
+    s.BindInt64(0, id);
+
+    childrenPublicIds.clear();
+    while (s.Step())
     {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE,
-                          "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)");
-      s.BindInt64(0, patient);
-      s.Run();
+      childrenPublicIds.push_back(s.ColumnString(0));
     }
   }
 }
--- a/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.h	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.h	Mon Mar 15 15:30:42 2021 +0100
@@ -36,10 +36,6 @@
 #include "IDatabaseWrapper.h"
 
 #include "../../../OrthancFramework/Sources/SQLite/Connection.h"
-#include "Compatibility/ICreateInstance.h"
-#include "Compatibility/IGetChildrenMetadata.h"
-#include "Compatibility/ILookupResourceAndParent.h"
-#include "Compatibility/ISetResourcesContent.h"
 
 namespace Orthanc
 {
@@ -48,14 +44,10 @@
    * translates low-level requests into SQL statements. Mutual
    * exclusion MUST be implemented at a higher level.
    **/
-  class SQLiteDatabaseWrapper :
-    public IDatabaseWrapper,
-    public Compatibility::ICreateInstance,
-    public Compatibility::IGetChildrenMetadata,
-    public Compatibility::ILookupResourceAndParent,
-    public Compatibility::ISetResourcesContent
+  class SQLiteDatabaseWrapper : public IDatabaseWrapper
   {
   private:
+    class TransactionBase;
     class SignalFileDeleted;
     class SignalResourceDeleted;
     class SignalRemainingAncestor;
@@ -63,8 +55,8 @@
     class ReadWriteTransaction;
     class LookupFormatter;
 
-    IDatabaseListener* listener_;
     SQLite::Connection db_;
+    TransactionBase* activeTransaction_;
     SignalRemainingAncestor* signalRemainingAncestor_;
     unsigned int version_;
 
@@ -78,292 +70,82 @@
                                       SQLite::Statement& s,
                                       uint32_t maxResults);
 
-    void ClearTable(const std::string& tableName);
-
-    // Unused => could be removed
-    int GetGlobalIntegerProperty(GlobalProperty property,
-                                 int defaultValue);
-
   public:
     SQLiteDatabaseWrapper(const std::string& path);
 
     SQLiteDatabaseWrapper();
 
-    virtual void Open()
-      ORTHANC_OVERRIDE;
+    virtual ~SQLiteDatabaseWrapper();
 
-    virtual void Close()
-      ORTHANC_OVERRIDE
+    virtual void Open() ORTHANC_OVERRIDE;
+
+    virtual void Close() ORTHANC_OVERRIDE
     {
       db_.Close();
     }
 
-    virtual bool LookupParent(int64_t& parentId,
-                              int64_t resourceId)
-      ORTHANC_OVERRIDE;
-
-    virtual std::string GetPublicId(int64_t resourceId)
-      ORTHANC_OVERRIDE;
-
-    virtual ResourceType GetResourceType(int64_t resourceId)
-      ORTHANC_OVERRIDE;
-
-    virtual void DeleteResource(int64_t id)
-      ORTHANC_OVERRIDE;
-
-    virtual void GetChanges(std::list<ServerIndexChange>& target /*out*/,
-                            bool& done /*out*/,
-                            int64_t since,
-                            uint32_t maxResults)
-      ORTHANC_OVERRIDE;
-
-    virtual void GetLastChange(std::list<ServerIndexChange>& target /*out*/)
-      ORTHANC_OVERRIDE;
-
     virtual IDatabaseWrapper::ITransaction* StartTransaction(TransactionType type,
                                                              IDatabaseListener& listener)
       ORTHANC_OVERRIDE;
 
-    virtual void FlushToDisk()
-      ORTHANC_OVERRIDE
+    virtual void FlushToDisk() ORTHANC_OVERRIDE
     {
       db_.FlushToDisk();
     }
 
-    virtual bool HasFlushToDisk() const
-      ORTHANC_OVERRIDE
+    virtual bool HasFlushToDisk() const ORTHANC_OVERRIDE
     {
       return true;
     }
 
-    virtual void ClearChanges()
-      ORTHANC_OVERRIDE
-    {
-      ClearTable("Changes");
-    }
-
-    virtual void ClearExportedResources()
-      ORTHANC_OVERRIDE
-    {
-      ClearTable("ExportedResources");
-    }
-
-    virtual void GetAllMetadata(std::map<MetadataType, std::string>& target,
-                                int64_t id)
-      ORTHANC_OVERRIDE;
-
-    virtual unsigned int GetDatabaseVersion()
-      ORTHANC_OVERRIDE
+    virtual unsigned int GetDatabaseVersion() ORTHANC_OVERRIDE
     {
       return version_;
     }
 
     virtual void Upgrade(unsigned int targetVersion,
-                         IStorageArea& storageArea)
-      ORTHANC_OVERRIDE;
-
-
-    /**
-     * The methods declared below are for unit testing only!
-     **/
-
-    const char* GetErrorMessage() const
-    {
-      return db_.GetErrorMessage();
-    }
-
-    void GetChildren(std::list<std::string>& childrenPublicIds,
-                     int64_t id);
-
-    int64_t GetTableRecordCount(const std::string& table);
-    
-    bool GetParentPublicId(std::string& target,
-                           int64_t id);
+                         IStorageArea& storageArea) ORTHANC_OVERRIDE;
 
 
 
     /**
-     * Until Orthanc 1.4.0, the methods below were part of the
-     * "DatabaseWrapperBase" class, that is now placed in the
-     * graveyard.
+     * The "StartTransaction()" method is guaranteed to return a class
+     * derived from "UnitTestsTransaction". The methods of
+     * "UnitTestsTransaction" give access to additional information
+     * about the underlying SQLite database to be used in unit tests.
      **/
-
-    virtual void SetGlobalProperty(GlobalProperty property,
-                                   const std::string& value)
-      ORTHANC_OVERRIDE;
-
-    virtual bool LookupGlobalProperty(std::string& target,
-                                      GlobalProperty property)
-      ORTHANC_OVERRIDE;
-
-    virtual int64_t CreateResource(const std::string& publicId,
-                                   ResourceType type)
-      ORTHANC_OVERRIDE;
-
-    virtual bool LookupResource(int64_t& id,
-                                ResourceType& type,
-                                const std::string& publicId)
-      ORTHANC_OVERRIDE;
-
-    virtual void AttachChild(int64_t parent,
-                             int64_t child)
-      ORTHANC_OVERRIDE;
-
-    virtual void SetMetadata(int64_t id,
-                             MetadataType type,
-                             const std::string& value)
-      ORTHANC_OVERRIDE;
-
-    virtual void DeleteMetadata(int64_t id,
-                                MetadataType type)
-      ORTHANC_OVERRIDE;
-
-    virtual bool LookupMetadata(std::string& target,
-                                int64_t id,
-                                MetadataType type)
-      ORTHANC_OVERRIDE;
-
-    virtual void AddAttachment(int64_t id,
-                               const FileInfo& attachment)
-      ORTHANC_OVERRIDE;
+    class UnitTestsTransaction : public ITransaction
+    {
+    protected:
+      SQLite::Connection& db_;
+      
+    public:
+      UnitTestsTransaction(SQLite::Connection& db) :
+        db_(db)
+      {
+      }
+      
+      void GetChildren(std::list<std::string>& childrenPublicIds,
+                       int64_t id);
 
-    virtual void DeleteAttachment(int64_t id,
-                                  FileContentType attachment)
-      ORTHANC_OVERRIDE;
-
-    virtual void ListAvailableAttachments(std::set<FileContentType>& target,
-                                          int64_t id)
-      ORTHANC_OVERRIDE;
-
-    virtual bool LookupAttachment(FileInfo& attachment,
-                                  int64_t id,
-                                  FileContentType contentType)
-      ORTHANC_OVERRIDE;
-
-    virtual void ClearMainDicomTags(int64_t id)
-      ORTHANC_OVERRIDE;
-
-    virtual void SetMainDicomTag(int64_t id,
-                                 const DicomTag& tag,
-                                 const std::string& value)
-      ORTHANC_OVERRIDE;
-
-    virtual void SetIdentifierTag(int64_t id,
-                                  const DicomTag& tag,
-                                  const std::string& value)
-      ORTHANC_OVERRIDE;
-
-    virtual void GetMainDicomTags(DicomMap& map,
-                                  int64_t id)
-      ORTHANC_OVERRIDE;
-
-    virtual void GetChildrenPublicId(std::list<std::string>& target,
-                                     int64_t id)
-      ORTHANC_OVERRIDE;
-
-    virtual void GetChildrenInternalId(std::list<int64_t>& target,
-                                       int64_t id)
-      ORTHANC_OVERRIDE;
-
-    virtual void LogChange(int64_t internalId,
-                           const ServerIndexChange& change)
-      ORTHANC_OVERRIDE;
-
-    virtual void LogExportedResource(const ExportedResource& resource)
-      ORTHANC_OVERRIDE;
+      int64_t GetTableRecordCount(const std::string& table);
     
-    virtual void GetExportedResources(std::list<ExportedResource>& target /*out*/,
-                                      bool& done /*out*/,
-                                      int64_t since,
-                                      uint32_t maxResults)
-      ORTHANC_OVERRIDE;
-
-    virtual void GetLastExportedResource(std::list<ExportedResource>& target /*out*/)
-      ORTHANC_OVERRIDE;
-
-    virtual uint64_t GetTotalCompressedSize()
-      ORTHANC_OVERRIDE;
-    
-    virtual uint64_t GetTotalUncompressedSize()
-      ORTHANC_OVERRIDE;
-
-    virtual uint64_t GetResourceCount(ResourceType resourceType)
-      ORTHANC_OVERRIDE;
-
-    virtual void GetAllPublicIds(std::list<std::string>& target,
-                                 ResourceType resourceType)
-      ORTHANC_OVERRIDE;
+      bool GetParentPublicId(std::string& target,
+                             int64_t id);
 
-    virtual void GetAllPublicIds(std::list<std::string>& target,
-                                 ResourceType resourceType,
-                                 size_t since,
-                                 size_t limit)
-      ORTHANC_OVERRIDE;
-
-    virtual bool SelectPatientToRecycle(int64_t& internalId)
-      ORTHANC_OVERRIDE;
+      int64_t CreateResource(const std::string& publicId,
+                             ResourceType type);
 
-    virtual bool SelectPatientToRecycle(int64_t& internalId,
-                                        int64_t patientIdToAvoid)
-      ORTHANC_OVERRIDE;
-
-    virtual bool IsProtectedPatient(int64_t internalId)
-      ORTHANC_OVERRIDE;
-
-    virtual void SetProtectedPatient(int64_t internalId, 
-                                     bool isProtected)
-      ORTHANC_OVERRIDE;
-
-    virtual bool IsExistingResource(int64_t internalId)
-      ORTHANC_OVERRIDE;
+      void AttachChild(int64_t parent,
+                       int64_t child);
 
-    virtual bool IsDiskSizeAbove(uint64_t threshold)
-      ORTHANC_OVERRIDE;
-
-    virtual void ApplyLookupResources(std::list<std::string>& resourcesId,
-                                      std::list<std::string>* instancesId,
-                                      const std::vector<DatabaseConstraint>& lookup,
-                                      ResourceType queryLevel,
-                                      size_t limit)
-      ORTHANC_OVERRIDE;
-
-    virtual bool CreateInstance(CreateInstanceResult& result,
-                                int64_t& instanceId,
-                                const std::string& patient,
-                                const std::string& study,
-                                const std::string& series,
-                                const std::string& instance)
-      ORTHANC_OVERRIDE
-    {
-      return ICreateInstance::Apply
-        (*this, result, instanceId, patient, study, series, instance);
-    }
+      void SetIdentifierTag(int64_t id,
+                            const DicomTag& tag,
+                            const std::string& value);
 
-    virtual void SetResourcesContent(const Orthanc::ResourcesContent& content)
-      ORTHANC_OVERRIDE
-    {
-      ISetResourcesContent::Apply(*this, content);
-    }
-
-    virtual void GetChildrenMetadata(std::list<std::string>& target,
-                                     int64_t resourceId,
-                                     MetadataType metadata)
-      ORTHANC_OVERRIDE
-    {
-      IGetChildrenMetadata::Apply(*this, target, resourceId, metadata);
-    }
-
-    virtual int64_t GetLastChangeIndex() ORTHANC_OVERRIDE;
-
-    virtual void TagMostRecentPatient(int64_t patient) ORTHANC_OVERRIDE;
-
-    virtual bool LookupResourceAndParent(int64_t& id,
-                                         ResourceType& type,
-                                         std::string& parentPublicId,
-                                         const std::string& publicId)
-      ORTHANC_OVERRIDE
-    {
-      return ILookupResourceAndParent::Apply(*this, id, type, parentPublicId, publicId);
-    }
+      void SetMainDicomTag(int64_t id,
+                           const DicomTag& tag,
+                           const std::string& value);
+    };
   };
 }
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Mon Mar 15 15:30:42 2021 +0100
@@ -425,7 +425,7 @@
 
     if (changeType <= ChangeType_INTERNAL_LastLogged)
     {
-      db_.LogChange(internalId, change);
+      transaction_.LogChange(internalId, change);
     }
 
     GetTransactionContext().SignalChange(change);
@@ -436,7 +436,7 @@
                                                                                  int64_t expectedNumberOfInstances)
   {
     std::list<std::string> values;
-    db_.GetChildrenMetadata(values, id, MetadataType_Instance_IndexInSeries);
+    transaction_.GetChildrenMetadata(values, id, MetadataType_Instance_IndexInSeries);
 
     std::set<int64_t> instances;
 
@@ -555,6 +555,12 @@
       }
     }
 
+    IDatabaseWrapper::ITransaction& GetDatabaseTransaction()
+    {
+      assert(transaction_.get() != NULL);
+      return *transaction_;
+    }
+
     void Commit()
     {
       if (isCommitted_)
@@ -611,7 +617,7 @@
           
           Transaction transaction(db_, *factory_, TransactionType_ReadOnly);  // TODO - Only if not "TransactionType_Implicit"
           {
-            ReadOnlyTransaction t(db_, transaction.GetContext());
+            ReadOnlyTransaction t(transaction.GetDatabaseTransaction(), transaction.GetContext());
             readOperations->Apply(t);
           }
           transaction.Commit();
@@ -622,7 +628,7 @@
           
           Transaction transaction(db_, *factory_, TransactionType_ReadWrite);
           {
-            ReadWriteTransaction t(db_, transaction.GetContext());
+            ReadWriteTransaction t(transaction.GetDatabaseTransaction(), transaction.GetContext());
             writeOperations->Apply(t);
           }
           transaction.Commit();
@@ -2501,7 +2507,7 @@
       DicomTransferSyntax                   transferSyntax_;
       
     public:
-      Operations(const ParsedDicomFile& dicom)
+      explicit Operations(const ParsedDicomFile& dicom)
       {
         OrthancConfiguration::DefaultExtractDicomSummary(summary_, dicom);
         hasher_.reset(new DicomInstanceHasher(summary_));
@@ -2563,7 +2569,7 @@
   }
 
 
-  static bool IsRecyclingNeeded(IDatabaseWrapper& db,
+  static bool IsRecyclingNeeded(IDatabaseWrapper::ITransaction& transaction,
                                 uint64_t maximumStorageSize,
                                 unsigned int maximumPatients,
                                 uint64_t addedInstanceSize)
@@ -2578,7 +2584,7 @@
                                boost::lexical_cast<std::string>(maximumStorageSize));
       }
       
-      if (db.IsDiskSizeAbove(maximumStorageSize - addedInstanceSize))
+      if (transaction.IsDiskSizeAbove(maximumStorageSize - addedInstanceSize))
       {
         return true;
       }
@@ -2586,7 +2592,7 @@
 
     if (maximumPatients != 0)
     {
-      uint64_t patientCount = db.GetResourceCount(ResourceType_Patient);
+      uint64_t patientCount = transaction.GetResourceCount(ResourceType_Patient);
       if (patientCount > maximumPatients)
       {
         return true;
@@ -2604,7 +2610,7 @@
   {
     // TODO - Performance: Avoid calls to "IsRecyclingNeeded()"
     
-    if (IsRecyclingNeeded(db_, maximumStorageSize, maximumPatients, addedInstanceSize))
+    if (IsRecyclingNeeded(transaction_, maximumStorageSize, maximumPatients, addedInstanceSize))
     {
       // Check whether other DICOM instances from this patient are
       // already stored
@@ -2618,7 +2624,7 @@
       else
       {
         ResourceType type;
-        hasPatientToAvoid = db_.LookupResource(patientToAvoid, type, newPatientId);
+        hasPatientToAvoid = transaction_.LookupResource(patientToAvoid, type, newPatientId);
         if (type != ResourceType_Patient)
         {
           throw OrthancException(ErrorCode_InternalError);
@@ -2633,8 +2639,8 @@
         // If other instances of this patient are already in the store,
         // we must avoid to recycle them
         bool ok = (hasPatientToAvoid ?
-                   db_.SelectPatientToRecycle(patientToRecycle, patientToAvoid) :
-                   db_.SelectPatientToRecycle(patientToRecycle));
+                   transaction_.SelectPatientToRecycle(patientToRecycle, patientToAvoid) :
+                   transaction_.SelectPatientToRecycle(patientToRecycle));
         
         if (!ok)
         {
@@ -2642,9 +2648,9 @@
         }
       
         LOG(TRACE) << "Recycling one patient";
-        db_.DeleteResource(patientToRecycle);
-
-        if (!IsRecyclingNeeded(db_, maximumStorageSize, maximumPatients, addedInstanceSize))
+        transaction_.DeleteResource(patientToRecycle);
+
+        if (!IsRecyclingNeeded(transaction_, maximumStorageSize, maximumPatients, addedInstanceSize))
         {
           // OK, we're done
           return;
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.h	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.h	Mon Mar 15 15:30:42 2021 +0100
@@ -96,13 +96,13 @@
       ITransactionContext&  context_;
       
     protected:
-      IDatabaseWrapper&  db_;
+      IDatabaseWrapper::ITransaction&  transaction_;
       
     public:
-      explicit ReadOnlyTransaction(IDatabaseWrapper& db,
+      explicit ReadOnlyTransaction(IDatabaseWrapper::ITransaction& transaction,
                                    ITransactionContext& context) :
         context_(context),
-        db_(db)
+        transaction_(transaction)
       {
       }
 
@@ -129,19 +129,19 @@
                                 ResourceType queryLevel,
                                 size_t limit)
       {
-        return db_.ApplyLookupResources(resourcesId, instancesId, lookup, queryLevel, limit);
+        return transaction_.ApplyLookupResources(resourcesId, instancesId, lookup, queryLevel, limit);
       }
 
       void GetAllMetadata(std::map<MetadataType, std::string>& target,
                           int64_t id)
       {
-        db_.GetAllMetadata(target, id);
+        transaction_.GetAllMetadata(target, id);
       }
 
       void GetAllPublicIds(std::list<std::string>& target,
                            ResourceType resourceType)
       {
-        return db_.GetAllPublicIds(target, resourceType);
+        return transaction_.GetAllPublicIds(target, resourceType);
       }
 
       void GetAllPublicIds(std::list<std::string>& target,
@@ -149,7 +149,7 @@
                            size_t since,
                            size_t limit)
       {
-        return db_.GetAllPublicIds(target, resourceType, since, limit);
+        return transaction_.GetAllPublicIds(target, resourceType, since, limit);
       }  
 
       void GetChanges(std::list<ServerIndexChange>& target /*out*/,
@@ -157,19 +157,19 @@
                       int64_t since,
                       uint32_t maxResults)
       {
-        db_.GetChanges(target, done, since, maxResults);
+        transaction_.GetChanges(target, done, since, maxResults);
       }
 
       void GetChildrenInternalId(std::list<int64_t>& target,
                                  int64_t id)
       {
-        db_.GetChildrenInternalId(target, id);
+        transaction_.GetChildrenInternalId(target, id);
       }
 
       void GetChildrenPublicId(std::list<std::string>& target,
                                int64_t id)
       {
-        db_.GetChildrenPublicId(target, id);
+        transaction_.GetChildrenPublicId(target, id);
       }
 
       void GetExportedResources(std::list<ExportedResource>& target /*out*/,
@@ -177,97 +177,97 @@
                                 int64_t since,
                                 uint32_t maxResults)
       {
-        return db_.GetExportedResources(target, done, since, maxResults);
+        return transaction_.GetExportedResources(target, done, since, maxResults);
       }
 
       void GetLastChange(std::list<ServerIndexChange>& target /*out*/)
       {
-        db_.GetLastChange(target);
+        transaction_.GetLastChange(target);
       }
 
       void GetLastExportedResource(std::list<ExportedResource>& target /*out*/)
       {
-        return db_.GetLastExportedResource(target);
+        return transaction_.GetLastExportedResource(target);
       }
 
       int64_t GetLastChangeIndex()
       {
-        return db_.GetLastChangeIndex();
+        return transaction_.GetLastChangeIndex();
       }
 
       void GetMainDicomTags(DicomMap& map,
                             int64_t id)
       {
-        db_.GetMainDicomTags(map, id);
+        transaction_.GetMainDicomTags(map, id);
       }
 
       std::string GetPublicId(int64_t resourceId)
       {
-        return db_.GetPublicId(resourceId);
+        return transaction_.GetPublicId(resourceId);
       }
       
       uint64_t GetResourceCount(ResourceType resourceType)
       {
-        return db_.GetResourceCount(resourceType);
+        return transaction_.GetResourceCount(resourceType);
       }
       
       ResourceType GetResourceType(int64_t resourceId)
       {
-        return db_.GetResourceType(resourceId);
+        return transaction_.GetResourceType(resourceId);
       }
 
       uint64_t GetTotalCompressedSize()
       {
-        return db_.GetTotalCompressedSize();
+        return transaction_.GetTotalCompressedSize();
       }
     
       uint64_t GetTotalUncompressedSize()
       {
-        return db_.GetTotalUncompressedSize();
+        return transaction_.GetTotalUncompressedSize();
       }
       
       bool IsProtectedPatient(int64_t internalId)
       {
-        return db_.IsProtectedPatient(internalId);
+        return transaction_.IsProtectedPatient(internalId);
       }
 
       void ListAvailableAttachments(std::set<FileContentType>& target,
                                     int64_t id)
       {
-        db_.ListAvailableAttachments(target, id);
+        transaction_.ListAvailableAttachments(target, id);
       }
 
       bool LookupAttachment(FileInfo& attachment,
                             int64_t id,
                             FileContentType contentType)
       {
-        return db_.LookupAttachment(attachment, id, contentType);
+        return transaction_.LookupAttachment(attachment, id, contentType);
       }
       
       bool LookupGlobalProperty(std::string& target,
                                 GlobalProperty property)
       {
-        return db_.LookupGlobalProperty(target, property);
+        return transaction_.LookupGlobalProperty(target, property);
       }
 
       bool LookupMetadata(std::string& target,
                           int64_t id,
                           MetadataType type)
       {
-        return db_.LookupMetadata(target, id, type);
+        return transaction_.LookupMetadata(target, id, type);
       }
 
       bool LookupParent(int64_t& parentId,
                         int64_t resourceId)
       {
-        return db_.LookupParent(parentId, resourceId);
+        return transaction_.LookupParent(parentId, resourceId);
       }
         
       bool LookupResource(int64_t& id,
                           ResourceType& type,
                           const std::string& publicId)
       {
-        return db_.LookupResource(id, type, publicId);
+        return transaction_.LookupResource(id, type, publicId);
       }
       
       bool LookupResourceAndParent(int64_t& id,
@@ -275,7 +275,7 @@
                                    std::string& parentPublicId,
                                    const std::string& publicId)
       {
-        return db_.LookupResourceAndParent(id, type, parentPublicId, publicId);
+        return transaction_.LookupResourceAndParent(id, type, parentPublicId, publicId);
       }
     };
 
@@ -283,31 +283,31 @@
     class ReadWriteTransaction : public ReadOnlyTransaction
     {
     public:
-      ReadWriteTransaction(IDatabaseWrapper& db,
+      ReadWriteTransaction(IDatabaseWrapper::ITransaction& transaction,
                            ITransactionContext& context) :
-        ReadOnlyTransaction(db, context)
+        ReadOnlyTransaction(transaction, context)
       {
       }
 
       void AddAttachment(int64_t id,
                          const FileInfo& attachment)
       {
-        db_.AddAttachment(id, attachment);
+        transaction_.AddAttachment(id, attachment);
       }
       
       void ClearChanges()
       {
-        db_.ClearChanges();
+        transaction_.ClearChanges();
       }
 
       void ClearExportedResources()
       {
-        db_.ClearExportedResources();
+        transaction_.ClearExportedResources();
       }
 
       void ClearMainDicomTags(int64_t id)
       {
-        return db_.ClearMainDicomTags(id);
+        return transaction_.ClearMainDicomTags(id);
       }
 
       bool CreateInstance(IDatabaseWrapper::CreateInstanceResult& result, /* out */
@@ -317,24 +317,24 @@
                           const std::string& series,
                           const std::string& instance)
       {
-        return db_.CreateInstance(result, instanceId, patient, study, series, instance);
+        return transaction_.CreateInstance(result, instanceId, patient, study, series, instance);
       }
 
       void DeleteAttachment(int64_t id,
                             FileContentType attachment)
       {
-        return db_.DeleteAttachment(id, attachment);
+        return transaction_.DeleteAttachment(id, attachment);
       }
       
       void DeleteMetadata(int64_t id,
                           MetadataType type)
       {
-        db_.DeleteMetadata(id, type);
+        transaction_.DeleteMetadata(id, type);
       }
 
       void DeleteResource(int64_t id)
       {
-        db_.DeleteResource(id);
+        transaction_.DeleteResource(id);
       }
 
       void LogChange(int64_t internalId,
@@ -344,31 +344,31 @@
 
       void LogExportedResource(const ExportedResource& resource)
       {
-        db_.LogExportedResource(resource);
+        transaction_.LogExportedResource(resource);
       }
 
       void SetGlobalProperty(GlobalProperty property,
                              const std::string& value)
       {
-        db_.SetGlobalProperty(property, value);
+        transaction_.SetGlobalProperty(property, value);
       }
 
       void SetMetadata(int64_t id,
                        MetadataType type,
                        const std::string& value)
       {
-        return db_.SetMetadata(id, type, value);
+        return transaction_.SetMetadata(id, type, value);
       }
 
       void SetProtectedPatient(int64_t internalId, 
                                bool isProtected)
       {
-        db_.SetProtectedPatient(internalId, isProtected);
+        transaction_.SetProtectedPatient(internalId, isProtected);
       }
 
       void SetResourcesContent(const ResourcesContent& content)
       {
-        db_.SetResourcesContent(content);
+        transaction_.SetResourcesContent(content);
       }
 
       void Recycle(uint64_t maximumStorageSize,
@@ -423,7 +423,7 @@
                              unsigned int maximumPatientCount);
 
   public:
-    StatelessDatabaseOperations(IDatabaseWrapper& database);
+    explicit StatelessDatabaseOperations(IDatabaseWrapper& database);
 
     void SetTransactionContextFactory(ITransactionContextFactory* factory /* takes ownership */);
     
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/Database/VoidDatabaseListener.cpp	Mon Mar 15 15:30:42 2021 +0100
@@ -0,0 +1,57 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2021 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersServer.h"
+#include "VoidDatabaseListener.h"
+
+#include "../../../OrthancFramework/Sources/OrthancException.h"
+
+namespace Orthanc
+{
+  void VoidDatabaseListener::SignalRemainingAncestor(ResourceType parentType,
+                                                     const std::string& publicId)
+  {
+    throw OrthancException(ErrorCode_InternalError);
+  }
+      
+  void VoidDatabaseListener::SignalAttachmentDeleted(const FileInfo& info)
+  {
+    throw OrthancException(ErrorCode_InternalError);
+  }
+
+  void VoidDatabaseListener::SignalResourceDeleted(ResourceType type,
+                                                   const std::string& publicId)
+  {
+    throw OrthancException(ErrorCode_InternalError);
+  }      
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/Database/VoidDatabaseListener.h	Mon Mar 15 15:30:42 2021 +0100
@@ -0,0 +1,56 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2021 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../../OrthancFramework/Sources/Compatibility.h"
+#include "IDatabaseListener.h"
+
+namespace Orthanc
+{
+  /**
+   * This is a listener that can be used for transactions that do
+   * not create/delete attachments or resources.
+   **/
+  class VoidDatabaseListener : public IDatabaseListener
+  {
+  public:
+    virtual void SignalRemainingAncestor(ResourceType parentType,
+                                         const std::string& publicId) ORTHANC_OVERRIDE;
+      
+    virtual void SignalAttachmentDeleted(const FileInfo& info) ORTHANC_OVERRIDE;
+
+    virtual void SignalResourceDeleted(ResourceType type,
+                                       const std::string& publicId) ORTHANC_OVERRIDE;
+  };
+}
--- a/OrthancServer/Sources/ServerIndex.cpp	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/Sources/ServerIndex.cpp	Mon Mar 15 15:30:42 2021 +0100
@@ -91,6 +91,7 @@
     {
       sizeOfFilesToRemove_ = 0;
       hasRemainingLevel_ = false;
+      remainingType_ = ResourceType_Instance;  // dummy initialization
       pendingFilesToRemove_.clear();
       pendingChanges_.clear();
       sizeOfAddedAttachments_ = 0;
@@ -234,7 +235,7 @@
     ServerContext& context_;
       
   public:
-    TransactionContextFactory(ServerContext& context) :
+    explicit TransactionContextFactory(ServerContext& context) :
       context_(context)
     {
     }
--- a/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.cpp	Mon Mar 15 15:30:42 2021 +0100
@@ -711,7 +711,7 @@
     unsigned int    counter_;
 
   public:
-    MediaIndexVisitor(ZipCommands& commands) :
+    explicit MediaIndexVisitor(ZipCommands& commands) :
       commands_(commands),
       counter_(0)
     {
@@ -750,7 +750,7 @@
     bool                                    isMedia_;
 
   public:
-    ZipWriterIterator(TemporaryFile& target,
+    ZipWriterIterator(const TemporaryFile& target,
                       ServerContext& context,
                       ArchiveIndex& archive,
                       bool isMedia,
--- a/OrthancServer/Sources/ServerToolbox.cpp	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/Sources/ServerToolbox.cpp	Mon Mar 15 15:30:42 2021 +0100
@@ -165,7 +165,7 @@
   namespace ServerToolbox
   {
     bool FindOneChildInstance(int64_t& result,
-                              IDatabaseWrapper& database,
+                              IDatabaseWrapper::ITransaction& transaction,
                               int64_t resource,
                               ResourceType type)
     {
@@ -178,7 +178,7 @@
         }
 
         std::list<int64_t> children;
-        database.GetChildrenInternalId(children, resource);
+        transaction.GetChildrenInternalId(children, resource);
         if (children.empty())
         {
           return false;
@@ -190,7 +190,7 @@
     }
 
 
-    void ReconstructMainDicomTags(IDatabaseWrapper& database,
+    void ReconstructMainDicomTags(IDatabaseWrapper::ITransaction& transaction,
                                   IStorageArea& storageArea,
                                   ResourceType level)
     {
@@ -230,7 +230,7 @@
       LOG(WARNING) << "Upgrade: Reconstructing the main DICOM tags of all the " << plural << "...";
 
       std::list<std::string> resources;
-      database.GetAllPublicIds(resources, level);
+      transaction.GetAllPublicIds(resources, level);
 
       for (std::list<std::string>::const_iterator
              it = resources.begin(); it != resources.end(); ++it)
@@ -239,9 +239,9 @@
         int64_t resource, instance;
         ResourceType tmp;
 
-        if (!database.LookupResource(resource, tmp, *it) ||
+        if (!transaction.LookupResource(resource, tmp, *it) ||
             tmp != level ||
-            !FindOneChildInstance(instance, database, resource, level))
+            !FindOneChildInstance(instance, transaction, resource, level))
         {
           throw OrthancException(ErrorCode_InternalError,
                                  "Cannot find an instance for " +
@@ -251,11 +251,11 @@
 
         // Get the DICOM file attached to some instances in the resource
         FileInfo attachment;
-        if (!database.LookupAttachment(attachment, instance, FileContentType_Dicom))
+        if (!transaction.LookupAttachment(attachment, instance, FileContentType_Dicom))
         {
           throw OrthancException(ErrorCode_InternalError,
                                  "Cannot retrieve the DICOM file associated with instance " +
-                                 database.GetPublicId(instance));
+                                 transaction.GetPublicId(instance));
         }
 
         try
@@ -272,16 +272,16 @@
           DicomMap dicomSummary;
           OrthancConfiguration::DefaultExtractDicomSummary(dicomSummary, dicom);
 
-          database.ClearMainDicomTags(resource);
+          transaction.ClearMainDicomTags(resource);
 
           ResourcesContent tags;
           tags.AddResource(resource, level, dicomSummary);
-          database.SetResourcesContent(tags);
+          transaction.SetResourcesContent(tags);
         }
         catch (OrthancException&)
         {
           LOG(ERROR) << "Cannot decode the DICOM file with UUID " << attachment.GetUuid()
-                     << " associated with instance " << database.GetPublicId(instance);
+                     << " associated with instance " << transaction.GetPublicId(instance);
           throw;
         }
       }
--- a/OrthancServer/Sources/ServerToolbox.h	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/Sources/ServerToolbox.h	Mon Mar 15 15:30:42 2021 +0100
@@ -33,6 +33,7 @@
 
 #pragma once
 
+#include "Database/IDatabaseWrapper.h"
 #include "ServerEnumerations.h"
 
 #include <boost/noncopyable.hpp>
@@ -41,17 +42,16 @@
 namespace Orthanc
 {
   class ServerContext;
-  class IDatabaseWrapper;
   class IStorageArea;
 
   namespace ServerToolbox
   {
     bool FindOneChildInstance(int64_t& result,
-                              IDatabaseWrapper& database,
+                              IDatabaseWrapper::ITransaction& transaction,
                               int64_t resource,
                               ResourceType type);
 
-    void ReconstructMainDicomTags(IDatabaseWrapper& database,
+    void ReconstructMainDicomTags(IDatabaseWrapper::ITransaction& transaction,
                                   IStorageArea& storageArea,
                                   ResourceType level);
 
--- a/OrthancServer/UnitTestsSources/ServerIndexTests.cpp	Fri Mar 12 16:04:09 2021 +0100
+++ b/OrthancServer/UnitTestsSources/ServerIndexTests.cpp	Mon Mar 15 15:30:42 2021 +0100
@@ -96,7 +96,7 @@
   protected:
     std::unique_ptr<TestDatabaseListener>  listener_;
     std::unique_ptr<SQLiteDatabaseWrapper> index_;
-    std::unique_ptr<IDatabaseWrapper::ITransaction>  transaction_;
+    std::unique_ptr<SQLiteDatabaseWrapper::UnitTestsTransaction>  transaction_;
 
   public:
     DatabaseWrapperTest()
@@ -108,7 +108,8 @@
       listener_.reset(new TestDatabaseListener);
       index_.reset(new SQLiteDatabaseWrapper);
       index_->Open();
-      transaction_.reset(index_->StartTransaction(TransactionType_ReadWrite, *listener_));
+      transaction_.reset(dynamic_cast<SQLiteDatabaseWrapper::UnitTestsTransaction*>(
+                           index_->StartTransaction(TransactionType_ReadWrite, *listener_)));
     }
 
     virtual void TearDown() ORTHANC_OVERRIDE
@@ -123,33 +124,33 @@
 
     void CheckTableRecordCount(uint32_t expected, const char* table)
     {
-      ASSERT_EQ(expected, index_->GetTableRecordCount(table));
+      ASSERT_EQ(expected, transaction_->GetTableRecordCount(table));
     }
 
     void CheckNoParent(int64_t id)
     {
       std::string s;
-      ASSERT_FALSE(index_->GetParentPublicId(s, id));
+      ASSERT_FALSE(transaction_->GetParentPublicId(s, id));
     }
 
     void CheckParentPublicId(const char* expected, int64_t id)
     {
       std::string s;
-      ASSERT_TRUE(index_->GetParentPublicId(s, id));
+      ASSERT_TRUE(transaction_->GetParentPublicId(s, id));
       ASSERT_EQ(expected, s);
     }
 
     void CheckNoChild(int64_t id)
     {
       std::list<std::string> j;
-      index_->GetChildren(j, id);
+      transaction_->GetChildren(j, id);
       ASSERT_EQ(0u, j.size());
     }
 
     void CheckOneChild(const char* expected, int64_t id)
     {
       std::list<std::string> j;
-      index_->GetChildren(j, id);
+      transaction_->GetChildren(j, id);
       ASSERT_EQ(1u, j.size());
       ASSERT_EQ(expected, j.front());
     }
@@ -159,7 +160,7 @@
                           int64_t id)
     {
       std::list<std::string> j;
-      index_->GetChildren(j, id);
+      transaction_->GetChildren(j, id);
       ASSERT_EQ(2u, j.size());
       ASSERT_TRUE((expected1 == j.front() && expected2 == j.back()) ||
                   (expected1 == j.back() && expected2 == j.front()));                    
@@ -178,7 +179,7 @@
       std::vector<DatabaseConstraint> lookup;
       lookup.push_back(c.ConvertToDatabaseConstraint(level, DicomTagType_Identifier));
       
-      index_->ApplyLookupResources(result, NULL, lookup, level, 0 /* no limit */);
+      transaction_->ApplyLookupResources(result, NULL, lookup, level, 0 /* no limit */);
     }    
 
     void DoLookupIdentifier2(std::list<std::string>& result,
@@ -198,7 +199,7 @@
       lookup.push_back(c1.ConvertToDatabaseConstraint(level, DicomTagType_Identifier));
       lookup.push_back(c2.ConvertToDatabaseConstraint(level, DicomTagType_Identifier));
       
-      index_->ApplyLookupResources(result, NULL, lookup, level, 0 /* no limit */);
+      transaction_->ApplyLookupResources(result, NULL, lookup, level, 0 /* no limit */);
     }
   };
 }
@@ -207,65 +208,65 @@
 TEST_F(DatabaseWrapperTest, Simple)
 {
   int64_t a[] = {
-    index_->CreateResource("a", ResourceType_Patient),   // 0
-    index_->CreateResource("b", ResourceType_Study),     // 1
-    index_->CreateResource("c", ResourceType_Series),    // 2
-    index_->CreateResource("d", ResourceType_Instance),  // 3
-    index_->CreateResource("e", ResourceType_Instance),  // 4
-    index_->CreateResource("f", ResourceType_Instance),  // 5
-    index_->CreateResource("g", ResourceType_Study)      // 6
+    transaction_->CreateResource("a", ResourceType_Patient),   // 0
+    transaction_->CreateResource("b", ResourceType_Study),     // 1
+    transaction_->CreateResource("c", ResourceType_Series),    // 2
+    transaction_->CreateResource("d", ResourceType_Instance),  // 3
+    transaction_->CreateResource("e", ResourceType_Instance),  // 4
+    transaction_->CreateResource("f", ResourceType_Instance),  // 5
+    transaction_->CreateResource("g", ResourceType_Study)      // 6
   };
 
-  ASSERT_EQ("a", index_->GetPublicId(a[0]));
-  ASSERT_EQ("b", index_->GetPublicId(a[1]));
-  ASSERT_EQ("c", index_->GetPublicId(a[2]));
-  ASSERT_EQ("d", index_->GetPublicId(a[3]));
-  ASSERT_EQ("e", index_->GetPublicId(a[4]));
-  ASSERT_EQ("f", index_->GetPublicId(a[5]));
-  ASSERT_EQ("g", index_->GetPublicId(a[6]));
+  ASSERT_EQ("a", transaction_->GetPublicId(a[0]));
+  ASSERT_EQ("b", transaction_->GetPublicId(a[1]));
+  ASSERT_EQ("c", transaction_->GetPublicId(a[2]));
+  ASSERT_EQ("d", transaction_->GetPublicId(a[3]));
+  ASSERT_EQ("e", transaction_->GetPublicId(a[4]));
+  ASSERT_EQ("f", transaction_->GetPublicId(a[5]));
+  ASSERT_EQ("g", transaction_->GetPublicId(a[6]));
 
-  ASSERT_EQ(ResourceType_Patient, index_->GetResourceType(a[0]));
-  ASSERT_EQ(ResourceType_Study, index_->GetResourceType(a[1]));
-  ASSERT_EQ(ResourceType_Series, index_->GetResourceType(a[2]));
-  ASSERT_EQ(ResourceType_Instance, index_->GetResourceType(a[3]));
-  ASSERT_EQ(ResourceType_Instance, index_->GetResourceType(a[4]));
-  ASSERT_EQ(ResourceType_Instance, index_->GetResourceType(a[5]));
-  ASSERT_EQ(ResourceType_Study, index_->GetResourceType(a[6]));
+  ASSERT_EQ(ResourceType_Patient, transaction_->GetResourceType(a[0]));
+  ASSERT_EQ(ResourceType_Study, transaction_->GetResourceType(a[1]));
+  ASSERT_EQ(ResourceType_Series, transaction_->GetResourceType(a[2]));
+  ASSERT_EQ(ResourceType_Instance, transaction_->GetResourceType(a[3]));
+  ASSERT_EQ(ResourceType_Instance, transaction_->GetResourceType(a[4]));
+  ASSERT_EQ(ResourceType_Instance, transaction_->GetResourceType(a[5]));
+  ASSERT_EQ(ResourceType_Study, transaction_->GetResourceType(a[6]));
 
   {
     std::list<std::string> t;
-    index_->GetAllPublicIds(t, ResourceType_Patient);
+    transaction_->GetAllPublicIds(t, ResourceType_Patient);
 
     ASSERT_EQ(1u, t.size());
     ASSERT_EQ("a", t.front());
 
-    index_->GetAllPublicIds(t, ResourceType_Series);
+    transaction_->GetAllPublicIds(t, ResourceType_Series);
     ASSERT_EQ(1u, t.size());
     ASSERT_EQ("c", t.front());
 
-    index_->GetAllPublicIds(t, ResourceType_Study);
+    transaction_->GetAllPublicIds(t, ResourceType_Study);
     ASSERT_EQ(2u, t.size());
 
-    index_->GetAllPublicIds(t, ResourceType_Instance);
+    transaction_->GetAllPublicIds(t, ResourceType_Instance);
     ASSERT_EQ(3u, t.size());
   }
 
-  index_->SetGlobalProperty(GlobalProperty_FlushSleep, "World");
+  transaction_->SetGlobalProperty(GlobalProperty_FlushSleep, "World");
 
-  index_->AttachChild(a[0], a[1]);
-  index_->AttachChild(a[1], a[2]);
-  index_->AttachChild(a[2], a[3]);
-  index_->AttachChild(a[2], a[4]);
-  index_->AttachChild(a[6], a[5]);
+  transaction_->AttachChild(a[0], a[1]);
+  transaction_->AttachChild(a[1], a[2]);
+  transaction_->AttachChild(a[2], a[3]);
+  transaction_->AttachChild(a[2], a[4]);
+  transaction_->AttachChild(a[6], a[5]);
 
   int64_t parent;
-  ASSERT_FALSE(index_->LookupParent(parent, a[0]));
-  ASSERT_TRUE(index_->LookupParent(parent, a[1])); ASSERT_EQ(a[0], parent);
-  ASSERT_TRUE(index_->LookupParent(parent, a[2])); ASSERT_EQ(a[1], parent);
-  ASSERT_TRUE(index_->LookupParent(parent, a[3])); ASSERT_EQ(a[2], parent);
-  ASSERT_TRUE(index_->LookupParent(parent, a[4])); ASSERT_EQ(a[2], parent);
-  ASSERT_TRUE(index_->LookupParent(parent, a[5])); ASSERT_EQ(a[6], parent);
-  ASSERT_FALSE(index_->LookupParent(parent, a[6]));
+  ASSERT_FALSE(transaction_->LookupParent(parent, a[0]));
+  ASSERT_TRUE(transaction_->LookupParent(parent, a[1])); ASSERT_EQ(a[0], parent);
+  ASSERT_TRUE(transaction_->LookupParent(parent, a[2])); ASSERT_EQ(a[1], parent);
+  ASSERT_TRUE(transaction_->LookupParent(parent, a[3])); ASSERT_EQ(a[2], parent);
+  ASSERT_TRUE(transaction_->LookupParent(parent, a[4])); ASSERT_EQ(a[2], parent);
+  ASSERT_TRUE(transaction_->LookupParent(parent, a[5])); ASSERT_EQ(a[6], parent);
+  ASSERT_FALSE(transaction_->LookupParent(parent, a[6]));
 
   std::string s;
 
@@ -278,14 +279,14 @@
   CheckParentPublicId("g", a[5]);
 
   std::list<std::string> l;
-  index_->GetChildrenPublicId(l, a[0]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("b", l.front());
-  index_->GetChildrenPublicId(l, a[1]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("c", l.front());
-  index_->GetChildrenPublicId(l, a[3]); ASSERT_EQ(0u, l.size()); 
-  index_->GetChildrenPublicId(l, a[4]); ASSERT_EQ(0u, l.size()); 
-  index_->GetChildrenPublicId(l, a[5]); ASSERT_EQ(0u, l.size()); 
-  index_->GetChildrenPublicId(l, a[6]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("f", l.front());
+  transaction_->GetChildrenPublicId(l, a[0]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("b", l.front());
+  transaction_->GetChildrenPublicId(l, a[1]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("c", l.front());
+  transaction_->GetChildrenPublicId(l, a[3]); ASSERT_EQ(0u, l.size()); 
+  transaction_->GetChildrenPublicId(l, a[4]); ASSERT_EQ(0u, l.size()); 
+  transaction_->GetChildrenPublicId(l, a[5]); ASSERT_EQ(0u, l.size()); 
+  transaction_->GetChildrenPublicId(l, a[6]); ASSERT_EQ(1u, l.size()); ASSERT_EQ("f", l.front());
 
-  index_->GetChildrenPublicId(l, a[2]); ASSERT_EQ(2u, l.size()); 
+  transaction_->GetChildrenPublicId(l, a[2]); ASSERT_EQ(2u, l.size()); 
   if (l.front() == "d")
   {
     ASSERT_EQ("e", l.back());
@@ -297,64 +298,64 @@
   }
 
   std::map<MetadataType, std::string> md;
-  index_->GetAllMetadata(md, a[4]);
+  transaction_->GetAllMetadata(md, a[4]);
   ASSERT_EQ(0u, md.size());
 
-  index_->AddAttachment(a[4], FileInfo("my json file", FileContentType_DicomAsJson, 42, "md5", 
+  transaction_->AddAttachment(a[4], FileInfo("my json file", FileContentType_DicomAsJson, 42, "md5", 
                                        CompressionType_ZlibWithSize, 21, "compressedMD5"));
-  index_->AddAttachment(a[4], FileInfo("my dicom file", FileContentType_Dicom, 42, "md5"));
-  index_->AddAttachment(a[6], FileInfo("world", FileContentType_Dicom, 44, "md5"));
-  index_->SetMetadata(a[4], MetadataType_RemoteAet, "PINNACLE");
+  transaction_->AddAttachment(a[4], FileInfo("my dicom file", FileContentType_Dicom, 42, "md5"));
+  transaction_->AddAttachment(a[6], FileInfo("world", FileContentType_Dicom, 44, "md5"));
+  transaction_->SetMetadata(a[4], MetadataType_RemoteAet, "PINNACLE");
   
-  index_->GetAllMetadata(md, a[4]);
+  transaction_->GetAllMetadata(md, a[4]);
   ASSERT_EQ(1u, md.size());
   ASSERT_EQ("PINNACLE", md[MetadataType_RemoteAet]);
-  index_->SetMetadata(a[4], MetadataType_ModifiedFrom, "TUTU");
-  index_->GetAllMetadata(md, a[4]);
+  transaction_->SetMetadata(a[4], MetadataType_ModifiedFrom, "TUTU");
+  transaction_->GetAllMetadata(md, a[4]);
   ASSERT_EQ(2u, md.size());
 
   std::map<MetadataType, std::string> md2;
-  index_->GetAllMetadata(md2, a[4]);
+  transaction_->GetAllMetadata(md2, a[4]);
   ASSERT_EQ(2u, md2.size());
   ASSERT_EQ("TUTU", md2[MetadataType_ModifiedFrom]);
   ASSERT_EQ("PINNACLE", md2[MetadataType_RemoteAet]);
 
-  index_->DeleteMetadata(a[4], MetadataType_ModifiedFrom);
-  index_->GetAllMetadata(md, a[4]);
+  transaction_->DeleteMetadata(a[4], MetadataType_ModifiedFrom);
+  transaction_->GetAllMetadata(md, a[4]);
   ASSERT_EQ(1u, md.size());
   ASSERT_EQ("PINNACLE", md[MetadataType_RemoteAet]);
 
-  index_->GetAllMetadata(md2, a[4]);
+  transaction_->GetAllMetadata(md2, a[4]);
   ASSERT_EQ(1u, md2.size());
   ASSERT_EQ("PINNACLE", md2[MetadataType_RemoteAet]);
 
 
-  ASSERT_EQ(21u + 42u + 44u, index_->GetTotalCompressedSize());
-  ASSERT_EQ(42u + 42u + 44u, index_->GetTotalUncompressedSize());
+  ASSERT_EQ(21u + 42u + 44u, transaction_->GetTotalCompressedSize());
+  ASSERT_EQ(42u + 42u + 44u, transaction_->GetTotalUncompressedSize());
 
-  index_->SetMainDicomTag(a[3], DicomTag(0x0010, 0x0010), "PatientName");
+  transaction_->SetMainDicomTag(a[3], DicomTag(0x0010, 0x0010), "PatientName");
 
   int64_t b;
   ResourceType t;
-  ASSERT_TRUE(index_->LookupResource(b, t, "g"));
+  ASSERT_TRUE(transaction_->LookupResource(b, t, "g"));
   ASSERT_EQ(7, b);
   ASSERT_EQ(ResourceType_Study, t);
 
-  ASSERT_TRUE(index_->LookupMetadata(s, a[4], MetadataType_RemoteAet));
-  ASSERT_FALSE(index_->LookupMetadata(s, a[4], MetadataType_Instance_IndexInSeries));
+  ASSERT_TRUE(transaction_->LookupMetadata(s, a[4], MetadataType_RemoteAet));
+  ASSERT_FALSE(transaction_->LookupMetadata(s, a[4], MetadataType_Instance_IndexInSeries));
   ASSERT_EQ("PINNACLE", s);
 
   std::string u;
-  ASSERT_TRUE(index_->LookupMetadata(u, a[4], MetadataType_RemoteAet));
+  ASSERT_TRUE(transaction_->LookupMetadata(u, a[4], MetadataType_RemoteAet));
   ASSERT_EQ("PINNACLE", u);
-  ASSERT_FALSE(index_->LookupMetadata(u, a[4], MetadataType_Instance_IndexInSeries));
+  ASSERT_FALSE(transaction_->LookupMetadata(u, a[4], MetadataType_Instance_IndexInSeries));
 
-  ASSERT_TRUE(index_->LookupGlobalProperty(s, GlobalProperty_FlushSleep));
-  ASSERT_FALSE(index_->LookupGlobalProperty(s, static_cast<GlobalProperty>(42)));
+  ASSERT_TRUE(transaction_->LookupGlobalProperty(s, GlobalProperty_FlushSleep));
+  ASSERT_FALSE(transaction_->LookupGlobalProperty(s, static_cast<GlobalProperty>(42)));
   ASSERT_EQ("World", s);
 
   FileInfo att;
-  ASSERT_TRUE(index_->LookupAttachment(att, a[4], FileContentType_DicomAsJson));
+  ASSERT_TRUE(transaction_->LookupAttachment(att, a[4], FileContentType_DicomAsJson));
   ASSERT_EQ("my json file", att.GetUuid());
   ASSERT_EQ(21u, att.GetCompressedSize());
   ASSERT_EQ("md5", att.GetUncompressedMD5());
@@ -362,7 +363,7 @@
   ASSERT_EQ(42u, att.GetUncompressedSize());
   ASSERT_EQ(CompressionType_ZlibWithSize, att.GetCompressionType());
 
-  ASSERT_TRUE(index_->LookupAttachment(att, a[6], FileContentType_Dicom));
+  ASSERT_TRUE(transaction_->LookupAttachment(att, a[6], FileContentType_Dicom));
   ASSERT_EQ("world", att.GetUuid());
   ASSERT_EQ(44u, att.GetCompressedSize());
   ASSERT_EQ("md5", att.GetUncompressedMD5());
@@ -378,7 +379,7 @@
   CheckTableRecordCount(1, "Metadata");
   CheckTableRecordCount(1, "MainDicomTags");
 
-  index_->DeleteResource(a[0]);
+  transaction_->DeleteResource(a[0]);
   ASSERT_EQ(5u, listener_->deletedResources_.size());
   ASSERT_EQ(2u, listener_->deletedFiles_.size());
   ASSERT_FALSE(std::find(listener_->deletedFiles_.begin(), 
@@ -393,7 +394,7 @@
   CheckTableRecordCount(1, "AttachedFiles");
   CheckTableRecordCount(0, "MainDicomTags");
 
-  index_->DeleteResource(a[5]);
+  transaction_->DeleteResource(a[5]);
   ASSERT_EQ(7u, listener_->deletedResources_.size());
 
   CheckTableRecordCount(0, "Resources");
@@ -401,11 +402,11 @@
   CheckTableRecordCount(3, "GlobalProperties");
 
   std::string tmp;
-  ASSERT_TRUE(index_->LookupGlobalProperty(tmp, GlobalProperty_DatabaseSchemaVersion));
+  ASSERT_TRUE(transaction_->LookupGlobalProperty(tmp, GlobalProperty_DatabaseSchemaVersion));
   ASSERT_EQ("6", tmp);
-  ASSERT_TRUE(index_->LookupGlobalProperty(tmp, GlobalProperty_FlushSleep));
+  ASSERT_TRUE(transaction_->LookupGlobalProperty(tmp, GlobalProperty_FlushSleep));
   ASSERT_EQ("World", tmp);
-  ASSERT_TRUE(index_->LookupGlobalProperty(tmp, GlobalProperty_GetTotalSizeIsFast));
+  ASSERT_TRUE(transaction_->LookupGlobalProperty(tmp, GlobalProperty_GetTotalSizeIsFast));
   ASSERT_EQ("1", tmp);
 
   ASSERT_EQ(3u, listener_->deletedFiles_.size());
@@ -418,23 +419,23 @@
 TEST_F(DatabaseWrapperTest, Upward)
 {
   int64_t a[] = {
-    index_->CreateResource("a", ResourceType_Patient),   // 0
-    index_->CreateResource("b", ResourceType_Study),     // 1
-    index_->CreateResource("c", ResourceType_Series),    // 2
-    index_->CreateResource("d", ResourceType_Instance),  // 3
-    index_->CreateResource("e", ResourceType_Instance),  // 4
-    index_->CreateResource("f", ResourceType_Study),     // 5
-    index_->CreateResource("g", ResourceType_Series),    // 6
-    index_->CreateResource("h", ResourceType_Series)     // 7
+    transaction_->CreateResource("a", ResourceType_Patient),   // 0
+    transaction_->CreateResource("b", ResourceType_Study),     // 1
+    transaction_->CreateResource("c", ResourceType_Series),    // 2
+    transaction_->CreateResource("d", ResourceType_Instance),  // 3
+    transaction_->CreateResource("e", ResourceType_Instance),  // 4
+    transaction_->CreateResource("f", ResourceType_Study),     // 5
+    transaction_->CreateResource("g", ResourceType_Series),    // 6
+    transaction_->CreateResource("h", ResourceType_Series)     // 7
   };
 
-  index_->AttachChild(a[0], a[1]);
-  index_->AttachChild(a[1], a[2]);
-  index_->AttachChild(a[2], a[3]);
-  index_->AttachChild(a[2], a[4]);
-  index_->AttachChild(a[1], a[6]);
-  index_->AttachChild(a[0], a[5]);
-  index_->AttachChild(a[5], a[7]);
+  transaction_->AttachChild(a[0], a[1]);
+  transaction_->AttachChild(a[1], a[2]);
+  transaction_->AttachChild(a[2], a[3]);
+  transaction_->AttachChild(a[2], a[4]);
+  transaction_->AttachChild(a[1], a[6]);
+  transaction_->AttachChild(a[0], a[5]);
+  transaction_->AttachChild(a[5], a[7]);
 
   CheckTwoChildren("b", "f", a[0]);
   CheckTwoChildren("c", "g", a[1]);
@@ -446,22 +447,22 @@
   CheckNoChild(a[7]);
 
   listener_->Reset();
-  index_->DeleteResource(a[3]);
+  transaction_->DeleteResource(a[3]);
   ASSERT_EQ("c", listener_->ancestorId_);
   ASSERT_EQ(ResourceType_Series, listener_->ancestorType_);
 
   listener_->Reset();
-  index_->DeleteResource(a[4]);
+  transaction_->DeleteResource(a[4]);
   ASSERT_EQ("b", listener_->ancestorId_);
   ASSERT_EQ(ResourceType_Study, listener_->ancestorType_);
 
   listener_->Reset();
-  index_->DeleteResource(a[7]);
+  transaction_->DeleteResource(a[7]);
   ASSERT_EQ("a", listener_->ancestorId_);
   ASSERT_EQ(ResourceType_Patient, listener_->ancestorType_);
 
   listener_->Reset();
-  index_->DeleteResource(a[6]);
+  transaction_->DeleteResource(a[6]);
   ASSERT_EQ("", listener_->ancestorId_);  // No more ancestor
 }
 
@@ -472,10 +473,10 @@
   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, 
+    patients.push_back(transaction_->CreateResource(p, ResourceType_Patient));
+    transaction_->AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10, 
                                                 "md5-" + boost::lexical_cast<std::string>(i)));
-    ASSERT_FALSE(index_->IsProtectedPatient(patients[i]));
+    ASSERT_FALSE(transaction_->IsProtectedPatient(patients[i]));
   }
 
   CheckTableRecordCount(10u, "Resources");
@@ -484,8 +485,8 @@
   listener_->Reset();
   ASSERT_EQ(0u, listener_->deletedResources_.size());
 
-  index_->DeleteResource(patients[5]);
-  index_->DeleteResource(patients[0]);
+  transaction_->DeleteResource(patients[5]);
+  transaction_->DeleteResource(patients[0]);
   ASSERT_EQ(2u, listener_->deletedResources_.size());
 
   CheckTableRecordCount(8u, "Resources");
@@ -496,28 +497,28 @@
   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(transaction_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]);
+  transaction_->DeleteResource(p);
   ASSERT_EQ(3u, listener_->deletedResources_.size());
-  ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]);
-  index_->DeleteResource(p);
+  ASSERT_TRUE(transaction_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]);
+  transaction_->DeleteResource(p);
   ASSERT_EQ(4u, listener_->deletedResources_.size());
-  ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]);
-  index_->DeleteResource(p);
+  ASSERT_TRUE(transaction_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]);
+  transaction_->DeleteResource(p);
   ASSERT_EQ(5u, listener_->deletedResources_.size());
-  ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]);
-  index_->DeleteResource(p);
+  ASSERT_TRUE(transaction_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]);
+  transaction_->DeleteResource(p);
   ASSERT_EQ(6u, listener_->deletedResources_.size());
-  ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[6]);
-  index_->DeleteResource(p);
-  index_->DeleteResource(patients[8]);
+  ASSERT_TRUE(transaction_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[6]);
+  transaction_->DeleteResource(p);
+  transaction_->DeleteResource(patients[8]);
   ASSERT_EQ(8u, listener_->deletedResources_.size());
-  ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[7]);
-  index_->DeleteResource(p);
+  ASSERT_TRUE(transaction_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[7]);
+  transaction_->DeleteResource(p);
   ASSERT_EQ(9u, listener_->deletedResources_.size());
-  ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[9]);
-  index_->DeleteResource(p);
-  ASSERT_FALSE(index_->SelectPatientToRecycle(p));
+  ASSERT_TRUE(transaction_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[9]);
+  transaction_->DeleteResource(p);
+  ASSERT_FALSE(transaction_->SelectPatientToRecycle(p));
   ASSERT_EQ(10u, listener_->deletedResources_.size());
 
   ASSERT_EQ(10u, listener_->deletedFiles_.size());
@@ -533,39 +534,39 @@
   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,
+    patients.push_back(transaction_->CreateResource(p, ResourceType_Patient));
+    transaction_->AddAttachment(patients[i], FileInfo(p, FileContentType_Dicom, i + 10,
                                                 "md5-" + boost::lexical_cast<std::string>(i)));
-    ASSERT_FALSE(index_->IsProtectedPatient(patients[i]));
+    ASSERT_FALSE(transaction_->IsProtectedPatient(patients[i]));
   }
 
   CheckTableRecordCount(5, "Resources");
   CheckTableRecordCount(5, "PatientRecyclingOrder");
 
-  ASSERT_FALSE(index_->IsProtectedPatient(patients[2]));
-  index_->SetProtectedPatient(patients[2], true);
-  ASSERT_TRUE(index_->IsProtectedPatient(patients[2]));
+  ASSERT_FALSE(transaction_->IsProtectedPatient(patients[2]));
+  transaction_->SetProtectedPatient(patients[2], true);
+  ASSERT_TRUE(transaction_->IsProtectedPatient(patients[2]));
   CheckTableRecordCount(5, "Resources");
   CheckTableRecordCount(4, "PatientRecyclingOrder");
 
-  index_->SetProtectedPatient(patients[2], true);
-  ASSERT_TRUE(index_->IsProtectedPatient(patients[2]));
+  transaction_->SetProtectedPatient(patients[2], true);
+  ASSERT_TRUE(transaction_->IsProtectedPatient(patients[2]));
   CheckTableRecordCount(4, "PatientRecyclingOrder");
-  index_->SetProtectedPatient(patients[2], false);
-  ASSERT_FALSE(index_->IsProtectedPatient(patients[2]));
+  transaction_->SetProtectedPatient(patients[2], false);
+  ASSERT_FALSE(transaction_->IsProtectedPatient(patients[2]));
   CheckTableRecordCount(5, "PatientRecyclingOrder");
-  index_->SetProtectedPatient(patients[2], false);
-  ASSERT_FALSE(index_->IsProtectedPatient(patients[2]));
+  transaction_->SetProtectedPatient(patients[2], false);
+  ASSERT_FALSE(transaction_->IsProtectedPatient(patients[2]));
   CheckTableRecordCount(5, "PatientRecyclingOrder");
   CheckTableRecordCount(5, "Resources");
-  index_->SetProtectedPatient(patients[2], true);
-  ASSERT_TRUE(index_->IsProtectedPatient(patients[2]));
+  transaction_->SetProtectedPatient(patients[2], true);
+  ASSERT_TRUE(transaction_->IsProtectedPatient(patients[2]));
   CheckTableRecordCount(4, "PatientRecyclingOrder");
-  index_->SetProtectedPatient(patients[2], false);
-  ASSERT_FALSE(index_->IsProtectedPatient(patients[2]));
+  transaction_->SetProtectedPatient(patients[2], false);
+  ASSERT_FALSE(transaction_->IsProtectedPatient(patients[2]));
   CheckTableRecordCount(5, "PatientRecyclingOrder");
-  index_->SetProtectedPatient(patients[3], true);
-  ASSERT_TRUE(index_->IsProtectedPatient(patients[3]));
+  transaction_->SetProtectedPatient(patients[3], true);
+  ASSERT_TRUE(transaction_->IsProtectedPatient(patients[3]));
   CheckTableRecordCount(4, "PatientRecyclingOrder");
 
   CheckTableRecordCount(5, "Resources");
@@ -574,33 +575,33 @@
   // Unprotecting a patient puts it at the last position in the recycling queue
   int64_t p;
   ASSERT_EQ(0u, listener_->deletedResources_.size());
-  ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[0]);
-  index_->DeleteResource(p);
+  ASSERT_TRUE(transaction_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[0]);
+  transaction_->DeleteResource(p);
   ASSERT_EQ(1u, listener_->deletedResources_.size());
-  ASSERT_TRUE(index_->SelectPatientToRecycle(p, patients[1])); ASSERT_EQ(p, patients[4]);
-  ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]);
-  index_->DeleteResource(p);
+  ASSERT_TRUE(transaction_->SelectPatientToRecycle(p, patients[1])); ASSERT_EQ(p, patients[4]);
+  ASSERT_TRUE(transaction_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[1]);
+  transaction_->DeleteResource(p);
   ASSERT_EQ(2u, listener_->deletedResources_.size());
-  ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]);
-  index_->DeleteResource(p);
+  ASSERT_TRUE(transaction_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[4]);
+  transaction_->DeleteResource(p);
   ASSERT_EQ(3u, listener_->deletedResources_.size());
-  ASSERT_FALSE(index_->SelectPatientToRecycle(p, patients[2]));
-  ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]);
-  index_->DeleteResource(p);
+  ASSERT_FALSE(transaction_->SelectPatientToRecycle(p, patients[2]));
+  ASSERT_TRUE(transaction_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[2]);
+  transaction_->DeleteResource(p);
   ASSERT_EQ(4u, listener_->deletedResources_.size());
   // "patients[3]" is still protected
-  ASSERT_FALSE(index_->SelectPatientToRecycle(p));
+  ASSERT_FALSE(transaction_->SelectPatientToRecycle(p));
 
   ASSERT_EQ(4u, listener_->deletedFiles_.size());
   CheckTableRecordCount(1, "Resources");
   CheckTableRecordCount(0, "PatientRecyclingOrder");
 
-  index_->SetProtectedPatient(patients[3], false);
+  transaction_->SetProtectedPatient(patients[3], false);
   CheckTableRecordCount(1, "PatientRecyclingOrder");
-  ASSERT_FALSE(index_->SelectPatientToRecycle(p, patients[3]));
-  ASSERT_TRUE(index_->SelectPatientToRecycle(p, patients[2]));
-  ASSERT_TRUE(index_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]);
-  index_->DeleteResource(p);
+  ASSERT_FALSE(transaction_->SelectPatientToRecycle(p, patients[3]));
+  ASSERT_TRUE(transaction_->SelectPatientToRecycle(p, patients[2]));
+  ASSERT_TRUE(transaction_->SelectPatientToRecycle(p)); ASSERT_EQ(p, patients[3]);
+  transaction_->DeleteResource(p);
   ASSERT_EQ(5u, listener_->deletedResources_.size());
 
   ASSERT_EQ(5u, listener_->deletedFiles_.size());
@@ -635,16 +636,16 @@
 TEST_F(DatabaseWrapperTest, LookupIdentifier)
 {
   int64_t a[] = {
-    index_->CreateResource("a", ResourceType_Study),   // 0
-    index_->CreateResource("b", ResourceType_Study),   // 1
-    index_->CreateResource("c", ResourceType_Study),   // 2
-    index_->CreateResource("d", ResourceType_Series)   // 3
+    transaction_->CreateResource("a", ResourceType_Study),   // 0
+    transaction_->CreateResource("b", ResourceType_Study),   // 1
+    transaction_->CreateResource("c", ResourceType_Study),   // 2
+    transaction_->CreateResource("d", ResourceType_Series)   // 3
   };
 
-  index_->SetIdentifierTag(a[0], DICOM_TAG_STUDY_INSTANCE_UID, "0");
-  index_->SetIdentifierTag(a[1], DICOM_TAG_STUDY_INSTANCE_UID, "1");
-  index_->SetIdentifierTag(a[2], DICOM_TAG_STUDY_INSTANCE_UID, "0");
-  index_->SetIdentifierTag(a[3], DICOM_TAG_SERIES_INSTANCE_UID, "0");
+  transaction_->SetIdentifierTag(a[0], DICOM_TAG_STUDY_INSTANCE_UID, "0");
+  transaction_->SetIdentifierTag(a[1], DICOM_TAG_STUDY_INSTANCE_UID, "1");
+  transaction_->SetIdentifierTag(a[2], DICOM_TAG_STUDY_INSTANCE_UID, "0");
+  transaction_->SetIdentifierTag(a[3], DICOM_TAG_SERIES_INSTANCE_UID, "0");
 
   std::list<std::string> s;