changeset 3093:2e1808b6146a db-changes

reorganization of folders
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sat, 05 Jan 2019 16:09:21 +0100
parents fc57988dbfd8
children 61da3c9b4121
files CMakeLists.txt OrthancServer/Database/Compatibility/DatabaseLookup.cpp OrthancServer/Database/Compatibility/DatabaseLookup.h OrthancServer/Database/Compatibility/ICreateInstance.cpp OrthancServer/Database/Compatibility/ICreateInstance.h OrthancServer/Database/Compatibility/IGetChildrenMetadata.cpp OrthancServer/Database/Compatibility/IGetChildrenMetadata.h OrthancServer/Database/Compatibility/ILookupResources.cpp OrthancServer/Database/Compatibility/ILookupResources.h OrthancServer/Database/Compatibility/ISetResourcesContent.h OrthancServer/Database/Compatibility/SetOfResources.cpp OrthancServer/Database/Compatibility/SetOfResources.h OrthancServer/Database/IDatabaseWrapper.h OrthancServer/Database/SQLiteDatabaseWrapper.cpp OrthancServer/Database/SQLiteDatabaseWrapper.h OrthancServer/IDatabaseWrapper.h OrthancServer/OrthancInitialization.cpp OrthancServer/OrthancInitialization.h OrthancServer/SQLiteDatabaseWrapper.cpp OrthancServer/SQLiteDatabaseWrapper.h OrthancServer/Search/Compatibility/DatabaseLookup.cpp OrthancServer/Search/Compatibility/DatabaseLookup.h OrthancServer/Search/Compatibility/ICreateInstance.cpp OrthancServer/Search/Compatibility/ICreateInstance.h OrthancServer/Search/Compatibility/IGetChildrenMetadata.cpp OrthancServer/Search/Compatibility/IGetChildrenMetadata.h OrthancServer/Search/Compatibility/ILookupResources.cpp OrthancServer/Search/Compatibility/ILookupResources.h OrthancServer/Search/Compatibility/ISetResourcesContent.h OrthancServer/Search/Compatibility/SetOfResources.cpp OrthancServer/Search/Compatibility/SetOfResources.h OrthancServer/ServerIndex.cpp OrthancServer/ServerIndex.h OrthancServer/ServerToolbox.cpp Plugins/Engine/OrthancPluginDatabase.h UnitTestsSources/MultiThreadingTests.cpp UnitTestsSources/ServerIndexTests.cpp
diffstat 37 files changed, 3168 insertions(+), 3168 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Sat Jan 05 15:50:55 2019 +0100
+++ b/CMakeLists.txt	Sat Jan 05 16:09:21 2019 +0100
@@ -53,6 +53,12 @@
 #####################################################################
 
 set(ORTHANC_SERVER_SOURCES
+  OrthancServer/Database/Compatibility/DatabaseLookup.cpp
+  OrthancServer/Database/Compatibility/ICreateInstance.cpp
+  OrthancServer/Database/Compatibility/IGetChildrenMetadata.cpp
+  OrthancServer/Database/Compatibility/ILookupResources.cpp
+  OrthancServer/Database/Compatibility/SetOfResources.cpp
+  OrthancServer/Database/SQLiteDatabaseWrapper.cpp
   OrthancServer/DicomInstanceOrigin.cpp
   OrthancServer/DicomInstanceToStore.cpp
   OrthancServer/ExportedResource.cpp
@@ -70,12 +76,6 @@
   OrthancServer/OrthancRestApi/OrthancRestResources.cpp
   OrthancServer/OrthancRestApi/OrthancRestSystem.cpp
   OrthancServer/QueryRetrieveHandler.cpp
-  OrthancServer/SQLiteDatabaseWrapper.cpp
-  OrthancServer/Search/Compatibility/DatabaseLookup.cpp
-  OrthancServer/Search/Compatibility/ICreateInstance.cpp
-  OrthancServer/Search/Compatibility/IGetChildrenMetadata.cpp
-  OrthancServer/Search/Compatibility/ILookupResources.cpp
-  OrthancServer/Search/Compatibility/SetOfResources.cpp
   OrthancServer/Search/DatabaseConstraint.cpp
   OrthancServer/Search/DatabaseLookup.cpp
   OrthancServer/Search/DicomTagConstraint.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Database/Compatibility/DatabaseLookup.cpp	Sat Jan 05 16:09:21 2019 +0100
@@ -0,0 +1,419 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "DatabaseLookup.h"
+
+#include "../../../Core/OrthancException.h"
+#include "../../ServerToolbox.h"
+#include "SetOfResources.h"
+
+namespace Orthanc
+{
+  namespace Compatibility
+  {
+    namespace
+    {
+      // Anonymous namespace to avoid clashes between compiler modules
+      class MainTagsConstraints : boost::noncopyable
+      {
+      private:
+        std::vector<DicomTagConstraint*>  constraints_;
+
+      public:
+        ~MainTagsConstraints()
+        {
+          for (size_t i = 0; i < constraints_.size(); i++)
+          {
+            assert(constraints_[i] != NULL);
+            delete constraints_[i];
+          }
+        }
+
+        void Reserve(size_t n)
+        {
+          constraints_.reserve(n);
+        }
+
+        size_t GetSize() const
+        {
+          return constraints_.size();
+        }
+
+        DicomTagConstraint& GetConstraint(size_t i) const
+        {
+          if (i >= constraints_.size())
+          {
+            throw OrthancException(ErrorCode_ParameterOutOfRange);
+          }
+          else
+          {
+            assert(constraints_[i] != NULL);
+            return *constraints_[i];
+          }
+        }
+        
+        void Add(const DatabaseConstraint& constraint)
+        {
+          constraints_.push_back(new DicomTagConstraint(constraint));
+        }          
+      };
+    }
+    
+    
+    static void ApplyIdentifierConstraint(SetOfResources& candidates,
+                                          ILookupResources& compatibility,
+                                          const DatabaseConstraint& constraint,
+                                          ResourceType level)
+    {
+      std::list<int64_t> matches;
+
+      switch (constraint.GetConstraintType())
+      {
+        case ConstraintType_Equal:
+          compatibility.LookupIdentifier(matches, level, constraint.GetTag(),
+                                    IdentifierConstraintType_Equal, constraint.GetSingleValue());
+          break;
+          
+        case ConstraintType_SmallerOrEqual:
+          compatibility.LookupIdentifier(matches, level, constraint.GetTag(),
+                                    IdentifierConstraintType_SmallerOrEqual, constraint.GetSingleValue());
+          break;
+          
+        case ConstraintType_GreaterOrEqual:
+          compatibility.LookupIdentifier(matches, level, constraint.GetTag(),
+                                    IdentifierConstraintType_GreaterOrEqual, constraint.GetSingleValue());
+
+          break;
+          
+        case ConstraintType_Wildcard:
+          compatibility.LookupIdentifier(matches, level, constraint.GetTag(),
+                                    IdentifierConstraintType_Wildcard, constraint.GetSingleValue());
+
+          break;
+          
+        case ConstraintType_List:
+          for (size_t i = 0; i < constraint.GetValuesCount(); i++)
+          {
+            std::list<int64_t> tmp;
+            compatibility.LookupIdentifier(tmp, level, constraint.GetTag(),
+                                      IdentifierConstraintType_Wildcard, constraint.GetValue(i));
+            matches.splice(matches.end(), tmp);
+          }
+
+          break;
+          
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+
+      candidates.Intersect(matches);
+    }
+
+    
+    static void ApplyIdentifierRange(SetOfResources& candidates,
+                                     ILookupResources& compatibility,
+                                     const DatabaseConstraint& smaller,
+                                     const DatabaseConstraint& greater,
+                                     ResourceType level)
+    {
+      assert(smaller.GetConstraintType() == ConstraintType_SmallerOrEqual &&
+             greater.GetConstraintType() == ConstraintType_GreaterOrEqual &&
+             smaller.GetTag() == greater.GetTag() &&
+             ServerToolbox::IsIdentifier(smaller.GetTag(), level));
+
+      std::list<int64_t> matches;
+      compatibility.LookupIdentifierRange(matches, level, smaller.GetTag(),
+                                     greater.GetSingleValue(), smaller.GetSingleValue());
+      candidates.Intersect(matches);
+    }
+
+    
+    static void ApplyLevel(SetOfResources& candidates,
+                           IDatabaseWrapper& database,
+                           ILookupResources& compatibility,
+                           const std::vector<DatabaseConstraint>& lookup,
+                           ResourceType level)
+    {
+      typedef std::set<const DatabaseConstraint*>  SetOfConstraints;
+      typedef std::map<DicomTag, SetOfConstraints> Identifiers;
+
+      // (1) Select which constraints apply to this level, and split
+      // them between "identifier tags" constraints and "main DICOM
+      // tags" constraints
+
+      Identifiers       identifiers;
+      SetOfConstraints  mainTags;
+      
+      for (size_t i = 0; i < lookup.size(); i++)
+      {
+        if (lookup[i].GetLevel() == level)
+        {
+          if (lookup[i].IsIdentifier())
+          {
+            identifiers[lookup[i].GetTag()].insert(&lookup[i]);
+          }
+          else
+          {
+            mainTags.insert(&lookup[i]);
+          }
+        }
+      }
+
+      
+      // (2) Apply the constraints over the identifiers
+      
+      for (Identifiers::const_iterator it = identifiers.begin();
+           it != identifiers.end(); ++it)
+      {
+        // Check whether some range constraint over identifiers is
+        // present at this level
+        const DatabaseConstraint* smaller = NULL;
+        const DatabaseConstraint* greater = NULL;
+        
+        for (SetOfConstraints::const_iterator it2 = it->second.begin();
+             it2 != it->second.end(); ++it2)
+        {
+          assert(*it2 != NULL);
+        
+          if ((*it2)->GetConstraintType() == ConstraintType_SmallerOrEqual)
+          {
+            smaller = *it2;
+          }
+
+          if ((*it2)->GetConstraintType() == ConstraintType_GreaterOrEqual)
+          {
+            greater = *it2;
+          }
+        }
+
+        if (smaller != NULL &&
+            greater != NULL)
+        {
+          // There is a range constraint: Apply it, as it is more efficient
+          ApplyIdentifierRange(candidates, compatibility, *smaller, *greater, level);
+        }
+        else
+        {
+          smaller = NULL;
+          greater = NULL;
+        }
+
+        for (SetOfConstraints::const_iterator it2 = it->second.begin();
+             it2 != it->second.end(); ++it2)
+        {
+          // Check to avoid applying twice the range constraint
+          if (*it2 != smaller &&
+              *it2 != greater)
+          {
+            ApplyIdentifierConstraint(candidates, compatibility, **it2, level);
+          }
+        }
+      }
+
+
+      // (3) Apply the constraints over the main DICOM tags (no index
+      // here, so this is less efficient than filtering over the
+      // identifiers)
+      if (!mainTags.empty())
+      {
+        MainTagsConstraints c;
+        c.Reserve(mainTags.size());
+        
+        for (SetOfConstraints::const_iterator it = mainTags.begin();
+             it != mainTags.end(); ++it)
+        {
+          assert(*it != NULL);
+          c.Add(**it);
+        }
+
+        std::list<int64_t>  source;
+        candidates.Flatten(compatibility, source);
+        candidates.Clear();
+
+        std::list<int64_t>  filtered;
+        for (std::list<int64_t>::const_iterator candidate = source.begin(); 
+             candidate != source.end(); ++candidate)
+        {
+          DicomMap tags;
+          database.GetMainDicomTags(tags, *candidate);
+
+          bool match = true;
+
+          for (size_t i = 0; i < c.GetSize(); i++)
+          {
+            if (!c.GetConstraint(i).IsMatch(tags))
+            {
+              match = false;
+              break;
+            }
+          }
+        
+          if (match)
+          {
+            filtered.push_back(*candidate);
+          }
+        }
+
+        candidates.Intersect(filtered);
+      }
+    }
+
+
+    static std::string GetOneInstance(IDatabaseWrapper& compatibility,
+                                      int64_t resource,
+                                      ResourceType level)
+    {
+      for (int i = level; i < ResourceType_Instance; i++)
+      {
+        assert(compatibility.GetResourceType(resource) == static_cast<ResourceType>(i));
+
+        std::list<int64_t> children;
+        compatibility.GetChildrenInternalId(children, resource);
+          
+        if (children.empty())
+        {
+          throw OrthancException(ErrorCode_Database);
+        }
+          
+        resource = children.front();
+      }
+
+      return compatibility.GetPublicId(resource);
+    }
+                           
+
+    void DatabaseLookup::ApplyLookupResources(std::list<std::string>& resourcesId,
+                                              std::list<std::string>* instancesId,
+                                              const std::vector<DatabaseConstraint>& lookup,
+                                              ResourceType queryLevel,
+                                              size_t limit)
+    {
+      // This is a re-implementation of
+      // "../../../Resources/Graveyard/DatabaseOptimizations/LookupResource.cpp"
+
+      assert(ResourceType_Patient < ResourceType_Study &&
+             ResourceType_Study < ResourceType_Series &&
+             ResourceType_Series < ResourceType_Instance);
+    
+      ResourceType upperLevel = queryLevel;
+      ResourceType lowerLevel = queryLevel;
+
+      for (size_t i = 0; i < lookup.size(); i++)
+      {
+        ResourceType level = lookup[i].GetLevel();
+
+        if (level < upperLevel)
+        {
+          upperLevel = level;
+        }
+
+        if (level > lowerLevel)
+        {
+          lowerLevel = level;
+        }
+      }
+
+      assert(upperLevel <= queryLevel &&
+             queryLevel <= lowerLevel);
+
+      SetOfResources candidates(database_, upperLevel);
+
+      for (int level = upperLevel; level <= lowerLevel; level++)
+      {
+        ApplyLevel(candidates, database_, compatibility_, lookup, static_cast<ResourceType>(level));
+
+        if (level != lowerLevel)
+        {
+          candidates.GoDown();
+        }
+      }
+
+      std::list<int64_t> resources;
+      candidates.Flatten(compatibility_, resources);
+
+      // Climb up, up to queryLevel
+
+      for (int level = lowerLevel; level > queryLevel; level--)
+      {
+        std::list<int64_t> parents;
+        for (std::list<int64_t>::const_iterator
+               it = resources.begin(); it != resources.end(); ++it)
+        {
+          int64_t parent;
+          if (database_.LookupParent(parent, *it))
+          {
+            parents.push_back(parent);
+          }
+        }
+
+        resources.swap(parents);
+      }
+
+      // Apply the limit, if given
+
+      if (limit != 0 &&
+          resources.size() > limit)
+      {
+        resources.resize(limit);
+      }
+
+      // Get the public ID of all the selected resources
+
+      size_t pos = 0;
+
+      for (std::list<int64_t>::const_iterator
+             it = resources.begin(); it != resources.end(); ++it, pos++)
+      {
+        assert(database_.GetResourceType(*it) == queryLevel);
+
+        const std::string resource = database_.GetPublicId(*it);
+        resourcesId.push_back(resource);
+
+        if (instancesId != NULL)
+        {
+          if (queryLevel == ResourceType_Instance)
+          {
+            // The resource is itself the instance
+            instancesId->push_back(resource);
+          }
+          else
+          {
+            // Collect one child instance for each of the selected resources
+            instancesId->push_back(GetOneInstance(database_, *it, queryLevel));
+          }
+        }
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Database/Compatibility/DatabaseLookup.h	Sat Jan 05 16:09:21 2019 +0100
@@ -0,0 +1,64 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "../IDatabaseWrapper.h"
+#include "ILookupResources.h"
+
+namespace Orthanc
+{
+  namespace Compatibility
+  {
+    class DatabaseLookup : public boost::noncopyable
+    {
+    private:
+      IDatabaseWrapper&              database_;
+      ILookupResources&  compatibility_;
+
+    public:
+      DatabaseLookup(IDatabaseWrapper&  database,
+                     ILookupResources& compatibility) :
+        database_(database),
+        compatibility_(compatibility)
+      {
+      }
+
+      void ApplyLookupResources(std::list<std::string>& resourcesId,
+                                std::list<std::string>* instancesId,
+                                const std::vector<DatabaseConstraint>& lookup,
+                                ResourceType queryLevel,
+                                size_t limit);
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Database/Compatibility/ICreateInstance.cpp	Sat Jan 05 16:09:21 2019 +0100
@@ -0,0 +1,155 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "ICreateInstance.h"
+
+#include "../../../Core/OrthancException.h"
+
+namespace Orthanc
+{
+  namespace Compatibility
+  {
+    bool ICreateInstance::Apply(ICreateInstance& database,
+                                IDatabaseWrapper::CreateInstanceResult& result,
+                                int64_t& instanceId,
+                                const std::string& hashPatient,
+                                const std::string& hashStudy,
+                                const std::string& hashSeries,
+                                const std::string& hashInstance)
+    {
+      {
+        ResourceType type;
+        int64_t tmp;
+        
+        if (database.LookupResource(tmp, type, hashInstance))
+        {
+          // The instance already exists
+          assert(type == ResourceType_Instance);
+          instanceId = tmp;
+          return false;
+        }
+      }
+
+      instanceId = database.CreateResource(hashInstance, ResourceType_Instance);
+
+      result.isNewPatient_ = false;
+      result.isNewStudy_ = false;
+      result.isNewSeries_ = false;
+      result.patientId_ = -1;
+      result.studyId_ = -1;
+      result.seriesId_ = -1;
+      
+      // Detect up to which level the patient/study/series/instance
+      // hierarchy must be created
+
+      {
+        ResourceType dummy;
+
+        if (database.LookupResource(result.seriesId_, dummy, hashSeries))
+        {
+          assert(dummy == ResourceType_Series);
+          // The patient, the study and the series already exist
+
+          bool ok = (database.LookupResource(result.patientId_, dummy, hashPatient) &&
+                     database.LookupResource(result.studyId_, dummy, hashStudy));
+          assert(ok);
+        }
+        else if (database.LookupResource(result.studyId_, dummy, hashStudy))
+        {
+          assert(dummy == ResourceType_Study);
+
+          // New series: The patient and the study already exist
+          result.isNewSeries_ = true;
+
+          bool ok = database.LookupResource(result.patientId_, dummy, hashPatient);
+          assert(ok);
+        }
+        else if (database.LookupResource(result.patientId_, dummy, hashPatient))
+        {
+          assert(dummy == ResourceType_Patient);
+
+          // New study and series: The patient already exist
+          result.isNewStudy_ = true;
+          result.isNewSeries_ = true;
+        }
+        else
+        {
+          // New patient, study and series: Nothing exists
+          result.isNewPatient_ = true;
+          result.isNewStudy_ = true;
+          result.isNewSeries_ = true;
+        }
+      }
+
+      // Create the series if needed
+      if (result.isNewSeries_)
+      {
+        result.seriesId_ = database.CreateResource(hashSeries, ResourceType_Series);
+      }
+
+      // Create the study if needed
+      if (result.isNewStudy_)
+      {
+        result.studyId_ = database.CreateResource(hashStudy, ResourceType_Study);
+      }
+
+      // Create the patient if needed
+      if (result.isNewPatient_)
+      {
+        result.patientId_ = database.CreateResource(hashPatient, ResourceType_Patient);
+      }
+
+      // Create the parent-to-child links
+      database.AttachChild(result.seriesId_, instanceId);
+
+      if (result.isNewSeries_)
+      {
+        database.AttachChild(result.studyId_, result.seriesId_);
+      }
+
+      if (result.isNewStudy_)
+      {
+        database.AttachChild(result.patientId_, result.studyId_);
+      }
+
+      // Sanity checks
+      assert(result.patientId_ != -1);
+      assert(result.studyId_ != -1);
+      assert(result.seriesId_ != -1);
+      assert(instanceId != -1);
+
+      return true;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Database/Compatibility/ICreateInstance.h	Sat Jan 05 16:09:21 2019 +0100
@@ -0,0 +1,64 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "../IDatabaseWrapper.h"
+
+namespace Orthanc
+{
+  namespace Compatibility
+  {
+    class ICreateInstance : public boost::noncopyable
+    {
+    public:
+      virtual bool LookupResource(int64_t& id,
+                                  ResourceType& type,
+                                  const std::string& publicId) = 0;
+
+      virtual int64_t CreateResource(const std::string& publicId,
+                                     ResourceType type) = 0;
+
+      virtual void AttachChild(int64_t parent,
+                               int64_t child) = 0;
+      
+      static bool Apply(ICreateInstance& database,
+                        IDatabaseWrapper::CreateInstanceResult& result,
+                        int64_t& instanceId,
+                        const std::string& patient,
+                        const std::string& study,
+                        const std::string& series,
+                        const std::string& instance);
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Database/Compatibility/IGetChildrenMetadata.cpp	Sat Jan 05 16:09:21 2019 +0100
@@ -0,0 +1,67 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "IGetChildrenMetadata.h"
+
+namespace Orthanc
+{
+  namespace Compatibility
+  {
+    void IGetChildrenMetadata::Apply(IGetChildrenMetadata& database,
+                                     std::list<std::string>& target,
+                                     int64_t resourceId,
+                                     MetadataType metadata)
+    {
+      // This function comes from an optimization of
+      // "ServerIndex::GetSeriesStatus()" in Orthanc <= 1.5.1
+      // Loop over the instances of this series
+
+      target.clear();
+      
+      std::list<int64_t> children;
+      database.GetChildrenInternalId(children, resourceId);
+
+      std::set<int64_t> instances;
+      for (std::list<int64_t>::const_iterator 
+             it = children.begin(); it != children.end(); ++it)
+      {
+        std::string value;
+        if (database.LookupMetadata(value, *it, metadata))
+        {
+          target.push_back(value);
+        }
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Database/Compatibility/IGetChildrenMetadata.h	Sat Jan 05 16:09:21 2019 +0100
@@ -0,0 +1,61 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "../../ServerEnumerations.h"
+
+#include <boost/noncopyable.hpp>
+#include <list>
+
+namespace Orthanc
+{
+  namespace Compatibility
+  {
+    class IGetChildrenMetadata : public boost::noncopyable
+    {
+    public:
+      virtual void GetChildrenInternalId(std::list<int64_t>& target,
+                                         int64_t id) = 0;
+
+      virtual bool LookupMetadata(std::string& target,
+                                  int64_t id,
+                                  MetadataType type) = 0;
+
+      static void Apply(IGetChildrenMetadata& database,
+                        std::list<std::string>& target,
+                        int64_t resourceId,
+                        MetadataType metadata);
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Database/Compatibility/ILookupResources.cpp	Sat Jan 05 16:09:21 2019 +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-2019 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 "ILookupResources.h"
+
+#include "DatabaseLookup.h"
+
+namespace Orthanc
+{
+  namespace Compatibility
+  {
+    void ILookupResources::Apply(
+      IDatabaseWrapper& database,
+      ILookupResources& compatibility,
+      std::list<std::string>& resourcesId,
+      std::list<std::string>* instancesId,
+      const std::vector<DatabaseConstraint>& lookup,
+      ResourceType queryLevel,
+      size_t limit)
+    {
+      Compatibility::DatabaseLookup compat(database, compatibility);
+      compat.ApplyLookupResources(resourcesId, instancesId, lookup, queryLevel, limit);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Database/Compatibility/ILookupResources.h	Sat Jan 05 16:09:21 2019 +0100
@@ -0,0 +1,78 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "../IDatabaseWrapper.h"
+
+namespace Orthanc
+{
+  namespace Compatibility
+  {
+    /**
+     * This is a compatibility class that contains database primitives
+     * that were used in Orthanc <= 1.5.1, and that have been removed
+     * during the optimization of the database engine.
+     **/
+    class ILookupResources : public boost::noncopyable
+    {     
+    public:
+      virtual ~ILookupResources()
+      {
+      }
+      
+      virtual void GetAllInternalIds(std::list<int64_t>& target,
+                                     ResourceType resourceType) = 0;
+      
+      virtual void LookupIdentifier(std::list<int64_t>& result,
+                                    ResourceType level,
+                                    const DicomTag& tag,
+                                    IdentifierConstraintType type,
+                                    const std::string& value) = 0;
+ 
+      virtual void LookupIdentifierRange(std::list<int64_t>& result,
+                                         ResourceType level,
+                                         const DicomTag& tag,
+                                         const std::string& start,
+                                         const std::string& end) = 0;
+
+      static void Apply(IDatabaseWrapper& database,
+                        ILookupResources& compatibility,
+                        std::list<std::string>& resourcesId,
+                        std::list<std::string>* instancesId,
+                        const std::vector<DatabaseConstraint>& lookup,
+                        ResourceType queryLevel,
+                        size_t limit);
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Database/Compatibility/ISetResourcesContent.h	Sat Jan 05 16:09:21 2019 +0100
@@ -0,0 +1,68 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "../../ServerToolbox.h"
+
+namespace Orthanc
+{
+  namespace Compatibility
+  {
+    class ISetResourcesContent : public boost::noncopyable
+    {
+    public:
+      virtual ~ISetResourcesContent()
+      {
+      }
+      
+      virtual void SetMainDicomTag(int64_t id,
+                                   const DicomTag& tag,
+                                   const std::string& value) = 0;
+
+      virtual void SetIdentifierTag(int64_t id,
+                                    const DicomTag& tag,
+                                    const std::string& value) = 0;
+
+      virtual void SetMetadata(int64_t id,
+                               MetadataType type,
+                               const std::string& value) = 0;
+
+      static void Apply(ISetResourcesContent& that,
+                        const ResourcesContent& content)
+      {
+        content.Store(that);
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Database/Compatibility/SetOfResources.cpp	Sat Jan 05 16:09:21 2019 +0100
@@ -0,0 +1,161 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "SetOfResources.h"
+
+#include "../../../Core/OrthancException.h"
+
+
+namespace Orthanc
+{
+  namespace Compatibility
+  {
+    void SetOfResources::Intersect(const std::list<int64_t>& resources)
+    {
+      if (resources_.get() == NULL)
+      {
+        resources_.reset(new Resources);
+
+        for (std::list<int64_t>::const_iterator
+               it = resources.begin(); it != resources.end(); ++it)
+        {
+          resources_->insert(*it);
+        }
+      }
+      else
+      {
+        std::auto_ptr<Resources> filtered(new Resources);
+
+        for (std::list<int64_t>::const_iterator
+               it = resources.begin(); it != resources.end(); ++it)
+        {
+          if (resources_->find(*it) != resources_->end())
+          {
+            filtered->insert(*it);
+          }
+        }
+
+        resources_ = filtered;
+      }
+    }
+
+
+    void SetOfResources::GoDown()
+    {
+      if (level_ == ResourceType_Instance)
+      {
+        throw OrthancException(ErrorCode_BadSequenceOfCalls);
+      }
+
+      if (resources_.get() != NULL)
+      {
+        std::auto_ptr<Resources> children(new Resources);
+
+        for (Resources::const_iterator it = resources_->begin(); 
+             it != resources_->end(); ++it)
+        {
+          std::list<int64_t> tmp;
+          database_.GetChildrenInternalId(tmp, *it);
+
+          for (std::list<int64_t>::const_iterator
+                 child = tmp.begin(); child != tmp.end(); ++child)
+          {
+            children->insert(*child);
+          }
+        }
+
+        resources_ = children;
+      }
+
+      switch (level_)
+      {
+        case ResourceType_Patient:
+          level_ = ResourceType_Study;
+          break;
+
+        case ResourceType_Study:
+          level_ = ResourceType_Series;
+          break;
+
+        case ResourceType_Series:
+          level_ = ResourceType_Instance;
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+
+
+    void SetOfResources::Flatten(std::list<std::string>& result)
+    {
+      result.clear();
+      
+      if (resources_.get() == NULL)
+      {
+        // All the resources of this level are part of the filter
+        database_.GetAllPublicIds(result, level_);
+      }
+      else
+      {
+        for (Resources::const_iterator it = resources_->begin(); 
+             it != resources_->end(); ++it)
+        {
+          result.push_back(database_.GetPublicId(*it));
+        }
+      }
+    }
+
+
+    void SetOfResources::Flatten(ILookupResources& compatibility,
+                                 std::list<int64_t>& result)
+    {
+      result.clear();
+      
+      if (resources_.get() == NULL)
+      {
+        // All the resources of this level are part of the filter
+        compatibility.GetAllInternalIds(result, level_);
+      }
+      else
+      {
+        for (Resources::const_iterator it = resources_->begin(); 
+             it != resources_->end(); ++it)
+        {
+          result.push_back(*it);
+        }
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Database/Compatibility/SetOfResources.h	Sat Jan 05 16:09:21 2019 +0100
@@ -0,0 +1,83 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "../IDatabaseWrapper.h"
+#include "ILookupResources.h"
+
+#include <set>
+#include <memory>
+
+namespace Orthanc
+{
+  namespace Compatibility
+  {
+    class SetOfResources : public boost::noncopyable
+    {
+    private:
+      typedef std::set<int64_t>  Resources;
+
+      IDatabaseWrapper&         database_;
+      ResourceType              level_;
+      std::auto_ptr<Resources>  resources_;
+    
+    public:
+      SetOfResources(IDatabaseWrapper& database,
+                     ResourceType level) : 
+        database_(database),
+        level_(level)
+      {
+      }
+
+      ResourceType GetLevel() const
+      {
+        return level_;
+      }
+
+      void Intersect(const std::list<int64_t>& resources);
+
+      void GoDown();
+
+      void Flatten(ILookupResources& compatibility,
+                   std::list<int64_t>& result);
+
+      void Flatten(std::list<std::string>& result);
+
+      void Clear()
+      {
+        resources_.reset(NULL);
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Database/IDatabaseWrapper.h	Sat Jan 05 16:09:21 2019 +0100
@@ -0,0 +1,245 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "../../Core/DicomFormat/DicomMap.h"
+#include "../../Core/FileStorage/FileInfo.h"
+#include "../../Core/FileStorage/IStorageArea.h"
+#include "../../Core/SQLite/ITransaction.h"
+
+#include "../ExportedResource.h"
+#include "../IDatabaseListener.h"
+#include "../Search/DatabaseConstraint.h"
+#include "../Search/DatabaseLookup.h"
+
+#include <list>
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class ResourcesContent;
+
+  
+  class IDatabaseWrapper : public boost::noncopyable
+  {
+  public:
+    class ITransaction : public boost::noncopyable
+    {
+    public:
+      virtual ~ITransaction()
+      {
+      }
+
+      virtual void Begin() = 0;
+
+      virtual void Rollback() = 0;
+
+      virtual void Commit(int64_t fileSizeDelta) = 0;
+    };
+
+
+    struct CreateInstanceResult
+    {
+      bool     isNewPatient_;
+      bool     isNewStudy_;
+      bool     isNewSeries_;
+      int64_t  patientId_;
+      int64_t  studyId_;
+      int64_t  seriesId_;
+    };
+
+    virtual ~IDatabaseWrapper()
+    {
+    }
+
+    virtual void Open() = 0;
+
+    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 ListAvailableMetadata(std::list<MetadataType>& target,
+                                       int64_t id) = 0;
+
+    virtual void ListAvailableAttachments(std::list<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() = 0;
+
+    virtual void SetListener(IDatabaseListener& listener) = 0;
+
+    virtual unsigned int GetDatabaseVersion() = 0;
+
+    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 already exists. If "false" is
+    // returned, the content of "result" is undefined, but
+    // "instanceId" must be properly set.
+    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;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Database/SQLiteDatabaseWrapper.cpp	Sat Jan 05 16:09:21 2019 +0100
@@ -0,0 +1,1273 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "SQLiteDatabaseWrapper.h"
+
+#include "../../Core/DicomFormat/DicomArray.h"
+#include "../../Core/Logging.h"
+#include "../../Core/SQLite/Transaction.h"
+#include "../Search/ISqlLookupFormatter.h"
+#include "../ServerToolbox.h"
+
+#include <EmbeddedResources.h>
+
+#include <stdio.h>
+#include <boost/lexical_cast.hpp>
+
+namespace Orthanc
+{
+  namespace Internals
+  {
+    class SignalFileDeleted : public SQLite::IScalarFunction
+    {
+    private:
+      IDatabaseListener& listener_;
+
+    public:
+      SignalFileDeleted(IDatabaseListener& listener) :
+        listener_(listener)
+      {
+      }
+
+      virtual const char* GetName() const
+      {
+        return "SignalFileDeleted";
+      }
+
+      virtual unsigned int GetCardinality() const
+      {
+        return 7;
+      }
+
+      virtual void Compute(SQLite::FunctionContext& context)
+      {
+        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);
+        
+        listener_.SignalFileDeleted(info);
+      }
+    };
+
+    class SignalResourceDeleted : public SQLite::IScalarFunction
+    {
+    private:
+      IDatabaseListener& listener_;
+
+    public:
+      SignalResourceDeleted(IDatabaseListener& listener) :
+        listener_(listener)
+      {
+      }
+
+      virtual const char* GetName() const
+      {
+        return "SignalResourceDeleted";
+      }
+
+      virtual unsigned int GetCardinality() const
+      {
+        return 2;
+      }
+
+      virtual void Compute(SQLite::FunctionContext& context)
+      {
+        ResourceType type = static_cast<ResourceType>(context.GetIntValue(1));
+        ServerIndexChange change(ChangeType_Deleted, type, context.GetStringValue(0));
+        listener_.SignalChange(change);
+      }
+    };
+
+    class SignalRemainingAncestor : public SQLite::IScalarFunction
+    {
+    private:
+      bool hasRemainingAncestor_;
+      std::string remainingPublicId_;
+      ResourceType remainingType_;
+
+    public:
+      SignalRemainingAncestor() : 
+        hasRemainingAncestor_(false)
+      {
+      }
+
+      void Reset()
+      {
+        hasRemainingAncestor_ = false;
+      }
+
+      virtual const char* GetName() const
+      {
+        return "SignalRemainingAncestor";
+      }
+
+      virtual unsigned int GetCardinality() const
+      {
+        return 2;
+      }
+
+      virtual void Compute(SQLite::FunctionContext& context)
+      {
+        VLOG(1) << "There exists a remaining ancestor with public ID \""
+                << context.GetStringValue(0)
+                << "\" of type "
+                << context.GetIntValue(1);
+
+        if (!hasRemainingAncestor_ ||
+            remainingType_ >= context.GetIntValue(1))
+        {
+          hasRemainingAncestor_ = true;
+          remainingPublicId_ = context.GetStringValue(0);
+          remainingType_ = static_cast<ResourceType>(context.GetIntValue(1));
+        }
+      }
+
+      bool HasRemainingAncestor() const
+      {
+        return hasRemainingAncestor_;
+      }
+
+      const std::string& GetRemainingAncestorId() const
+      {
+        assert(hasRemainingAncestor_);
+        return remainingPublicId_;
+      }
+
+      ResourceType GetRemainingAncestorType() const
+      {
+        assert(hasRemainingAncestor_);
+        return remainingType_;
+      }
+    };
+  }
+
+
+  void SQLiteDatabaseWrapper::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())
+    {
+      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());
+  }
+
+
+  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);
+
+    childrenPublicIds.clear();
+    while (s.Step())
+    {
+      childrenPublicIds.push_back(s.ColumnString(0));
+    }
+  }
+
+
+  void SQLiteDatabaseWrapper::DeleteResource(int64_t id)
+  {
+    signalRemainingAncestor_->Reset();
+
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Resources WHERE internalId=?");
+    s.BindInt64(0, id);
+    s.Run();
+
+    if (signalRemainingAncestor_->HasRemainingAncestor() &&
+        listener_ != NULL)
+    {
+      listener_->SignalRemainingAncestor(signalRemainingAncestor_->GetRemainingAncestorType(),
+                                         signalRemainingAncestor_->GetRemainingAncestorId());
+    }
+  }
+
+
+  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);
+
+    if (s.Step())
+    {
+      target = s.ColumnString(0);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  int64_t SQLiteDatabaseWrapper::GetTableRecordCount(const std::string& table)
+  {
+    char buf[128];
+    sprintf(buf, "SELECT COUNT(*) FROM %s", table.c_str());
+    SQLite::Statement s(db_, buf);
+
+    if (!s.Step())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    int64_t c = s.ColumnInt(0);
+    assert(!s.Step());
+
+    return c;
+  }
+
+    
+  SQLiteDatabaseWrapper::SQLiteDatabaseWrapper(const std::string& path) : 
+    listener_(NULL), 
+    signalRemainingAncestor_(NULL),
+    version_(0)
+  {
+    db_.Open(path);
+  }
+
+
+  SQLiteDatabaseWrapper::SQLiteDatabaseWrapper() : 
+    listener_(NULL), 
+    signalRemainingAncestor_(NULL),
+    version_(0)
+  {
+    db_.OpenInMemory();
+  }
+
+
+  void SQLiteDatabaseWrapper::Open()
+  {
+    db_.Execute("PRAGMA ENCODING=\"UTF-8\";");
+
+    // Performance tuning of SQLite with PRAGMAs
+    // http://www.sqlite.org/pragma.html
+    db_.Execute("PRAGMA SYNCHRONOUS=NORMAL;");
+    db_.Execute("PRAGMA JOURNAL_MODE=WAL;");
+    db_.Execute("PRAGMA LOCKING_MODE=EXCLUSIVE;");
+    db_.Execute("PRAGMA WAL_AUTOCHECKPOINT=1000;");
+    //db_.Execute("PRAGMA TEMP_STORE=memory");
+
+    // Make "LIKE" case-sensitive in SQLite 
+    db_.Execute("PRAGMA case_sensitive_like = true;");
+    
+    if (!db_.DoesTableExist("GlobalProperties"))
+    {
+      LOG(INFO) << "Creating the database";
+      std::string query;
+      EmbeddedResources::GetFileResource(query, EmbeddedResources::PREPARE_DATABASE);
+      db_.Execute(query);
+    }
+
+    // Check the version of the database
+    std::string tmp;
+    if (!LookupGlobalProperty(tmp, GlobalProperty_DatabaseSchemaVersion))
+    {
+      tmp = "Unknown";
+    }
+
+    bool ok = false;
+    try
+    {
+      LOG(INFO) << "Version of the Orthanc database: " << tmp;
+      version_ = boost::lexical_cast<unsigned int>(tmp);
+      ok = true;
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+    }
+
+    if (!ok)
+    {
+      throw OrthancException(ErrorCode_IncompatibleDatabaseVersion,
+                             "Incompatible version of the Orthanc database: " + tmp);
+    }
+
+    // New in Orthanc 1.5.1
+    if (version_ == 6)
+    {
+      if (!LookupGlobalProperty(tmp, GlobalProperty_GetTotalSizeIsFast) ||
+          tmp != "1")
+      {
+        LOG(INFO) << "Installing the SQLite triggers to track the size of the attachments";
+        std::string query;
+        EmbeddedResources::GetFileResource(query, EmbeddedResources::INSTALL_TRACK_ATTACHMENTS_SIZE);
+        db_.Execute(query);
+      }
+    }
+
+    signalRemainingAncestor_ = new Internals::SignalRemainingAncestor;
+    db_.Register(signalRemainingAncestor_);
+  }
+
+
+  static void ExecuteUpgradeScript(SQLite::Connection& db,
+                                   EmbeddedResources::FileResourceId script)
+  {
+    std::string upgrade;
+    EmbeddedResources::GetFileResource(upgrade, script);
+    db.BeginTransaction();
+    db.Execute(upgrade);
+    db.CommitTransaction();    
+  }
+
+
+  void SQLiteDatabaseWrapper::Upgrade(unsigned int targetVersion,
+                                      IStorageArea& storageArea)
+  {
+    if (targetVersion != 6)
+    {
+      throw OrthancException(ErrorCode_IncompatibleDatabaseVersion);
+    }
+
+    // This version of Orthanc is only compatible with versions 3, 4,
+    // 5 and 6 of the DB schema
+    if (version_ != 3 &&
+        version_ != 4 &&
+        version_ != 5 &&
+        version_ != 6)
+    {
+      throw OrthancException(ErrorCode_IncompatibleDatabaseVersion);
+    }
+
+    if (version_ == 3)
+    {
+      LOG(WARNING) << "Upgrading database version from 3 to 4";
+      ExecuteUpgradeScript(db_, EmbeddedResources::UPGRADE_DATABASE_3_TO_4);
+      version_ = 4;
+    }
+
+    if (version_ == 4)
+    {
+      LOG(WARNING) << "Upgrading database version from 4 to 5";
+      ExecuteUpgradeScript(db_, EmbeddedResources::UPGRADE_DATABASE_4_TO_5);
+      version_ = 5;
+    }
+
+    if (version_ == 5)
+    {
+      LOG(WARNING) << "Upgrading database version from 5 to 6";
+      // 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();
+      version_ = 6;
+    }
+  }
+
+
+  void SQLiteDatabaseWrapper::SetListener(IDatabaseListener& listener)
+  {
+    listener_ = &listener;
+    db_.Register(new Internals::SignalFileDeleted(listener));
+    db_.Register(new Internals::SignalResourceDeleted(listener));
+  }
+
+
+  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::Transaction : public IDatabaseWrapper::ITransaction
+  {
+  private:
+    SQLiteDatabaseWrapper&              that_;
+    std::auto_ptr<SQLite::Transaction>  transaction_;
+    int64_t                             initialDiskSize_;
+
+  public:
+    Transaction(SQLiteDatabaseWrapper& that) :
+      that_(that),
+      transaction_(new SQLite::Transaction(that_.db_))
+    {
+#if defined(NDEBUG)
+      // Release mode
+      initialDiskSize_ = 0;
+#else
+      // Debug mode
+      initialDiskSize_ = static_cast<int64_t>(that_.GetTotalCompressedSize());
+#endif
+    }
+
+    virtual void Begin()
+    {
+      transaction_->Begin();
+    }
+
+    virtual void Rollback() 
+    {
+      transaction_->Rollback();
+    }
+
+    virtual void Commit(int64_t fileSizeDelta /* only used in debug */)
+    {
+      transaction_->Commit();
+
+      assert(initialDiskSize_ + fileSizeDelta >= 0 &&
+             initialDiskSize_ + fileSizeDelta == static_cast<int64_t>(that_.GetTotalCompressedSize()));
+    }
+  };
+
+
+  IDatabaseWrapper::ITransaction* SQLiteDatabaseWrapper::StartTransaction()
+  {
+    return new Transaction(*this);
+  }
+
+
+  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)
+  {
+    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();
+  }
+
+
+  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)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Resources SET parentId = ? WHERE internalId = ?");
+    s.BindInt64(0, parent);
+    s.BindInt64(1, child);
+    s.Run();
+  }
+
+
+  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::ListAvailableMetadata(std::list<MetadataType>& target,
+                                                    int64_t id)
+  {
+    target.clear();
+
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type FROM Metadata WHERE id=?");
+    s.BindInt64(0, id);
+
+    while (s.Step())
+    {
+      target.push_back(static_cast<MetadataType>(s.ColumnInt(0)));
+    }
+  }
+
+
+  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::list<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.push_back(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)
+  {
+    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();
+  }
+
+
+  void SQLiteDatabaseWrapper::GetMainDicomTags(DicomMap& map,
+                                               int64_t id)
+  {
+    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 = ?");     
+    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.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()
+  {
+    // 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
+    {
+      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;
+    }
+
+    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)
+    {
+      values_.push_back(value);
+      return "?";
+    }
+    
+    virtual std::string FormatResourceType(ResourceType level)
+    {
+      return boost::lexical_cast<std::string>(level);
+    }
+
+    virtual std::string FormatWildcardEscape()
+    {
+      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::auto_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));
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Database/SQLiteDatabaseWrapper.h	Sat Jan 05 16:09:21 2019 +0100
@@ -0,0 +1,358 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 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 "IDatabaseWrapper.h"
+
+#include "../../Core/SQLite/Connection.h"
+#include "../ServerToolbox.h"
+#include "Compatibility/ICreateInstance.h"
+#include "Compatibility/IGetChildrenMetadata.h"
+#include "Compatibility/ISetResourcesContent.h"
+
+namespace Orthanc
+{
+  namespace Internals
+  {
+    class SignalRemainingAncestor;
+  }
+
+  /**
+   * This class manages an instance of the Orthanc SQLite database. It
+   * 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::ISetResourcesContent
+  {
+  private:
+    class Transaction;
+    class LookupFormatter;
+
+    IDatabaseListener* listener_;
+    SQLite::Connection db_;
+    Internals::SignalRemainingAncestor* signalRemainingAncestor_;
+    unsigned int version_;
+
+    void GetChangesInternal(std::list<ServerIndexChange>& target,
+                            bool& done,
+                            SQLite::Statement& s,
+                            uint32_t maxResults);
+
+    void GetExportedResourcesInternal(std::list<ExportedResource>& target,
+                                      bool& done,
+                                      SQLite::Statement& s,
+                                      uint32_t maxResults);
+
+    void ClearTable(const std::string& tableName);
+
+  public:
+    SQLiteDatabaseWrapper(const std::string& path);
+
+    SQLiteDatabaseWrapper();
+
+    virtual void Open()
+      ORTHANC_OVERRIDE;
+
+    virtual void Close()
+      ORTHANC_OVERRIDE
+    {
+      db_.Close();
+    }
+
+    virtual void SetListener(IDatabaseListener& listener)
+      ORTHANC_OVERRIDE;
+
+    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()
+      ORTHANC_OVERRIDE;
+
+    virtual void FlushToDisk()
+      ORTHANC_OVERRIDE
+    {
+      db_.FlushToDisk();
+    }
+
+    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
+    {
+      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);
+
+
+
+    /**
+     * Until Orthanc 1.4.0, the methods below were part of the
+     * "DatabaseWrapperBase" class, that is now placed in the
+     * graveyard.
+     **/
+
+    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 ListAvailableMetadata(std::list<MetadataType>& target,
+                                       int64_t id)
+      ORTHANC_OVERRIDE;
+
+    virtual void AddAttachment(int64_t id,
+                               const FileInfo& attachment)
+      ORTHANC_OVERRIDE;
+
+    virtual void DeleteAttachment(int64_t id,
+                                  FileContentType attachment)
+      ORTHANC_OVERRIDE;
+
+    virtual void ListAvailableAttachments(std::list<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;
+    
+    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;
+
+    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;
+
+    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;
+
+    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);
+    }
+
+    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);
+    }
+  };
+}
--- a/OrthancServer/IDatabaseWrapper.h	Sat Jan 05 15:50:55 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,245 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "../Core/DicomFormat/DicomMap.h"
-#include "../Core/FileStorage/FileInfo.h"
-#include "../Core/FileStorage/IStorageArea.h"
-#include "../Core/SQLite/ITransaction.h"
-
-#include "ExportedResource.h"
-#include "IDatabaseListener.h"
-#include "Search/DatabaseConstraint.h"
-#include "Search/DatabaseLookup.h"
-
-#include <list>
-#include <boost/noncopyable.hpp>
-
-namespace Orthanc
-{
-  class ResourcesContent;
-
-  
-  class IDatabaseWrapper : public boost::noncopyable
-  {
-  public:
-    class ITransaction : public boost::noncopyable
-    {
-    public:
-      virtual ~ITransaction()
-      {
-      }
-
-      virtual void Begin() = 0;
-
-      virtual void Rollback() = 0;
-
-      virtual void Commit(int64_t fileSizeDelta) = 0;
-    };
-
-
-    struct CreateInstanceResult
-    {
-      bool     isNewPatient_;
-      bool     isNewStudy_;
-      bool     isNewSeries_;
-      int64_t  patientId_;
-      int64_t  studyId_;
-      int64_t  seriesId_;
-    };
-
-    virtual ~IDatabaseWrapper()
-    {
-    }
-
-    virtual void Open() = 0;
-
-    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 ListAvailableMetadata(std::list<MetadataType>& target,
-                                       int64_t id) = 0;
-
-    virtual void ListAvailableAttachments(std::list<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() = 0;
-
-    virtual void SetListener(IDatabaseListener& listener) = 0;
-
-    virtual unsigned int GetDatabaseVersion() = 0;
-
-    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 already exists. If "false" is
-    // returned, the content of "result" is undefined, but
-    // "instanceId" must be properly set.
-    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;
-  };
-}
--- a/OrthancServer/OrthancInitialization.cpp	Sat Jan 05 15:50:55 2019 +0100
+++ b/OrthancServer/OrthancInitialization.cpp	Sat Jan 05 16:09:21 2019 +0100
@@ -45,7 +45,7 @@
 #include "../Core/Logging.h"
 #include "../Core/OrthancException.h"
 
-#include "SQLiteDatabaseWrapper.h"
+#include "Database/SQLiteDatabaseWrapper.h"
 #include "OrthancConfiguration.h"
 
 #include <dcmtk/dcmnet/dul.h>   // For dcmDisableGethostbyaddr()
--- a/OrthancServer/OrthancInitialization.h	Sat Jan 05 15:50:55 2019 +0100
+++ b/OrthancServer/OrthancInitialization.h	Sat Jan 05 16:09:21 2019 +0100
@@ -34,7 +34,7 @@
 #pragma once
 
 #include "../Core/FileStorage/IStorageArea.h"
-#include "IDatabaseWrapper.h"
+#include "Database/IDatabaseWrapper.h"
 
 namespace Orthanc
 {
--- a/OrthancServer/SQLiteDatabaseWrapper.cpp	Sat Jan 05 15:50:55 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1272 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "SQLiteDatabaseWrapper.h"
-
-#include "../Core/DicomFormat/DicomArray.h"
-#include "../Core/Logging.h"
-#include "../Core/SQLite/Transaction.h"
-#include "EmbeddedResources.h"
-#include "Search/ISqlLookupFormatter.h"
-#include "ServerToolbox.h"
-
-#include <stdio.h>
-#include <boost/lexical_cast.hpp>
-
-namespace Orthanc
-{
-  namespace Internals
-  {
-    class SignalFileDeleted : public SQLite::IScalarFunction
-    {
-    private:
-      IDatabaseListener& listener_;
-
-    public:
-      SignalFileDeleted(IDatabaseListener& listener) :
-        listener_(listener)
-      {
-      }
-
-      virtual const char* GetName() const
-      {
-        return "SignalFileDeleted";
-      }
-
-      virtual unsigned int GetCardinality() const
-      {
-        return 7;
-      }
-
-      virtual void Compute(SQLite::FunctionContext& context)
-      {
-        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);
-        
-        listener_.SignalFileDeleted(info);
-      }
-    };
-
-    class SignalResourceDeleted : public SQLite::IScalarFunction
-    {
-    private:
-      IDatabaseListener& listener_;
-
-    public:
-      SignalResourceDeleted(IDatabaseListener& listener) :
-        listener_(listener)
-      {
-      }
-
-      virtual const char* GetName() const
-      {
-        return "SignalResourceDeleted";
-      }
-
-      virtual unsigned int GetCardinality() const
-      {
-        return 2;
-      }
-
-      virtual void Compute(SQLite::FunctionContext& context)
-      {
-        ResourceType type = static_cast<ResourceType>(context.GetIntValue(1));
-        ServerIndexChange change(ChangeType_Deleted, type, context.GetStringValue(0));
-        listener_.SignalChange(change);
-      }
-    };
-
-    class SignalRemainingAncestor : public SQLite::IScalarFunction
-    {
-    private:
-      bool hasRemainingAncestor_;
-      std::string remainingPublicId_;
-      ResourceType remainingType_;
-
-    public:
-      SignalRemainingAncestor() : 
-        hasRemainingAncestor_(false)
-      {
-      }
-
-      void Reset()
-      {
-        hasRemainingAncestor_ = false;
-      }
-
-      virtual const char* GetName() const
-      {
-        return "SignalRemainingAncestor";
-      }
-
-      virtual unsigned int GetCardinality() const
-      {
-        return 2;
-      }
-
-      virtual void Compute(SQLite::FunctionContext& context)
-      {
-        VLOG(1) << "There exists a remaining ancestor with public ID \""
-                << context.GetStringValue(0)
-                << "\" of type "
-                << context.GetIntValue(1);
-
-        if (!hasRemainingAncestor_ ||
-            remainingType_ >= context.GetIntValue(1))
-        {
-          hasRemainingAncestor_ = true;
-          remainingPublicId_ = context.GetStringValue(0);
-          remainingType_ = static_cast<ResourceType>(context.GetIntValue(1));
-        }
-      }
-
-      bool HasRemainingAncestor() const
-      {
-        return hasRemainingAncestor_;
-      }
-
-      const std::string& GetRemainingAncestorId() const
-      {
-        assert(hasRemainingAncestor_);
-        return remainingPublicId_;
-      }
-
-      ResourceType GetRemainingAncestorType() const
-      {
-        assert(hasRemainingAncestor_);
-        return remainingType_;
-      }
-    };
-  }
-
-
-  void SQLiteDatabaseWrapper::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())
-    {
-      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());
-  }
-
-
-  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);
-
-    childrenPublicIds.clear();
-    while (s.Step())
-    {
-      childrenPublicIds.push_back(s.ColumnString(0));
-    }
-  }
-
-
-  void SQLiteDatabaseWrapper::DeleteResource(int64_t id)
-  {
-    signalRemainingAncestor_->Reset();
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM Resources WHERE internalId=?");
-    s.BindInt64(0, id);
-    s.Run();
-
-    if (signalRemainingAncestor_->HasRemainingAncestor() &&
-        listener_ != NULL)
-    {
-      listener_->SignalRemainingAncestor(signalRemainingAncestor_->GetRemainingAncestorType(),
-                                         signalRemainingAncestor_->GetRemainingAncestorId());
-    }
-  }
-
-
-  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);
-
-    if (s.Step())
-    {
-      target = s.ColumnString(0);
-      return true;
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-  int64_t SQLiteDatabaseWrapper::GetTableRecordCount(const std::string& table)
-  {
-    char buf[128];
-    sprintf(buf, "SELECT COUNT(*) FROM %s", table.c_str());
-    SQLite::Statement s(db_, buf);
-
-    if (!s.Step())
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }
-
-    int64_t c = s.ColumnInt(0);
-    assert(!s.Step());
-
-    return c;
-  }
-
-    
-  SQLiteDatabaseWrapper::SQLiteDatabaseWrapper(const std::string& path) : 
-    listener_(NULL), 
-    signalRemainingAncestor_(NULL),
-    version_(0)
-  {
-    db_.Open(path);
-  }
-
-
-  SQLiteDatabaseWrapper::SQLiteDatabaseWrapper() : 
-    listener_(NULL), 
-    signalRemainingAncestor_(NULL),
-    version_(0)
-  {
-    db_.OpenInMemory();
-  }
-
-
-  void SQLiteDatabaseWrapper::Open()
-  {
-    db_.Execute("PRAGMA ENCODING=\"UTF-8\";");
-
-    // Performance tuning of SQLite with PRAGMAs
-    // http://www.sqlite.org/pragma.html
-    db_.Execute("PRAGMA SYNCHRONOUS=NORMAL;");
-    db_.Execute("PRAGMA JOURNAL_MODE=WAL;");
-    db_.Execute("PRAGMA LOCKING_MODE=EXCLUSIVE;");
-    db_.Execute("PRAGMA WAL_AUTOCHECKPOINT=1000;");
-    //db_.Execute("PRAGMA TEMP_STORE=memory");
-
-    // Make "LIKE" case-sensitive in SQLite 
-    db_.Execute("PRAGMA case_sensitive_like = true;");
-    
-    if (!db_.DoesTableExist("GlobalProperties"))
-    {
-      LOG(INFO) << "Creating the database";
-      std::string query;
-      EmbeddedResources::GetFileResource(query, EmbeddedResources::PREPARE_DATABASE);
-      db_.Execute(query);
-    }
-
-    // Check the version of the database
-    std::string tmp;
-    if (!LookupGlobalProperty(tmp, GlobalProperty_DatabaseSchemaVersion))
-    {
-      tmp = "Unknown";
-    }
-
-    bool ok = false;
-    try
-    {
-      LOG(INFO) << "Version of the Orthanc database: " << tmp;
-      version_ = boost::lexical_cast<unsigned int>(tmp);
-      ok = true;
-    }
-    catch (boost::bad_lexical_cast&)
-    {
-    }
-
-    if (!ok)
-    {
-      throw OrthancException(ErrorCode_IncompatibleDatabaseVersion,
-                             "Incompatible version of the Orthanc database: " + tmp);
-    }
-
-    // New in Orthanc 1.5.1
-    if (version_ == 6)
-    {
-      if (!LookupGlobalProperty(tmp, GlobalProperty_GetTotalSizeIsFast) ||
-          tmp != "1")
-      {
-        LOG(INFO) << "Installing the SQLite triggers to track the size of the attachments";
-        std::string query;
-        EmbeddedResources::GetFileResource(query, EmbeddedResources::INSTALL_TRACK_ATTACHMENTS_SIZE);
-        db_.Execute(query);
-      }
-    }
-
-    signalRemainingAncestor_ = new Internals::SignalRemainingAncestor;
-    db_.Register(signalRemainingAncestor_);
-  }
-
-
-  static void ExecuteUpgradeScript(SQLite::Connection& db,
-                                   EmbeddedResources::FileResourceId script)
-  {
-    std::string upgrade;
-    EmbeddedResources::GetFileResource(upgrade, script);
-    db.BeginTransaction();
-    db.Execute(upgrade);
-    db.CommitTransaction();    
-  }
-
-
-  void SQLiteDatabaseWrapper::Upgrade(unsigned int targetVersion,
-                                      IStorageArea& storageArea)
-  {
-    if (targetVersion != 6)
-    {
-      throw OrthancException(ErrorCode_IncompatibleDatabaseVersion);
-    }
-
-    // This version of Orthanc is only compatible with versions 3, 4,
-    // 5 and 6 of the DB schema
-    if (version_ != 3 &&
-        version_ != 4 &&
-        version_ != 5 &&
-        version_ != 6)
-    {
-      throw OrthancException(ErrorCode_IncompatibleDatabaseVersion);
-    }
-
-    if (version_ == 3)
-    {
-      LOG(WARNING) << "Upgrading database version from 3 to 4";
-      ExecuteUpgradeScript(db_, EmbeddedResources::UPGRADE_DATABASE_3_TO_4);
-      version_ = 4;
-    }
-
-    if (version_ == 4)
-    {
-      LOG(WARNING) << "Upgrading database version from 4 to 5";
-      ExecuteUpgradeScript(db_, EmbeddedResources::UPGRADE_DATABASE_4_TO_5);
-      version_ = 5;
-    }
-
-    if (version_ == 5)
-    {
-      LOG(WARNING) << "Upgrading database version from 5 to 6";
-      // 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();
-      version_ = 6;
-    }
-  }
-
-
-  void SQLiteDatabaseWrapper::SetListener(IDatabaseListener& listener)
-  {
-    listener_ = &listener;
-    db_.Register(new Internals::SignalFileDeleted(listener));
-    db_.Register(new Internals::SignalResourceDeleted(listener));
-  }
-
-
-  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::Transaction : public IDatabaseWrapper::ITransaction
-  {
-  private:
-    SQLiteDatabaseWrapper&              that_;
-    std::auto_ptr<SQLite::Transaction>  transaction_;
-    int64_t                             initialDiskSize_;
-
-  public:
-    Transaction(SQLiteDatabaseWrapper& that) :
-      that_(that),
-      transaction_(new SQLite::Transaction(that_.db_))
-    {
-#if defined(NDEBUG)
-      // Release mode
-      initialDiskSize_ = 0;
-#else
-      // Debug mode
-      initialDiskSize_ = static_cast<int64_t>(that_.GetTotalCompressedSize());
-#endif
-    }
-
-    virtual void Begin()
-    {
-      transaction_->Begin();
-    }
-
-    virtual void Rollback() 
-    {
-      transaction_->Rollback();
-    }
-
-    virtual void Commit(int64_t fileSizeDelta /* only used in debug */)
-    {
-      transaction_->Commit();
-
-      assert(initialDiskSize_ + fileSizeDelta >= 0 &&
-             initialDiskSize_ + fileSizeDelta == static_cast<int64_t>(that_.GetTotalCompressedSize()));
-    }
-  };
-
-
-  IDatabaseWrapper::ITransaction* SQLiteDatabaseWrapper::StartTransaction()
-  {
-    return new Transaction(*this);
-  }
-
-
-  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)
-  {
-    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();
-  }
-
-
-  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)
-  {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "UPDATE Resources SET parentId = ? WHERE internalId = ?");
-    s.BindInt64(0, parent);
-    s.BindInt64(1, child);
-    s.Run();
-  }
-
-
-  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::ListAvailableMetadata(std::list<MetadataType>& target,
-                                                    int64_t id)
-  {
-    target.clear();
-
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type FROM Metadata WHERE id=?");
-    s.BindInt64(0, id);
-
-    while (s.Step())
-    {
-      target.push_back(static_cast<MetadataType>(s.ColumnInt(0)));
-    }
-  }
-
-
-  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::list<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.push_back(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)
-  {
-    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();
-  }
-
-
-  void SQLiteDatabaseWrapper::GetMainDicomTags(DicomMap& map,
-                                               int64_t id)
-  {
-    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 = ?");     
-    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.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()
-  {
-    // 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
-    {
-      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;
-    }
-
-    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)
-    {
-      values_.push_back(value);
-      return "?";
-    }
-    
-    virtual std::string FormatResourceType(ResourceType level)
-    {
-      return boost::lexical_cast<std::string>(level);
-    }
-
-    virtual std::string FormatWildcardEscape()
-    {
-      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::auto_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));
-      }
-    }
-  }
-}
--- a/OrthancServer/SQLiteDatabaseWrapper.h	Sat Jan 05 15:50:55 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,358 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "IDatabaseWrapper.h"
-
-#include "../Core/SQLite/Connection.h"
-#include "Search/Compatibility/ICreateInstance.h"
-#include "Search/Compatibility/IGetChildrenMetadata.h"
-#include "Search/Compatibility/ISetResourcesContent.h"
-#include "ServerToolbox.h"
-
-namespace Orthanc
-{
-  namespace Internals
-  {
-    class SignalRemainingAncestor;
-  }
-
-  /**
-   * This class manages an instance of the Orthanc SQLite database. It
-   * 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::ISetResourcesContent
-  {
-  private:
-    class Transaction;
-    class LookupFormatter;
-
-    IDatabaseListener* listener_;
-    SQLite::Connection db_;
-    Internals::SignalRemainingAncestor* signalRemainingAncestor_;
-    unsigned int version_;
-
-    void GetChangesInternal(std::list<ServerIndexChange>& target,
-                            bool& done,
-                            SQLite::Statement& s,
-                            uint32_t maxResults);
-
-    void GetExportedResourcesInternal(std::list<ExportedResource>& target,
-                                      bool& done,
-                                      SQLite::Statement& s,
-                                      uint32_t maxResults);
-
-    void ClearTable(const std::string& tableName);
-
-  public:
-    SQLiteDatabaseWrapper(const std::string& path);
-
-    SQLiteDatabaseWrapper();
-
-    virtual void Open()
-      ORTHANC_OVERRIDE;
-
-    virtual void Close()
-      ORTHANC_OVERRIDE
-    {
-      db_.Close();
-    }
-
-    virtual void SetListener(IDatabaseListener& listener)
-      ORTHANC_OVERRIDE;
-
-    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()
-      ORTHANC_OVERRIDE;
-
-    virtual void FlushToDisk()
-      ORTHANC_OVERRIDE
-    {
-      db_.FlushToDisk();
-    }
-
-    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
-    {
-      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);
-
-
-
-    /**
-     * Until Orthanc 1.4.0, the methods below were part of the
-     * "DatabaseWrapperBase" class, that is now placed in the
-     * graveyard.
-     **/
-
-    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 ListAvailableMetadata(std::list<MetadataType>& target,
-                                       int64_t id)
-      ORTHANC_OVERRIDE;
-
-    virtual void AddAttachment(int64_t id,
-                               const FileInfo& attachment)
-      ORTHANC_OVERRIDE;
-
-    virtual void DeleteAttachment(int64_t id,
-                                  FileContentType attachment)
-      ORTHANC_OVERRIDE;
-
-    virtual void ListAvailableAttachments(std::list<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;
-    
-    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;
-
-    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;
-
-    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;
-
-    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);
-    }
-
-    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);
-    }
-  };
-}
--- a/OrthancServer/Search/Compatibility/DatabaseLookup.cpp	Sat Jan 05 15:50:55 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,419 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "DatabaseLookup.h"
-
-#include "../../../Core/OrthancException.h"
-#include "../../ServerToolbox.h"
-#include "SetOfResources.h"
-
-namespace Orthanc
-{
-  namespace Compatibility
-  {
-    namespace
-    {
-      // Anonymous namespace to avoid clashes between compiler modules
-      class MainTagsConstraints : boost::noncopyable
-      {
-      private:
-        std::vector<DicomTagConstraint*>  constraints_;
-
-      public:
-        ~MainTagsConstraints()
-        {
-          for (size_t i = 0; i < constraints_.size(); i++)
-          {
-            assert(constraints_[i] != NULL);
-            delete constraints_[i];
-          }
-        }
-
-        void Reserve(size_t n)
-        {
-          constraints_.reserve(n);
-        }
-
-        size_t GetSize() const
-        {
-          return constraints_.size();
-        }
-
-        DicomTagConstraint& GetConstraint(size_t i) const
-        {
-          if (i >= constraints_.size())
-          {
-            throw OrthancException(ErrorCode_ParameterOutOfRange);
-          }
-          else
-          {
-            assert(constraints_[i] != NULL);
-            return *constraints_[i];
-          }
-        }
-        
-        void Add(const DatabaseConstraint& constraint)
-        {
-          constraints_.push_back(new DicomTagConstraint(constraint));
-        }          
-      };
-    }
-    
-    
-    static void ApplyIdentifierConstraint(SetOfResources& candidates,
-                                          ILookupResources& compatibility,
-                                          const DatabaseConstraint& constraint,
-                                          ResourceType level)
-    {
-      std::list<int64_t> matches;
-
-      switch (constraint.GetConstraintType())
-      {
-        case ConstraintType_Equal:
-          compatibility.LookupIdentifier(matches, level, constraint.GetTag(),
-                                    IdentifierConstraintType_Equal, constraint.GetSingleValue());
-          break;
-          
-        case ConstraintType_SmallerOrEqual:
-          compatibility.LookupIdentifier(matches, level, constraint.GetTag(),
-                                    IdentifierConstraintType_SmallerOrEqual, constraint.GetSingleValue());
-          break;
-          
-        case ConstraintType_GreaterOrEqual:
-          compatibility.LookupIdentifier(matches, level, constraint.GetTag(),
-                                    IdentifierConstraintType_GreaterOrEqual, constraint.GetSingleValue());
-
-          break;
-          
-        case ConstraintType_Wildcard:
-          compatibility.LookupIdentifier(matches, level, constraint.GetTag(),
-                                    IdentifierConstraintType_Wildcard, constraint.GetSingleValue());
-
-          break;
-          
-        case ConstraintType_List:
-          for (size_t i = 0; i < constraint.GetValuesCount(); i++)
-          {
-            std::list<int64_t> tmp;
-            compatibility.LookupIdentifier(tmp, level, constraint.GetTag(),
-                                      IdentifierConstraintType_Wildcard, constraint.GetValue(i));
-            matches.splice(matches.end(), tmp);
-          }
-
-          break;
-          
-        default:
-          throw OrthancException(ErrorCode_InternalError);
-      }
-
-      candidates.Intersect(matches);
-    }
-
-    
-    static void ApplyIdentifierRange(SetOfResources& candidates,
-                                     ILookupResources& compatibility,
-                                     const DatabaseConstraint& smaller,
-                                     const DatabaseConstraint& greater,
-                                     ResourceType level)
-    {
-      assert(smaller.GetConstraintType() == ConstraintType_SmallerOrEqual &&
-             greater.GetConstraintType() == ConstraintType_GreaterOrEqual &&
-             smaller.GetTag() == greater.GetTag() &&
-             ServerToolbox::IsIdentifier(smaller.GetTag(), level));
-
-      std::list<int64_t> matches;
-      compatibility.LookupIdentifierRange(matches, level, smaller.GetTag(),
-                                     greater.GetSingleValue(), smaller.GetSingleValue());
-      candidates.Intersect(matches);
-    }
-
-    
-    static void ApplyLevel(SetOfResources& candidates,
-                           IDatabaseWrapper& database,
-                           ILookupResources& compatibility,
-                           const std::vector<DatabaseConstraint>& lookup,
-                           ResourceType level)
-    {
-      typedef std::set<const DatabaseConstraint*>  SetOfConstraints;
-      typedef std::map<DicomTag, SetOfConstraints> Identifiers;
-
-      // (1) Select which constraints apply to this level, and split
-      // them between "identifier tags" constraints and "main DICOM
-      // tags" constraints
-
-      Identifiers       identifiers;
-      SetOfConstraints  mainTags;
-      
-      for (size_t i = 0; i < lookup.size(); i++)
-      {
-        if (lookup[i].GetLevel() == level)
-        {
-          if (lookup[i].IsIdentifier())
-          {
-            identifiers[lookup[i].GetTag()].insert(&lookup[i]);
-          }
-          else
-          {
-            mainTags.insert(&lookup[i]);
-          }
-        }
-      }
-
-      
-      // (2) Apply the constraints over the identifiers
-      
-      for (Identifiers::const_iterator it = identifiers.begin();
-           it != identifiers.end(); ++it)
-      {
-        // Check whether some range constraint over identifiers is
-        // present at this level
-        const DatabaseConstraint* smaller = NULL;
-        const DatabaseConstraint* greater = NULL;
-        
-        for (SetOfConstraints::const_iterator it2 = it->second.begin();
-             it2 != it->second.end(); ++it2)
-        {
-          assert(*it2 != NULL);
-        
-          if ((*it2)->GetConstraintType() == ConstraintType_SmallerOrEqual)
-          {
-            smaller = *it2;
-          }
-
-          if ((*it2)->GetConstraintType() == ConstraintType_GreaterOrEqual)
-          {
-            greater = *it2;
-          }
-        }
-
-        if (smaller != NULL &&
-            greater != NULL)
-        {
-          // There is a range constraint: Apply it, as it is more efficient
-          ApplyIdentifierRange(candidates, compatibility, *smaller, *greater, level);
-        }
-        else
-        {
-          smaller = NULL;
-          greater = NULL;
-        }
-
-        for (SetOfConstraints::const_iterator it2 = it->second.begin();
-             it2 != it->second.end(); ++it2)
-        {
-          // Check to avoid applying twice the range constraint
-          if (*it2 != smaller &&
-              *it2 != greater)
-          {
-            ApplyIdentifierConstraint(candidates, compatibility, **it2, level);
-          }
-        }
-      }
-
-
-      // (3) Apply the constraints over the main DICOM tags (no index
-      // here, so this is less efficient than filtering over the
-      // identifiers)
-      if (!mainTags.empty())
-      {
-        MainTagsConstraints c;
-        c.Reserve(mainTags.size());
-        
-        for (SetOfConstraints::const_iterator it = mainTags.begin();
-             it != mainTags.end(); ++it)
-        {
-          assert(*it != NULL);
-          c.Add(**it);
-        }
-
-        std::list<int64_t>  source;
-        candidates.Flatten(compatibility, source);
-        candidates.Clear();
-
-        std::list<int64_t>  filtered;
-        for (std::list<int64_t>::const_iterator candidate = source.begin(); 
-             candidate != source.end(); ++candidate)
-        {
-          DicomMap tags;
-          database.GetMainDicomTags(tags, *candidate);
-
-          bool match = true;
-
-          for (size_t i = 0; i < c.GetSize(); i++)
-          {
-            if (!c.GetConstraint(i).IsMatch(tags))
-            {
-              match = false;
-              break;
-            }
-          }
-        
-          if (match)
-          {
-            filtered.push_back(*candidate);
-          }
-        }
-
-        candidates.Intersect(filtered);
-      }
-    }
-
-
-    static std::string GetOneInstance(IDatabaseWrapper& compatibility,
-                                      int64_t resource,
-                                      ResourceType level)
-    {
-      for (int i = level; i < ResourceType_Instance; i++)
-      {
-        assert(compatibility.GetResourceType(resource) == static_cast<ResourceType>(i));
-
-        std::list<int64_t> children;
-        compatibility.GetChildrenInternalId(children, resource);
-          
-        if (children.empty())
-        {
-          throw OrthancException(ErrorCode_Database);
-        }
-          
-        resource = children.front();
-      }
-
-      return compatibility.GetPublicId(resource);
-    }
-                           
-
-    void DatabaseLookup::ApplyLookupResources(std::list<std::string>& resourcesId,
-                                              std::list<std::string>* instancesId,
-                                              const std::vector<DatabaseConstraint>& lookup,
-                                              ResourceType queryLevel,
-                                              size_t limit)
-    {
-      // This is a re-implementation of
-      // "../../../Resources/Graveyard/DatabaseOptimizations/LookupResource.cpp"
-
-      assert(ResourceType_Patient < ResourceType_Study &&
-             ResourceType_Study < ResourceType_Series &&
-             ResourceType_Series < ResourceType_Instance);
-    
-      ResourceType upperLevel = queryLevel;
-      ResourceType lowerLevel = queryLevel;
-
-      for (size_t i = 0; i < lookup.size(); i++)
-      {
-        ResourceType level = lookup[i].GetLevel();
-
-        if (level < upperLevel)
-        {
-          upperLevel = level;
-        }
-
-        if (level > lowerLevel)
-        {
-          lowerLevel = level;
-        }
-      }
-
-      assert(upperLevel <= queryLevel &&
-             queryLevel <= lowerLevel);
-
-      SetOfResources candidates(database_, upperLevel);
-
-      for (int level = upperLevel; level <= lowerLevel; level++)
-      {
-        ApplyLevel(candidates, database_, compatibility_, lookup, static_cast<ResourceType>(level));
-
-        if (level != lowerLevel)
-        {
-          candidates.GoDown();
-        }
-      }
-
-      std::list<int64_t> resources;
-      candidates.Flatten(compatibility_, resources);
-
-      // Climb up, up to queryLevel
-
-      for (int level = lowerLevel; level > queryLevel; level--)
-      {
-        std::list<int64_t> parents;
-        for (std::list<int64_t>::const_iterator
-               it = resources.begin(); it != resources.end(); ++it)
-        {
-          int64_t parent;
-          if (database_.LookupParent(parent, *it))
-          {
-            parents.push_back(parent);
-          }
-        }
-
-        resources.swap(parents);
-      }
-
-      // Apply the limit, if given
-
-      if (limit != 0 &&
-          resources.size() > limit)
-      {
-        resources.resize(limit);
-      }
-
-      // Get the public ID of all the selected resources
-
-      size_t pos = 0;
-
-      for (std::list<int64_t>::const_iterator
-             it = resources.begin(); it != resources.end(); ++it, pos++)
-      {
-        assert(database_.GetResourceType(*it) == queryLevel);
-
-        const std::string resource = database_.GetPublicId(*it);
-        resourcesId.push_back(resource);
-
-        if (instancesId != NULL)
-        {
-          if (queryLevel == ResourceType_Instance)
-          {
-            // The resource is itself the instance
-            instancesId->push_back(resource);
-          }
-          else
-          {
-            // Collect one child instance for each of the selected resources
-            instancesId->push_back(GetOneInstance(database_, *it, queryLevel));
-          }
-        }
-      }
-    }
-  }
-}
--- a/OrthancServer/Search/Compatibility/DatabaseLookup.h	Sat Jan 05 15:50:55 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "../../IDatabaseWrapper.h"
-#include "ILookupResources.h"
-
-namespace Orthanc
-{
-  namespace Compatibility
-  {
-    class DatabaseLookup : public boost::noncopyable
-    {
-    private:
-      IDatabaseWrapper&              database_;
-      ILookupResources&  compatibility_;
-
-    public:
-      DatabaseLookup(IDatabaseWrapper&  database,
-                     ILookupResources& compatibility) :
-        database_(database),
-        compatibility_(compatibility)
-      {
-      }
-
-      void ApplyLookupResources(std::list<std::string>& resourcesId,
-                                std::list<std::string>* instancesId,
-                                const std::vector<DatabaseConstraint>& lookup,
-                                ResourceType queryLevel,
-                                size_t limit);
-    };
-  }
-}
--- a/OrthancServer/Search/Compatibility/ICreateInstance.cpp	Sat Jan 05 15:50:55 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,155 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "ICreateInstance.h"
-
-#include "../../../Core/OrthancException.h"
-
-namespace Orthanc
-{
-  namespace Compatibility
-  {
-    bool ICreateInstance::Apply(ICreateInstance& database,
-                                IDatabaseWrapper::CreateInstanceResult& result,
-                                int64_t& instanceId,
-                                const std::string& hashPatient,
-                                const std::string& hashStudy,
-                                const std::string& hashSeries,
-                                const std::string& hashInstance)
-    {
-      {
-        ResourceType type;
-        int64_t tmp;
-        
-        if (database.LookupResource(tmp, type, hashInstance))
-        {
-          // The instance already exists
-          assert(type == ResourceType_Instance);
-          instanceId = tmp;
-          return false;
-        }
-      }
-
-      instanceId = database.CreateResource(hashInstance, ResourceType_Instance);
-
-      result.isNewPatient_ = false;
-      result.isNewStudy_ = false;
-      result.isNewSeries_ = false;
-      result.patientId_ = -1;
-      result.studyId_ = -1;
-      result.seriesId_ = -1;
-      
-      // Detect up to which level the patient/study/series/instance
-      // hierarchy must be created
-
-      {
-        ResourceType dummy;
-
-        if (database.LookupResource(result.seriesId_, dummy, hashSeries))
-        {
-          assert(dummy == ResourceType_Series);
-          // The patient, the study and the series already exist
-
-          bool ok = (database.LookupResource(result.patientId_, dummy, hashPatient) &&
-                     database.LookupResource(result.studyId_, dummy, hashStudy));
-          assert(ok);
-        }
-        else if (database.LookupResource(result.studyId_, dummy, hashStudy))
-        {
-          assert(dummy == ResourceType_Study);
-
-          // New series: The patient and the study already exist
-          result.isNewSeries_ = true;
-
-          bool ok = database.LookupResource(result.patientId_, dummy, hashPatient);
-          assert(ok);
-        }
-        else if (database.LookupResource(result.patientId_, dummy, hashPatient))
-        {
-          assert(dummy == ResourceType_Patient);
-
-          // New study and series: The patient already exist
-          result.isNewStudy_ = true;
-          result.isNewSeries_ = true;
-        }
-        else
-        {
-          // New patient, study and series: Nothing exists
-          result.isNewPatient_ = true;
-          result.isNewStudy_ = true;
-          result.isNewSeries_ = true;
-        }
-      }
-
-      // Create the series if needed
-      if (result.isNewSeries_)
-      {
-        result.seriesId_ = database.CreateResource(hashSeries, ResourceType_Series);
-      }
-
-      // Create the study if needed
-      if (result.isNewStudy_)
-      {
-        result.studyId_ = database.CreateResource(hashStudy, ResourceType_Study);
-      }
-
-      // Create the patient if needed
-      if (result.isNewPatient_)
-      {
-        result.patientId_ = database.CreateResource(hashPatient, ResourceType_Patient);
-      }
-
-      // Create the parent-to-child links
-      database.AttachChild(result.seriesId_, instanceId);
-
-      if (result.isNewSeries_)
-      {
-        database.AttachChild(result.studyId_, result.seriesId_);
-      }
-
-      if (result.isNewStudy_)
-      {
-        database.AttachChild(result.patientId_, result.studyId_);
-      }
-
-      // Sanity checks
-      assert(result.patientId_ != -1);
-      assert(result.studyId_ != -1);
-      assert(result.seriesId_ != -1);
-      assert(instanceId != -1);
-
-      return true;
-    }
-  }
-}
--- a/OrthancServer/Search/Compatibility/ICreateInstance.h	Sat Jan 05 15:50:55 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "../../IDatabaseWrapper.h"
-
-namespace Orthanc
-{
-  namespace Compatibility
-  {
-    class ICreateInstance : public boost::noncopyable
-    {
-    public:
-      virtual bool LookupResource(int64_t& id,
-                                  ResourceType& type,
-                                  const std::string& publicId) = 0;
-
-      virtual int64_t CreateResource(const std::string& publicId,
-                                     ResourceType type) = 0;
-
-      virtual void AttachChild(int64_t parent,
-                               int64_t child) = 0;
-      
-      static bool Apply(ICreateInstance& database,
-                        IDatabaseWrapper::CreateInstanceResult& result,
-                        int64_t& instanceId,
-                        const std::string& patient,
-                        const std::string& study,
-                        const std::string& series,
-                        const std::string& instance);
-    };
-  }
-}
--- a/OrthancServer/Search/Compatibility/IGetChildrenMetadata.cpp	Sat Jan 05 15:50:55 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "IGetChildrenMetadata.h"
-
-namespace Orthanc
-{
-  namespace Compatibility
-  {
-    void IGetChildrenMetadata::Apply(IGetChildrenMetadata& database,
-                                     std::list<std::string>& target,
-                                     int64_t resourceId,
-                                     MetadataType metadata)
-    {
-      // This function comes from an optimization of
-      // "ServerIndex::GetSeriesStatus()" in Orthanc <= 1.5.1
-      // Loop over the instances of this series
-
-      target.clear();
-      
-      std::list<int64_t> children;
-      database.GetChildrenInternalId(children, resourceId);
-
-      std::set<int64_t> instances;
-      for (std::list<int64_t>::const_iterator 
-             it = children.begin(); it != children.end(); ++it)
-      {
-        std::string value;
-        if (database.LookupMetadata(value, *it, metadata))
-        {
-          target.push_back(value);
-        }
-      }
-    }
-  }
-}
--- a/OrthancServer/Search/Compatibility/IGetChildrenMetadata.h	Sat Jan 05 15:50:55 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "../../ServerEnumerations.h"
-
-#include <boost/noncopyable.hpp>
-#include <list>
-
-namespace Orthanc
-{
-  namespace Compatibility
-  {
-    class IGetChildrenMetadata : public boost::noncopyable
-    {
-    public:
-      virtual void GetChildrenInternalId(std::list<int64_t>& target,
-                                         int64_t id) = 0;
-
-      virtual bool LookupMetadata(std::string& target,
-                                  int64_t id,
-                                  MetadataType type) = 0;
-
-      static void Apply(IGetChildrenMetadata& database,
-                        std::list<std::string>& target,
-                        int64_t resourceId,
-                        MetadataType metadata);
-    };
-  }
-}
--- a/OrthancServer/Search/Compatibility/ILookupResources.cpp	Sat Jan 05 15:50:55 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "ILookupResources.h"
-
-#include "DatabaseLookup.h"
-
-namespace Orthanc
-{
-  namespace Compatibility
-  {
-    void ILookupResources::Apply(
-      IDatabaseWrapper& database,
-      ILookupResources& compatibility,
-      std::list<std::string>& resourcesId,
-      std::list<std::string>* instancesId,
-      const std::vector<DatabaseConstraint>& lookup,
-      ResourceType queryLevel,
-      size_t limit)
-    {
-      Compatibility::DatabaseLookup compat(database, compatibility);
-      compat.ApplyLookupResources(resourcesId, instancesId, lookup, queryLevel, limit);
-    }
-  }
-}
--- a/OrthancServer/Search/Compatibility/ILookupResources.h	Sat Jan 05 15:50:55 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "../../IDatabaseWrapper.h"
-
-namespace Orthanc
-{
-  namespace Compatibility
-  {
-    /**
-     * This is a compatibility class that contains database primitives
-     * that were used in Orthanc <= 1.5.1, and that have been removed
-     * during the optimization of the database engine.
-     **/
-    class ILookupResources : public boost::noncopyable
-    {     
-    public:
-      virtual ~ILookupResources()
-      {
-      }
-      
-      virtual void GetAllInternalIds(std::list<int64_t>& target,
-                                     ResourceType resourceType) = 0;
-      
-      virtual void LookupIdentifier(std::list<int64_t>& result,
-                                    ResourceType level,
-                                    const DicomTag& tag,
-                                    IdentifierConstraintType type,
-                                    const std::string& value) = 0;
- 
-      virtual void LookupIdentifierRange(std::list<int64_t>& result,
-                                         ResourceType level,
-                                         const DicomTag& tag,
-                                         const std::string& start,
-                                         const std::string& end) = 0;
-
-      static void Apply(IDatabaseWrapper& database,
-                        ILookupResources& compatibility,
-                        std::list<std::string>& resourcesId,
-                        std::list<std::string>* instancesId,
-                        const std::vector<DatabaseConstraint>& lookup,
-                        ResourceType queryLevel,
-                        size_t limit);
-    };
-  }
-}
--- a/OrthancServer/Search/Compatibility/ISetResourcesContent.h	Sat Jan 05 15:50:55 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "../../ServerToolbox.h"
-
-namespace Orthanc
-{
-  namespace Compatibility
-  {
-    class ISetResourcesContent : public boost::noncopyable
-    {
-    public:
-      virtual ~ISetResourcesContent()
-      {
-      }
-      
-      virtual void SetMainDicomTag(int64_t id,
-                                   const DicomTag& tag,
-                                   const std::string& value) = 0;
-
-      virtual void SetIdentifierTag(int64_t id,
-                                    const DicomTag& tag,
-                                    const std::string& value) = 0;
-
-      virtual void SetMetadata(int64_t id,
-                               MetadataType type,
-                               const std::string& value) = 0;
-
-      static void Apply(ISetResourcesContent& that,
-                        const ResourcesContent& content)
-      {
-        content.Store(that);
-      }
-    };
-  }
-}
--- a/OrthancServer/Search/Compatibility/SetOfResources.cpp	Sat Jan 05 15:50:55 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,161 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "SetOfResources.h"
-
-#include "../../../Core/OrthancException.h"
-
-
-namespace Orthanc
-{
-  namespace Compatibility
-  {
-    void SetOfResources::Intersect(const std::list<int64_t>& resources)
-    {
-      if (resources_.get() == NULL)
-      {
-        resources_.reset(new Resources);
-
-        for (std::list<int64_t>::const_iterator
-               it = resources.begin(); it != resources.end(); ++it)
-        {
-          resources_->insert(*it);
-        }
-      }
-      else
-      {
-        std::auto_ptr<Resources> filtered(new Resources);
-
-        for (std::list<int64_t>::const_iterator
-               it = resources.begin(); it != resources.end(); ++it)
-        {
-          if (resources_->find(*it) != resources_->end())
-          {
-            filtered->insert(*it);
-          }
-        }
-
-        resources_ = filtered;
-      }
-    }
-
-
-    void SetOfResources::GoDown()
-    {
-      if (level_ == ResourceType_Instance)
-      {
-        throw OrthancException(ErrorCode_BadSequenceOfCalls);
-      }
-
-      if (resources_.get() != NULL)
-      {
-        std::auto_ptr<Resources> children(new Resources);
-
-        for (Resources::const_iterator it = resources_->begin(); 
-             it != resources_->end(); ++it)
-        {
-          std::list<int64_t> tmp;
-          database_.GetChildrenInternalId(tmp, *it);
-
-          for (std::list<int64_t>::const_iterator
-                 child = tmp.begin(); child != tmp.end(); ++child)
-          {
-            children->insert(*child);
-          }
-        }
-
-        resources_ = children;
-      }
-
-      switch (level_)
-      {
-        case ResourceType_Patient:
-          level_ = ResourceType_Study;
-          break;
-
-        case ResourceType_Study:
-          level_ = ResourceType_Series;
-          break;
-
-        case ResourceType_Series:
-          level_ = ResourceType_Instance;
-          break;
-
-        default:
-          throw OrthancException(ErrorCode_InternalError);
-      }
-    }
-
-
-    void SetOfResources::Flatten(std::list<std::string>& result)
-    {
-      result.clear();
-      
-      if (resources_.get() == NULL)
-      {
-        // All the resources of this level are part of the filter
-        database_.GetAllPublicIds(result, level_);
-      }
-      else
-      {
-        for (Resources::const_iterator it = resources_->begin(); 
-             it != resources_->end(); ++it)
-        {
-          result.push_back(database_.GetPublicId(*it));
-        }
-      }
-    }
-
-
-    void SetOfResources::Flatten(ILookupResources& compatibility,
-                                 std::list<int64_t>& result)
-    {
-      result.clear();
-      
-      if (resources_.get() == NULL)
-      {
-        // All the resources of this level are part of the filter
-        compatibility.GetAllInternalIds(result, level_);
-      }
-      else
-      {
-        for (Resources::const_iterator it = resources_->begin(); 
-             it != resources_->end(); ++it)
-        {
-          result.push_back(*it);
-        }
-      }
-    }
-  }
-}
--- a/OrthancServer/Search/Compatibility/SetOfResources.h	Sat Jan 05 15:50:55 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,83 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 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 "../../IDatabaseWrapper.h"
-#include "ILookupResources.h"
-
-#include <set>
-#include <memory>
-
-namespace Orthanc
-{
-  namespace Compatibility
-  {
-    class SetOfResources : public boost::noncopyable
-    {
-    private:
-      typedef std::set<int64_t>  Resources;
-
-      IDatabaseWrapper&         database_;
-      ResourceType              level_;
-      std::auto_ptr<Resources>  resources_;
-    
-    public:
-      SetOfResources(IDatabaseWrapper& database,
-                     ResourceType level) : 
-        database_(database),
-        level_(level)
-      {
-      }
-
-      ResourceType GetLevel() const
-      {
-        return level_;
-      }
-
-      void Intersect(const std::list<int64_t>& resources);
-
-      void GoDown();
-
-      void Flatten(ILookupResources& compatibility,
-                   std::list<int64_t>& result);
-
-      void Flatten(std::list<std::string>& result);
-
-      void Clear()
-      {
-        resources_.reset(NULL);
-      }
-    };
-  }
-}
--- a/OrthancServer/ServerIndex.cpp	Sat Jan 05 15:50:55 2019 +0100
+++ b/OrthancServer/ServerIndex.cpp	Sat Jan 05 16:09:21 2019 +0100
@@ -944,7 +944,6 @@
 
   
       // Check whether the series of this new instance is now completed
-      // TODO - SPEED THIS UP
       SeriesStatus seriesStatus = GetSeriesStatus(status.seriesId_);
       if (seriesStatus == SeriesStatus_Complete)
       {
--- a/OrthancServer/ServerIndex.h	Sat Jan 05 15:50:55 2019 +0100
+++ b/OrthancServer/ServerIndex.h	Sat Jan 05 16:09:21 2019 +0100
@@ -40,7 +40,7 @@
 #include "../Core/DicomFormat/DicomMap.h"
 #include "ServerEnumerations.h"
 
-#include "IDatabaseWrapper.h"
+#include "Database/IDatabaseWrapper.h"
 
 namespace Orthanc
 {
--- a/OrthancServer/ServerToolbox.cpp	Sat Jan 05 15:50:55 2019 +0100
+++ b/OrthancServer/ServerToolbox.cpp	Sat Jan 05 16:09:21 2019 +0100
@@ -40,7 +40,7 @@
 #include "../Core/Logging.h"
 #include "../Core/OrthancException.h"
 #include "../Plugins/Engine/OrthancPlugins.h"
-#include "IDatabaseWrapper.h"
+#include "Database/IDatabaseWrapper.h"
 #include "ServerContext.h"
 
 #include <cassert>
--- a/Plugins/Engine/OrthancPluginDatabase.h	Sat Jan 05 15:50:55 2019 +0100
+++ b/Plugins/Engine/OrthancPluginDatabase.h	Sat Jan 05 16:09:21 2019 +0100
@@ -36,10 +36,10 @@
 #if ORTHANC_ENABLE_PLUGINS == 1
 
 #include "../../Core/SharedLibrary.h"
-#include "../../OrthancServer/Search/Compatibility/ICreateInstance.h"
-#include "../../OrthancServer/Search/Compatibility/IGetChildrenMetadata.h"
-#include "../../OrthancServer/Search/Compatibility/ILookupResources.h"
-#include "../../OrthancServer/Search/Compatibility/ISetResourcesContent.h"
+#include "../../OrthancServer/Database/Compatibility/ICreateInstance.h"
+#include "../../OrthancServer/Database/Compatibility/IGetChildrenMetadata.h"
+#include "../../OrthancServer/Database/Compatibility/ILookupResources.h"
+#include "../../OrthancServer/Database/Compatibility/ISetResourcesContent.h"
 #include "../Include/orthanc/OrthancCDatabasePlugin.h"
 #include "PluginsErrorDictionary.h"
 
--- a/UnitTestsSources/MultiThreadingTests.cpp	Sat Jan 05 15:50:55 2019 +0100
+++ b/UnitTestsSources/MultiThreadingTests.cpp	Sat Jan 05 16:09:21 2019 +0100
@@ -42,7 +42,7 @@
 #include "../Core/SerializationToolbox.h"
 #include "../Core/SystemToolbox.h"
 #include "../Core/Toolbox.h"
-#include "../OrthancServer/SQLiteDatabaseWrapper.h"
+#include "../OrthancServer/Database/SQLiteDatabaseWrapper.h"
 #include "../OrthancServer/ServerContext.h"
 #include "../OrthancServer/ServerJobs/LuaJobManager.h"
 #include "../OrthancServer/ServerJobs/OrthancJobUnserializer.h"
--- a/UnitTestsSources/ServerIndexTests.cpp	Sat Jan 05 15:50:55 2019 +0100
+++ b/UnitTestsSources/ServerIndexTests.cpp	Sat Jan 05 16:09:21 2019 +0100
@@ -37,7 +37,7 @@
 #include "../Core/FileStorage/FilesystemStorage.h"
 #include "../Core/FileStorage/MemoryStorageArea.h"
 #include "../Core/Logging.h"
-#include "../OrthancServer/SQLiteDatabaseWrapper.h"
+#include "../OrthancServer/Database/SQLiteDatabaseWrapper.h"
 #include "../OrthancServer/ServerContext.h"
 #include "../OrthancServer/ServerToolbox.h"