changeset 387:f35b17a38301

integration db-protobuf->mainline
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 03 Apr 2023 17:12:08 +0200
parents c1fe28de1bf6 (current diff) 0fed785e974e (diff)
children 3d6886f3e5b3
files
diffstat 22 files changed, 1860 insertions(+), 294 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Plugins/DatabaseBackendAdapterV2.cpp	Tue Mar 14 09:08:45 2023 +0100
+++ b/Framework/Plugins/DatabaseBackendAdapterV2.cpp	Mon Apr 03 17:12:08 2023 +0200
@@ -946,7 +946,9 @@
     try
     {
       DatabaseBackendAdapterV2::Adapter::DatabaseAccessor accessor(*adapter);      
-      adapter->GetBackend().LogExportedResource(accessor.GetManager(), *exported);
+      adapter->GetBackend().LogExportedResource(accessor.GetManager(), exported->resourceType, exported->publicId,
+                                                exported->modality, exported->date, exported->patientId,
+                                                exported->studyInstanceUid, exported->seriesInstanceUid, exported->sopInstanceUid);
       return OrthancPluginErrorCode_Success;
     }
     ORTHANC_PLUGINS_DATABASE_CATCH;
@@ -1632,17 +1634,21 @@
 
   void DatabaseBackendAdapterV2::Register(IDatabaseBackend* backend)
   {
-    if (backend == NULL)
     {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
+      std::unique_ptr<IDatabaseBackend> protection(backend);
+    
+      if (backend == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
 
-    if (adapter_.get() != NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      if (adapter_.get() != NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+
+      adapter_.reset(new Adapter(protection.release()));
     }
-
-    adapter_.reset(new Adapter(backend));
     
     OrthancPluginDatabaseBackend  params;
     memset(&params, 0, sizeof(params));
--- a/Framework/Plugins/DatabaseBackendAdapterV3.cpp	Tue Mar 14 09:08:45 2023 +0100
+++ b/Framework/Plugins/DatabaseBackendAdapterV3.cpp	Mon Apr 03 17:12:08 2023 +0200
@@ -25,8 +25,9 @@
 #if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)         // Macro introduced in Orthanc 1.3.1
 #  if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 9, 2)
 
+#include "IndexConnectionsPool.h"
+
 #include <Logging.h>
-#include <MultiThreading/SharedMessageQueue.h>
 #include <OrthancException.h>
 
 #include <stdexcept>
@@ -82,172 +83,6 @@
   }
     
     
-  class DatabaseBackendAdapterV3::Adapter : public boost::noncopyable
-  {
-  private:
-    class ManagerReference : public Orthanc::IDynamicObject
-    {
-    private:
-      DatabaseManager*  manager_;
-
-    public:
-      ManagerReference(DatabaseManager& manager) :
-        manager_(&manager)
-      {
-      }
-
-      DatabaseManager& GetManager()
-      {
-        assert(manager_ != NULL);
-        return *manager_;
-      }
-    };
-    
-    std::unique_ptr<IndexBackend>  backend_;
-    OrthancPluginContext*          context_;
-    boost::shared_mutex            connectionsMutex_;
-    size_t                         countConnections_;
-    std::list<DatabaseManager*>    connections_;
-    Orthanc::SharedMessageQueue    availableConnections_;
-
-  public:
-    Adapter(IndexBackend* backend,
-            size_t countConnections) :
-      backend_(backend),
-      countConnections_(countConnections)
-    {
-      if (countConnections == 0)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
-                                        "There must be a non-zero number of connections to the database");
-      }
-      else if (backend == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-      }
-      else
-      {
-        context_ = backend_->GetContext();
-      }
-    }
-
-    ~Adapter()
-    {
-      for (std::list<DatabaseManager*>::iterator
-             it = connections_.begin(); it != connections_.end(); ++it)
-      {
-        assert(*it != NULL);
-        delete *it;
-      }
-    }
-
-    OrthancPluginContext* GetContext() const
-    {
-      return context_;
-    }
-
-    void OpenConnections()
-    {
-      boost::unique_lock<boost::shared_mutex>  lock(connectionsMutex_);
-
-      if (connections_.size() == 0)
-      {
-        assert(backend_.get() != NULL);
-
-        {
-          std::unique_ptr<DatabaseManager> manager(new DatabaseManager(backend_->CreateDatabaseFactory()));
-          manager->GetDatabase();  // Make sure to open the database connection
-          
-          backend_->ConfigureDatabase(*manager);
-          connections_.push_back(manager.release());
-        }
-
-        for (size_t i = 1; i < countConnections_; i++)
-        {
-          connections_.push_back(new DatabaseManager(backend_->CreateDatabaseFactory()));
-          connections_.back()->GetDatabase();  // Make sure to open the database connection
-        }
-
-        for (std::list<DatabaseManager*>::iterator
-               it = connections_.begin(); it != connections_.end(); ++it)
-        {
-          assert(*it != NULL);
-          availableConnections_.Enqueue(new ManagerReference(**it));
-        }        
-      }
-      else
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-    }
-
-    void CloseConnections()
-    {
-      boost::unique_lock<boost::shared_mutex>  lock(connectionsMutex_);
-
-      if (connections_.size() != countConnections_)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      else if (availableConnections_.GetSize() != countConnections_)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Some connections are still in use, bug in the Orthanc core");
-      }
-      else
-      {
-        for (std::list<DatabaseManager*>::iterator
-               it = connections_.begin(); it != connections_.end(); ++it)
-        {
-          assert(*it != NULL);
-          (*it)->Close();
-        }
-      }
-    }
-
-    class DatabaseAccessor : public boost::noncopyable
-    {
-    private:
-      boost::shared_lock<boost::shared_mutex>  lock_;
-      Adapter&                                 adapter_;
-      DatabaseManager*                         manager_;
-      
-    public:
-      DatabaseAccessor(Adapter& adapter) :
-        lock_(adapter.connectionsMutex_),
-        adapter_(adapter),
-        manager_(NULL)
-      {
-        for (;;)
-        {
-          std::unique_ptr<Orthanc::IDynamicObject> manager(adapter.availableConnections_.Dequeue(100));
-          if (manager.get() != NULL)
-          {
-            manager_ = &dynamic_cast<ManagerReference&>(*manager).GetManager();
-            return;
-          }
-        }
-      }
-
-      ~DatabaseAccessor()
-      {
-        assert(manager_ != NULL);
-        adapter_.availableConnections_.Enqueue(new ManagerReference(*manager_));
-      }
-
-      IndexBackend& GetBackend() const
-      {
-        return *adapter_.backend_;
-      }
-
-      DatabaseManager& GetManager() const
-      {
-        assert(manager_ != NULL);
-        return *manager_;
-      }
-    };
-  };
-
-
   class DatabaseBackendAdapterV3::Output : public IDatabaseBackendOutput
   {
   private:
@@ -802,14 +637,14 @@
   class DatabaseBackendAdapterV3::Transaction : public boost::noncopyable
   {
   private:
-    Adapter&   adapter_;
-    std::unique_ptr<Adapter::DatabaseAccessor>  accessor_;
-    std::unique_ptr<Output>    output_;
+    IndexConnectionsPool&                            pool_;
+    std::unique_ptr<IndexConnectionsPool::Accessor>  accessor_;
+    std::unique_ptr<Output>                          output_;
     
   public:
-    Transaction(Adapter& adapter) :
-      adapter_(adapter),
-      accessor_(new Adapter::DatabaseAccessor(adapter)),
+    Transaction(IndexConnectionsPool& pool) :
+      pool_(pool),
+      accessor_(new IndexConnectionsPool::Accessor(pool)),
       output_(new Output)
     {
     }
@@ -961,35 +796,35 @@
     
   static OrthancPluginErrorCode Open(void* database)
   {
-    DatabaseBackendAdapterV3::Adapter* adapter = reinterpret_cast<DatabaseBackendAdapterV3::Adapter*>(database);
+    IndexConnectionsPool* pool = reinterpret_cast<IndexConnectionsPool*>(database);
 
     try
     {
-      adapter->OpenConnections();
+      pool->OpenConnections();
       return OrthancPluginErrorCode_Success;
     }
-    ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext());
+    ORTHANC_PLUGINS_DATABASE_CATCH(pool->GetContext());
   }
 
   
   static OrthancPluginErrorCode Close(void* database)
   {
-    DatabaseBackendAdapterV3::Adapter* adapter = reinterpret_cast<DatabaseBackendAdapterV3::Adapter*>(database);
+    IndexConnectionsPool* pool = reinterpret_cast<IndexConnectionsPool*>(database);
 
     try
     {
-      adapter->CloseConnections();
+      pool->CloseConnections();
       return OrthancPluginErrorCode_Success;
     }
-    ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext());
+    ORTHANC_PLUGINS_DATABASE_CATCH(pool->GetContext());
   }
 
   
   static OrthancPluginErrorCode DestructDatabase(void* database)
   {
-    DatabaseBackendAdapterV3::Adapter* adapter = reinterpret_cast<DatabaseBackendAdapterV3::Adapter*>(database);
+    IndexConnectionsPool* pool = reinterpret_cast<IndexConnectionsPool*>(database);
 
-    if (adapter == NULL)
+    if (pool == NULL)
     {
       return OrthancPluginErrorCode_InternalError;
     }
@@ -1001,10 +836,10 @@
       }
       else
       {
-        OrthancPluginLogError(adapter->GetContext(), "More than one index backend was registered, internal error");
+        OrthancPluginLogError(pool->GetContext(), "More than one index backend was registered, internal error");
       }
       
-      delete adapter;
+      delete pool;
 
       return OrthancPluginErrorCode_Success;
     }
@@ -1014,15 +849,15 @@
   static OrthancPluginErrorCode GetDatabaseVersion(void* database,
                                                    uint32_t* version)
   {
-    DatabaseBackendAdapterV3::Adapter* adapter = reinterpret_cast<DatabaseBackendAdapterV3::Adapter*>(database);
+    IndexConnectionsPool* pool = reinterpret_cast<IndexConnectionsPool*>(database);
       
     try
     {
-      DatabaseBackendAdapterV3::Adapter::DatabaseAccessor accessor(*adapter);
+      IndexConnectionsPool::Accessor accessor(*pool);
       *version = accessor.GetBackend().GetDatabaseVersion(accessor.GetManager());
       return OrthancPluginErrorCode_Success;
     }
-    ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext());
+    ORTHANC_PLUGINS_DATABASE_CATCH(pool->GetContext());
   }
 
 
@@ -1030,30 +865,30 @@
                                                 OrthancPluginStorageArea* storageArea,
                                                 uint32_t  targetVersion)
   {
-    DatabaseBackendAdapterV3::Adapter* adapter = reinterpret_cast<DatabaseBackendAdapterV3::Adapter*>(database);
+    IndexConnectionsPool* pool = reinterpret_cast<IndexConnectionsPool*>(database);
       
     try
     {
-      DatabaseBackendAdapterV3::Adapter::DatabaseAccessor accessor(*adapter);
+      IndexConnectionsPool::Accessor accessor(*pool);
       accessor.GetBackend().UpgradeDatabase(accessor.GetManager(), targetVersion, storageArea);
       return OrthancPluginErrorCode_Success;
     }
-    ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext());
+    ORTHANC_PLUGINS_DATABASE_CATCH(pool->GetContext());
   }
 
 
   static OrthancPluginErrorCode HasRevisionsSupport(void* database,
                                                     uint8_t* target)
   {
-    DatabaseBackendAdapterV3::Adapter* adapter = reinterpret_cast<DatabaseBackendAdapterV3::Adapter*>(database);
+    IndexConnectionsPool* pool = reinterpret_cast<IndexConnectionsPool*>(database);
       
     try
     {
-      DatabaseBackendAdapterV3::Adapter::DatabaseAccessor accessor(*adapter);
+      IndexConnectionsPool::Accessor accessor(*pool);
       *target = (accessor.GetBackend().HasRevisionsSupport() ? 1 : 0);
       return OrthancPluginErrorCode_Success;
     }
-    ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext());
+    ORTHANC_PLUGINS_DATABASE_CATCH(pool->GetContext());
   }
 
 
@@ -1061,11 +896,11 @@
                                                  OrthancPluginDatabaseTransaction** target /* out */,
                                                  OrthancPluginDatabaseTransactionType type)
   {
-    DatabaseBackendAdapterV3::Adapter* adapter = reinterpret_cast<DatabaseBackendAdapterV3::Adapter*>(database);
+    IndexConnectionsPool* pool = reinterpret_cast<IndexConnectionsPool*>(database);
       
     try
     {
-      std::unique_ptr<DatabaseBackendAdapterV3::Transaction> transaction(new DatabaseBackendAdapterV3::Transaction(*adapter));
+      std::unique_ptr<DatabaseBackendAdapterV3::Transaction> transaction(new DatabaseBackendAdapterV3::Transaction(*pool));
       
       switch (type)
       {
@@ -1085,7 +920,7 @@
       
       return OrthancPluginErrorCode_Success;
     }
-    ORTHANC_PLUGINS_DATABASE_CATCH(adapter->GetContext());
+    ORTHANC_PLUGINS_DATABASE_CATCH(pool->GetContext());
   }
 
   
@@ -1666,19 +1501,9 @@
 
     try
     {
-      OrthancPluginExportedResource exported;
-      exported.seq = 0;
-      exported.resourceType = resourceType;
-      exported.publicId = publicId;
-      exported.modality = modality;
-      exported.date = date;
-      exported.patientId = patientId;
-      exported.studyInstanceUid = studyInstanceUid;
-      exported.seriesInstanceUid = seriesInstanceUid;
-      exported.sopInstanceUid = sopInstanceUid;
-        
       t->GetOutput().Clear();
-      t->GetBackend().LogExportedResource(t->GetManager(), exported);
+      t->GetBackend().LogExportedResource(t->GetManager(), resourceType, publicId, modality, date,
+                                          patientId, studyInstanceUid, seriesInstanceUid, sopInstanceUid);
       return OrthancPluginErrorCode_Success;
     }
     ORTHANC_PLUGINS_DATABASE_CATCH(t->GetBackend().GetContext());
@@ -1988,6 +1813,8 @@
                                           size_t countConnections,
                                           unsigned int maxDatabaseRetries)
   {
+    std::unique_ptr<IndexBackend> protection(backend);
+    
     if (isBackendInUse_)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
@@ -2071,12 +1898,13 @@
     params.setProtectedPatient = SetProtectedPatient;
     params.setResourcesContent = SetResourcesContent;
 
-    OrthancPluginContext* context = backend->GetContext();
+    OrthancPluginContext* context = protection->GetContext();
  
     if (OrthancPluginRegisterDatabaseBackendV3(
           context, &params, sizeof(params), maxDatabaseRetries,
-          new Adapter(backend, countConnections)) != OrthancPluginErrorCode_Success)
+          new IndexConnectionsPool(protection.release(), countConnections)) != OrthancPluginErrorCode_Success)
     {
+      delete backend;
       throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Unable to register the database backend");
     }
 
--- a/Framework/Plugins/DatabaseBackendAdapterV3.h	Tue Mar 14 09:08:45 2023 +0100
+++ b/Framework/Plugins/DatabaseBackendAdapterV3.h	Mon Apr 03 17:12:08 2023 +0200
@@ -49,7 +49,6 @@
     }
 
   public:
-    class Adapter;
     class Transaction;
 
     class Factory : public IDatabaseBackendOutput::IFactory
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Plugins/DatabaseBackendAdapterV4.cpp	Mon Apr 03 17:12:08 2023 +0200
@@ -0,0 +1,1301 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2022 Osimis S.A., Belgium
+ * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DatabaseBackendAdapterV4.h"
+
+#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)         // Macro introduced in Orthanc 1.3.1
+#  if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 0)
+
+#include "IndexConnectionsPool.h"
+
+#include <OrthancDatabasePlugin.pb.h>  // Include protobuf messages
+
+#include <Logging.h>
+#include <OrthancException.h>
+
+#include <stdexcept>
+#include <list>
+#include <string>
+#include <cassert>
+
+
+#define ORTHANC_PLUGINS_DATABASE_CATCH(context) \
+
+
+namespace OrthancDatabases
+{
+  static bool isBackendInUse_ = false;  // Only for sanity checks
+
+
+  static Orthanc::DatabasePluginMessages::ResourceType Convert(OrthancPluginResourceType resourceType)
+  {
+    switch (resourceType)
+    {
+      case OrthancPluginResourceType_Patient:
+        return Orthanc::DatabasePluginMessages::RESOURCE_PATIENT;
+
+      case OrthancPluginResourceType_Study:
+        return Orthanc::DatabasePluginMessages::RESOURCE_STUDY;
+
+      case OrthancPluginResourceType_Series:
+        return Orthanc::DatabasePluginMessages::RESOURCE_SERIES;
+
+      case OrthancPluginResourceType_Instance:
+        return Orthanc::DatabasePluginMessages::RESOURCE_INSTANCE;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  static OrthancPluginResourceType Convert(Orthanc::DatabasePluginMessages::ResourceType resourceType)
+  {
+    switch (resourceType)
+    {
+      case Orthanc::DatabasePluginMessages::RESOURCE_PATIENT:
+        return OrthancPluginResourceType_Patient;
+
+      case Orthanc::DatabasePluginMessages::RESOURCE_STUDY:
+        return OrthancPluginResourceType_Study;
+
+      case Orthanc::DatabasePluginMessages::RESOURCE_SERIES:
+        return OrthancPluginResourceType_Series;
+
+      case Orthanc::DatabasePluginMessages::RESOURCE_INSTANCE:
+        return OrthancPluginResourceType_Instance;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  class Output : public IDatabaseBackendOutput
+  {
+  private:
+    Orthanc::DatabasePluginMessages::DeleteAttachment::Response*         deleteAttachment_;
+    Orthanc::DatabasePluginMessages::DeleteResource::Response*           deleteResource_;
+    Orthanc::DatabasePluginMessages::GetChanges::Response*               getChanges_;
+    Orthanc::DatabasePluginMessages::GetExportedResources::Response*     getExportedResources_;
+    Orthanc::DatabasePluginMessages::GetLastChange::Response*            getLastChange_;
+    Orthanc::DatabasePluginMessages::GetLastExportedResource::Response*  getLastExportedResource_;
+    Orthanc::DatabasePluginMessages::GetMainDicomTags::Response*         getMainDicomTags_;
+    Orthanc::DatabasePluginMessages::LookupAttachment::Response*         lookupAttachment_;
+    Orthanc::DatabasePluginMessages::LookupResources::Response*          lookupResources_;
+
+    void Clear()
+    {
+      deleteAttachment_ = NULL;
+      deleteResource_ = NULL;
+      getChanges_ = NULL;
+      getExportedResources_ = NULL;
+      getLastChange_ = NULL;
+      getLastExportedResource_ = NULL;
+      getMainDicomTags_ = NULL;
+      lookupAttachment_ = NULL;
+      lookupResources_ = NULL;
+    }
+    
+  public:
+    Output(Orthanc::DatabasePluginMessages::DeleteAttachment::Response& deleteAttachment)
+    {
+      Clear();
+      deleteAttachment_ = &deleteAttachment;
+    }
+    
+    Output(Orthanc::DatabasePluginMessages::DeleteResource::Response& deleteResource)
+    {
+      Clear();
+      deleteResource_ = &deleteResource;
+    }
+    
+    Output(Orthanc::DatabasePluginMessages::GetChanges::Response& getChanges)
+    {
+      Clear();
+      getChanges_ = &getChanges;
+    }
+    
+    Output(Orthanc::DatabasePluginMessages::GetExportedResources::Response& getExportedResources)
+    {
+      Clear();
+      getExportedResources_ = &getExportedResources;
+    }
+    
+    Output(Orthanc::DatabasePluginMessages::GetLastChange::Response& getLastChange)
+    {
+      Clear();
+      getLastChange_ = &getLastChange;
+    }
+    
+    Output(Orthanc::DatabasePluginMessages::GetLastExportedResource::Response& getLastExportedResource)
+    {
+      Clear();
+      getLastExportedResource_ = &getLastExportedResource;
+    }
+    
+    Output(Orthanc::DatabasePluginMessages::GetMainDicomTags::Response& getMainDicomTags)
+    {
+      Clear();
+      getMainDicomTags_ = &getMainDicomTags;
+    }
+    
+    Output(Orthanc::DatabasePluginMessages::LookupAttachment::Response& lookupAttachment)
+    {
+      Clear();
+      lookupAttachment_ = &lookupAttachment;
+    }
+    
+    Output(Orthanc::DatabasePluginMessages::LookupResources::Response& lookupResources)
+    {
+      Clear();
+      lookupResources_ = &lookupResources;
+    }
+    
+    virtual void SignalDeletedAttachment(const std::string& uuid,
+                                         int32_t            contentType,
+                                         uint64_t           uncompressedSize,
+                                         const std::string& uncompressedHash,
+                                         int32_t            compressionType,
+                                         uint64_t           compressedSize,
+                                         const std::string& compressedHash) ORTHANC_OVERRIDE
+    {
+      Orthanc::DatabasePluginMessages::FileInfo* attachment;
+
+      if (deleteAttachment_ != NULL)
+      {
+        if (deleteAttachment_->has_deleted_attachment())
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+        }
+        
+        attachment = deleteAttachment_->mutable_deleted_attachment();
+      }
+      else if (deleteResource_ != NULL)
+      {
+        attachment = deleteResource_->add_deleted_attachments();
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+      
+      attachment->set_uuid(uuid);
+      attachment->set_content_type(contentType);
+      attachment->set_uncompressed_size(uncompressedSize);
+      attachment->set_uncompressed_hash(uncompressedHash);
+      attachment->set_compression_type(compressionType);
+      attachment->set_compressed_size(compressedSize);
+      attachment->set_compressed_hash(compressedHash);
+    }
+
+    virtual void SignalDeletedResource(const std::string& publicId,
+                                       OrthancPluginResourceType resourceType) ORTHANC_OVERRIDE
+    {
+      if (deleteResource_ != NULL)
+      {
+        Orthanc::DatabasePluginMessages::DeleteResource_Response_Resource* resource = deleteResource_->add_deleted_resources();
+        resource->set_level(Convert(resourceType));
+        resource->set_public_id(publicId);
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+    }
+
+    virtual void SignalRemainingAncestor(const std::string& ancestorId,
+                                         OrthancPluginResourceType ancestorType) ORTHANC_OVERRIDE
+    {
+      if (deleteResource_ != NULL)
+      {
+        if (deleteResource_->is_remaining_ancestor())
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+        }
+        else
+        {
+          deleteResource_->set_is_remaining_ancestor(true);
+          deleteResource_->mutable_remaining_ancestor()->set_level(Convert(ancestorType));
+          deleteResource_->mutable_remaining_ancestor()->set_public_id(ancestorId);
+        }
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+    }
+    
+    virtual void AnswerAttachment(const std::string& uuid,
+                                  int32_t            contentType,
+                                  uint64_t           uncompressedSize,
+                                  const std::string& uncompressedHash,
+                                  int32_t            compressionType,
+                                  uint64_t           compressedSize,
+                                  const std::string& compressedHash) ORTHANC_OVERRIDE
+    {
+      if (lookupAttachment_ != NULL)
+      {
+        if (lookupAttachment_->found())
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+        }
+
+        lookupAttachment_->set_found(true);
+        lookupAttachment_->mutable_attachment()->set_uuid(uuid);
+        lookupAttachment_->mutable_attachment()->set_content_type(contentType);
+        lookupAttachment_->mutable_attachment()->set_uncompressed_size(uncompressedSize);
+        lookupAttachment_->mutable_attachment()->set_uncompressed_hash(uncompressedHash);
+        lookupAttachment_->mutable_attachment()->set_compression_type(compressionType);
+        lookupAttachment_->mutable_attachment()->set_compressed_size(compressedSize);
+        lookupAttachment_->mutable_attachment()->set_compressed_hash(compressedHash);
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+    }
+
+    virtual void AnswerChange(int64_t                    seq,
+                              int32_t                    changeType,
+                              OrthancPluginResourceType  resourceType,
+                              const std::string&         publicId,
+                              const std::string&         date) ORTHANC_OVERRIDE
+    {
+      Orthanc::DatabasePluginMessages::ServerIndexChange* change;
+      
+      if (getChanges_ != NULL)
+      {
+        change = getChanges_->add_changes();
+      }
+      else if (getLastChange_ != NULL)
+      {
+        if (getLastChange_->found())
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+        }
+
+        getLastChange_->set_found(true);
+        change = getLastChange_->mutable_change();
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+
+      change->set_seq(seq);
+      change->set_change_type(changeType);
+      change->set_resource_type(Convert(resourceType));
+      change->set_public_id(publicId);
+      change->set_date(date);
+    }
+
+    virtual void AnswerDicomTag(uint16_t group,
+                                uint16_t element,
+                                const std::string& value) ORTHANC_OVERRIDE
+    {
+      if (getMainDicomTags_ != NULL)
+      {
+        Orthanc::DatabasePluginMessages::GetMainDicomTags_Response_Tag* tag = getMainDicomTags_->add_tags();
+        tag->set_group(group);
+        tag->set_element(element);
+        tag->set_value(value);
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+    }
+
+    virtual void AnswerExportedResource(int64_t                    seq,
+                                        OrthancPluginResourceType  resourceType,
+                                        const std::string&         publicId,
+                                        const std::string&         modality,
+                                        const std::string&         date,
+                                        const std::string&         patientId,
+                                        const std::string&         studyInstanceUid,
+                                        const std::string&         seriesInstanceUid,
+                                        const std::string&         sopInstanceUid) ORTHANC_OVERRIDE
+    {
+      Orthanc::DatabasePluginMessages::ExportedResource* resource;
+
+      if (getExportedResources_ != NULL)
+      {
+        resource = getExportedResources_->add_resources();
+      }
+      else if (getLastExportedResource_ != NULL)
+      {
+        if (getLastExportedResource_->found())
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+        }
+
+        getLastExportedResource_->set_found(true);
+        resource = getLastExportedResource_->mutable_resource();
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+      
+      resource->set_seq(seq);
+      resource->set_resource_type(Convert(resourceType));
+      resource->set_public_id(publicId);
+      resource->set_modality(modality);
+      resource->set_date(date);
+      resource->set_patient_id(patientId);
+      resource->set_study_instance_uid(studyInstanceUid);
+      resource->set_series_instance_uid(seriesInstanceUid);
+      resource->set_sop_instance_uid(sopInstanceUid);
+    }
+    
+    virtual void AnswerMatchingResource(const std::string& resourceId) ORTHANC_OVERRIDE
+    {
+      if (lookupResources_ != NULL)
+      {
+        lookupResources_->add_resources_ids(resourceId);
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+    }
+    
+    virtual void AnswerMatchingResource(const std::string& resourceId,
+                                        const std::string& someInstanceId) ORTHANC_OVERRIDE
+    {
+      if (lookupResources_ != NULL)
+      {
+        lookupResources_->add_resources_ids(resourceId);
+        lookupResources_->add_instances_ids(someInstanceId);
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+      }
+    }
+  };
+  
+
+  static void ProcessDatabaseOperation(Orthanc::DatabasePluginMessages::DatabaseResponse& response,
+                                       const Orthanc::DatabasePluginMessages::DatabaseRequest& request,
+                                       IndexConnectionsPool& pool)
+  {
+    switch (request.operation())
+    {
+      case Orthanc::DatabasePluginMessages::OPERATION_GET_SYSTEM_INFORMATION:
+      {
+        IndexConnectionsPool::Accessor accessor(pool);
+        response.mutable_get_system_information()->set_database_version(accessor.GetBackend().GetDatabaseVersion(accessor.GetManager()));
+        response.mutable_get_system_information()->set_supports_flush_to_disk(false);
+        response.mutable_get_system_information()->set_supports_revisions(accessor.GetBackend().HasRevisionsSupport());
+        break;
+      }
+
+      case Orthanc::DatabasePluginMessages::OPERATION_OPEN:
+      {
+        pool.OpenConnections();
+        break;
+      }
+
+      case Orthanc::DatabasePluginMessages::OPERATION_CLOSE:
+      {
+        pool.CloseConnections();
+        break;
+      }
+
+      case Orthanc::DatabasePluginMessages::OPERATION_FLUSH_TO_DISK:
+      {
+        // Raise an exception since "set_supports_flush_to_disk(false)"
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      case Orthanc::DatabasePluginMessages::OPERATION_START_TRANSACTION:
+      {
+        std::unique_ptr<IndexConnectionsPool::Accessor> transaction(new IndexConnectionsPool::Accessor(pool));
+
+        switch (request.start_transaction().type())
+        {
+          case Orthanc::DatabasePluginMessages::TRANSACTION_READ_ONLY:
+            transaction->GetManager().StartTransaction(TransactionType_ReadOnly);
+            break;
+
+          case Orthanc::DatabasePluginMessages::TRANSACTION_READ_WRITE:
+            transaction->GetManager().StartTransaction(TransactionType_ReadWrite);
+            break;
+
+          default:
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+        }
+
+        response.mutable_start_transaction()->set_transaction(reinterpret_cast<intptr_t>(transaction.release()));
+        break;
+      }
+        
+      case Orthanc::DatabasePluginMessages::OPERATION_UPGRADE:
+      {
+        IndexConnectionsPool::Accessor accessor(pool);
+        OrthancPluginStorageArea* storageArea = reinterpret_cast<OrthancPluginStorageArea*>(request.upgrade().storage_area());
+        accessor.GetBackend().UpgradeDatabase(accessor.GetManager(), request.upgrade().target_version(), storageArea);
+        break;
+      }
+              
+      case Orthanc::DatabasePluginMessages::OPERATION_FINALIZE_TRANSACTION:
+      {
+        IndexConnectionsPool::Accessor* transaction = reinterpret_cast<IndexConnectionsPool::Accessor*>(request.finalize_transaction().transaction());
+        
+        if (transaction == NULL)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+        }
+        else
+        {
+          delete transaction;
+        }
+        
+        break;
+      }
+              
+      default:
+        LOG(ERROR) << "Not implemented database operation from protobuf: " << request.operation();
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  static void ApplyLookupResources(Orthanc::DatabasePluginMessages::LookupResources_Response& response,
+                                   const Orthanc::DatabasePluginMessages::LookupResources_Request& request,
+                                   IndexBackend& backend,
+                                   DatabaseManager& manager)
+  {
+    std::vector<Orthanc::DatabaseConstraint> lookup;
+    lookup.reserve(request.lookup().size());
+
+    size_t countValues = 0;
+
+    for (int i = 0; i < request.lookup().size(); i++)
+    {
+      const Orthanc::DatabasePluginMessages::DatabaseConstraint& constraint = request.lookup(i);
+      countValues += constraint.values().size();
+    }
+
+    std::vector<const char*> values;
+    values.reserve(countValues);
+
+    for (int i = 0; i < request.lookup().size(); i++)
+    {
+      const Orthanc::DatabasePluginMessages::DatabaseConstraint& constraint = request.lookup(i);
+
+      if (constraint.tag_group() > 0xffffu ||
+          constraint.tag_element() > 0xffffu)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+          
+      OrthancPluginDatabaseConstraint c;
+      c.level = Convert(constraint.level());
+      c.tagGroup = constraint.tag_group();
+      c.tagElement = constraint.tag_element();
+      c.isIdentifierTag = (constraint.is_identifier_tag() ? 1 : 0);
+      c.isCaseSensitive = (constraint.is_case_sensitive() ? 1 : 0);
+      c.isMandatory = (constraint.is_mandatory() ? 1 : 0);
+
+      switch (constraint.type())
+      {
+        case Orthanc::DatabasePluginMessages::CONSTRAINT_EQUAL:
+          c.type = OrthancPluginConstraintType_Equal;
+          break;
+              
+        case Orthanc::DatabasePluginMessages::CONSTRAINT_SMALLER_OR_EQUAL:
+          c.type = OrthancPluginConstraintType_SmallerOrEqual;
+          break;
+              
+        case Orthanc::DatabasePluginMessages::CONSTRAINT_GREATER_OR_EQUAL:
+          c.type = OrthancPluginConstraintType_GreaterOrEqual;
+          break;
+              
+        case Orthanc::DatabasePluginMessages::CONSTRAINT_WILDCARD:
+          c.type = OrthancPluginConstraintType_Wildcard;
+          break;
+              
+        case Orthanc::DatabasePluginMessages::CONSTRAINT_LIST:
+          c.type = OrthancPluginConstraintType_List;
+          break;
+              
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+          
+      c.valuesCount = constraint.values().size();
+
+      if (c.valuesCount == 0)
+      {
+        c.values = NULL;
+      }
+      else
+      {
+        c.values = &values[values.size()];
+            
+        for (int j = 0; j < constraint.values().size(); j++)
+        {
+          assert(values.size() < countValues);
+          values.push_back(constraint.values(j).c_str());
+        }
+      }
+
+      lookup.push_back(Orthanc::DatabaseConstraint(c));
+    }
+
+    assert(values.size() == countValues);
+
+    Output output(response);
+    backend.LookupResources(output, manager, lookup, Convert(request.query_level()),
+                            request.limit(), request.retrieve_instances_ids());
+  }
+
+  
+  static void ProcessTransactionOperation(Orthanc::DatabasePluginMessages::TransactionResponse& response,
+                                          const Orthanc::DatabasePluginMessages::TransactionRequest& request,
+                                          IndexBackend& backend,
+                                          DatabaseManager& manager)
+  {
+    switch (request.operation())
+    {
+      case Orthanc::DatabasePluginMessages::OPERATION_ROLLBACK:
+      {
+        manager.RollbackTransaction();
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_COMMIT:
+      {
+        manager.CommitTransaction();
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_ADD_ATTACHMENT:
+      {
+        OrthancPluginAttachment attachment;
+        attachment.uuid = request.add_attachment().attachment().uuid().c_str();
+        attachment.contentType = request.add_attachment().attachment().content_type();
+        attachment.uncompressedSize = request.add_attachment().attachment().uncompressed_size();
+        attachment.uncompressedHash = request.add_attachment().attachment().uncompressed_hash().c_str();
+        attachment.compressionType = request.add_attachment().attachment().compression_type();
+        attachment.compressedSize = request.add_attachment().attachment().compressed_size();
+        attachment.compressedHash = request.add_attachment().attachment().compressed_hash().c_str();
+        
+        backend.AddAttachment(manager, request.add_attachment().id(), attachment, request.add_attachment().revision());
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_CLEAR_CHANGES:
+      {
+        backend.ClearChanges(manager);
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_CLEAR_EXPORTED_RESOURCES:
+      {
+        backend.ClearExportedResources(manager);
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_DELETE_ATTACHMENT:
+      {
+        Output output(*response.mutable_delete_attachment());
+        backend.DeleteAttachment(output, manager, request.delete_attachment().id(), request.delete_attachment().type());
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_DELETE_METADATA:
+      {
+        backend.DeleteMetadata(manager, request.delete_metadata().id(), request.delete_metadata().type());
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_DELETE_RESOURCE:
+      {
+        response.mutable_delete_resource()->set_is_remaining_ancestor(false);
+
+        Output output(*response.mutable_delete_resource());
+        backend.DeleteResource(output, manager, request.delete_resource().id());
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_GET_ALL_METADATA:
+      {
+        typedef std::map<int32_t, std::string>  Values;
+
+        Values values;
+        backend.GetAllMetadata(values, manager, request.get_all_metadata().id());
+
+        response.mutable_get_all_metadata()->mutable_metadata()->Reserve(values.size());
+        for (Values::const_iterator it = values.begin(); it != values.end(); ++it)
+        {
+          Orthanc::DatabasePluginMessages::GetAllMetadata_Response_Metadata* metadata =
+            response.mutable_get_all_metadata()->add_metadata();
+          metadata->set_type(it->first);
+          metadata->set_value(it->second);
+        }
+        
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_GET_ALL_PUBLIC_IDS:
+      {
+        std::list<std::string>  values;
+        backend.GetAllPublicIds(values, manager, Convert(request.get_all_public_ids().resource_type()));
+
+        response.mutable_get_all_public_ids()->mutable_ids()->Reserve(values.size());
+        for (std::list<std::string>::const_iterator it = values.begin(); it != values.end(); ++it)
+        {
+          response.mutable_get_all_public_ids()->add_ids(*it);
+        }
+        
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_GET_ALL_PUBLIC_IDS_WITH_LIMITS:
+      {
+        std::list<std::string>  values;
+        backend.GetAllPublicIds(values, manager, Convert(request.get_all_public_ids_with_limits().resource_type()),
+                                request.get_all_public_ids_with_limits().since(),
+                                request.get_all_public_ids_with_limits().limit());
+
+        response.mutable_get_all_public_ids_with_limits()->mutable_ids()->Reserve(values.size());
+        for (std::list<std::string>::const_iterator it = values.begin(); it != values.end(); ++it)
+        {
+          response.mutable_get_all_public_ids_with_limits()->add_ids(*it);
+        }
+        
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_GET_CHANGES:
+      {
+        Output output(*response.mutable_get_changes());
+
+        bool done;
+        backend.GetChanges(output, done, manager, request.get_changes().since(), request.get_changes().limit());
+
+        response.mutable_get_changes()->set_done(done);
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_GET_CHILDREN_INTERNAL_ID:
+      {
+        std::list<int64_t>  values;
+        backend.GetChildrenInternalId(values, manager, request.get_children_internal_id().id());
+
+        response.mutable_get_children_internal_id()->mutable_ids()->Reserve(values.size());
+        for (std::list<int64_t>::const_iterator it = values.begin(); it != values.end(); ++it)
+        {
+          response.mutable_get_children_internal_id()->add_ids(*it);
+        }
+        
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_GET_CHILDREN_PUBLIC_ID:
+      {
+        std::list<std::string>  values;
+        backend.GetChildrenPublicId(values, manager, request.get_children_public_id().id());
+
+        response.mutable_get_children_public_id()->mutable_ids()->Reserve(values.size());
+        for (std::list<std::string>::const_iterator it = values.begin(); it != values.end(); ++it)
+        {
+          response.mutable_get_children_public_id()->add_ids(*it);
+        }
+        
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_GET_EXPORTED_RESOURCES:
+      {
+        Output output(*response.mutable_get_exported_resources());
+
+        bool done;
+        backend.GetExportedResources(output, done, manager, request.get_exported_resources().since(),
+                                     request.get_exported_resources().limit());
+
+        response.mutable_get_exported_resources()->set_done(done);
+        break;
+      }
+
+      case Orthanc::DatabasePluginMessages::OPERATION_GET_LAST_CHANGE:
+      {
+        response.mutable_get_last_change()->set_found(false);
+
+        Output output(*response.mutable_get_last_change());
+        backend.GetLastChange(output, manager);
+        break;
+      }
+
+      case Orthanc::DatabasePluginMessages::OPERATION_GET_LAST_EXPORTED_RESOURCE:
+      {
+        response.mutable_get_last_exported_resource()->set_found(false);
+
+        Output output(*response.mutable_get_last_exported_resource());
+        backend.GetLastExportedResource(output, manager);
+        break;
+      }
+
+      case Orthanc::DatabasePluginMessages::OPERATION_GET_MAIN_DICOM_TAGS:
+      {
+        Output output(*response.mutable_get_main_dicom_tags());
+        backend.GetMainDicomTags(output, manager, request.get_main_dicom_tags().id());
+        break;
+      }
+
+      case Orthanc::DatabasePluginMessages::OPERATION_GET_PUBLIC_ID:
+      {
+        const std::string id = backend.GetPublicId(manager, request.get_public_id().id());
+        response.mutable_get_public_id()->set_id(id);
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_GET_RESOURCES_COUNT:
+      {
+        OrthancPluginResourceType type = Convert(request.get_resources_count().type());
+        uint64_t count = backend.GetResourcesCount(manager, type);
+        response.mutable_get_resources_count()->set_count(count);
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_GET_RESOURCE_TYPE:
+      {
+        OrthancPluginResourceType type = backend.GetResourceType(manager, request.get_resource_type().id());
+        response.mutable_get_resource_type()->set_type(Convert(type));
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_GET_TOTAL_COMPRESSED_SIZE:
+      {
+        response.mutable_get_total_compressed_size()->set_size(backend.GetTotalCompressedSize(manager));
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_GET_TOTAL_UNCOMPRESSED_SIZE:
+      {
+        response.mutable_get_total_uncompressed_size()->set_size(backend.GetTotalUncompressedSize(manager));
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_IS_PROTECTED_PATIENT:
+      {
+        bool isProtected = backend.IsProtectedPatient(manager, request.is_protected_patient().patient_id());
+        response.mutable_is_protected_patient()->set_protected_patient(isProtected);
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_LIST_AVAILABLE_ATTACHMENTS:
+      {
+        std::list<int32_t>  values;
+        backend.ListAvailableAttachments(values, manager, request.list_available_attachments().id());
+
+        response.mutable_list_available_attachments()->mutable_attachments()->Reserve(values.size());
+        for (std::list<int32_t>::const_iterator it = values.begin(); it != values.end(); ++it)
+        {
+          response.mutable_list_available_attachments()->add_attachments(*it);
+        }
+        
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_LOG_CHANGE:
+      {
+        backend.LogChange(manager, request.log_change().change_type(),
+                          request.log_change().resource_id(),
+                          Convert(request.log_change().resource_type()),
+                          request.log_change().date().c_str());
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_LOG_EXPORTED_RESOURCE:
+      {
+        backend.LogExportedResource(manager,
+                                    Convert(request.log_exported_resource().resource_type()),
+                                    request.log_exported_resource().public_id().c_str(),
+                                    request.log_exported_resource().modality().c_str(),
+                                    request.log_exported_resource().date().c_str(),
+                                    request.log_exported_resource().patient_id().c_str(),
+                                    request.log_exported_resource().study_instance_uid().c_str(),
+                                    request.log_exported_resource().series_instance_uid().c_str(),
+                                    request.log_exported_resource().sop_instance_uid().c_str());
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_ATTACHMENT:
+      {
+        Output output(*response.mutable_lookup_attachment());
+        
+        int64_t revision = -1;
+        backend.LookupAttachment(output, revision, manager, request.lookup_attachment().id(), request.lookup_attachment().content_type());
+
+        if (response.lookup_attachment().found())
+        {
+          response.mutable_lookup_attachment()->set_revision(revision);
+        }
+        
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_GLOBAL_PROPERTY:
+      {
+        std::string value;
+        if (backend.LookupGlobalProperty(value, manager, request.lookup_global_property().server_id().c_str(),
+                                         request.lookup_global_property().property()))
+        {
+          response.mutable_lookup_global_property()->set_found(true);
+          response.mutable_lookup_global_property()->set_value(value);
+        }
+        else
+        {
+          response.mutable_lookup_global_property()->set_found(false);
+        }
+        
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_METADATA:
+      {
+        std::string value;
+        int64_t revision = -1;
+        if (backend.LookupMetadata(value, revision, manager, request.lookup_metadata().id(), request.lookup_metadata().metadata_type()))
+        {
+          response.mutable_lookup_metadata()->set_found(true);
+          response.mutable_lookup_metadata()->set_value(value);
+          response.mutable_lookup_metadata()->set_revision(revision);
+        }
+        else
+        {
+          response.mutable_lookup_metadata()->set_found(false);
+        }
+        
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_PARENT:
+      {
+        int64_t parent = -1;
+        if (backend.LookupParent(parent, manager, request.lookup_parent().id()))
+        {
+          response.mutable_lookup_parent()->set_found(true);
+          response.mutable_lookup_parent()->set_parent(parent);
+        }
+        else
+        {
+          response.mutable_lookup_parent()->set_found(false);
+        }
+        
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_RESOURCE:
+      {
+        int64_t internalId = -1;
+        OrthancPluginResourceType type;
+        if (backend.LookupResource(internalId, type, manager, request.lookup_resource().public_id().c_str()))
+        {
+          response.mutable_lookup_resource()->set_found(true);
+          response.mutable_lookup_resource()->set_internal_id(internalId);
+          response.mutable_lookup_resource()->set_type(Convert(type));
+        }
+        else
+        {
+          response.mutable_lookup_resource()->set_found(false);
+        }
+        
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_SELECT_PATIENT_TO_RECYCLE:
+      {
+        int64_t patientId = -1;
+        if (backend.SelectPatientToRecycle(patientId, manager))
+        {
+          response.mutable_select_patient_to_recycle()->set_found(true);
+          response.mutable_select_patient_to_recycle()->set_patient_id(patientId);
+        }
+        else
+        {
+          response.mutable_select_patient_to_recycle()->set_found(false);
+        }
+        
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_SELECT_PATIENT_TO_RECYCLE_WITH_AVOID:
+      {
+        int64_t patientId = -1;
+        if (backend.SelectPatientToRecycle(patientId, manager, request.select_patient_to_recycle_with_avoid().patient_id_to_avoid()))
+        {
+          response.mutable_select_patient_to_recycle_with_avoid()->set_found(true);
+          response.mutable_select_patient_to_recycle_with_avoid()->set_patient_id(patientId);
+        }
+        else
+        {
+          response.mutable_select_patient_to_recycle_with_avoid()->set_found(false);
+        }
+        
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_SET_GLOBAL_PROPERTY:
+      {
+        backend.SetGlobalProperty(manager, request.set_global_property().server_id().c_str(),
+                                  request.set_global_property().property(),
+                                  request.set_global_property().value().c_str());
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_CLEAR_MAIN_DICOM_TAGS:
+      {
+        backend.ClearMainDicomTags(manager, request.clear_main_dicom_tags().id());
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_SET_METADATA:
+      {
+        backend.SetMetadata(manager, request.set_metadata().id(),
+                            request.set_metadata().metadata_type(),
+                            request.set_metadata().value().c_str(),
+                            request.set_metadata().revision());
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_SET_PROTECTED_PATIENT:
+      {
+        backend.SetProtectedPatient(manager, request.set_protected_patient().patient_id(),
+                                    request.set_protected_patient().protected_patient());
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_IS_DISK_SIZE_ABOVE:
+      {
+        bool above = (backend.GetTotalCompressedSize(manager) >= request.is_disk_size_above().threshold());
+        response.mutable_is_disk_size_above()->set_result(above);
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_RESOURCES:
+      {
+        ApplyLookupResources(*response.mutable_lookup_resources(), request.lookup_resources(), backend, manager);
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_CREATE_INSTANCE:
+      {
+        const char* hashPatient = request.create_instance().patient().c_str();
+        const char* hashStudy = request.create_instance().study().c_str();
+        const char* hashSeries = request.create_instance().series().c_str();
+        const char* hashInstance = request.create_instance().instance().c_str();
+        
+        OrthancPluginCreateInstanceResult result;
+        
+        if (backend.HasCreateInstance())
+        {
+          backend.CreateInstance(result, manager, hashPatient, hashStudy, hashSeries, hashInstance);
+        }
+        else
+        {
+          backend.CreateInstanceGeneric(result, manager, hashPatient, hashStudy, hashSeries, hashInstance);
+        }
+
+        response.mutable_create_instance()->set_is_new_instance(result.isNewInstance);
+        response.mutable_create_instance()->set_instance_id(result.instanceId);
+
+        if (result.isNewInstance)
+        {
+          response.mutable_create_instance()->set_is_new_patient(result.isNewPatient);
+          response.mutable_create_instance()->set_is_new_study(result.isNewStudy);
+          response.mutable_create_instance()->set_is_new_series(result.isNewSeries);
+          response.mutable_create_instance()->set_patient_id(result.patientId);
+          response.mutable_create_instance()->set_study_id(result.studyId);
+          response.mutable_create_instance()->set_series_id(result.seriesId);
+        }
+        
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_SET_RESOURCES_CONTENT:
+      {
+        std::vector<OrthancPluginResourcesContentTags> identifierTags;
+        std::vector<OrthancPluginResourcesContentTags> mainDicomTags;
+
+        identifierTags.reserve(request.set_resources_content().tags().size());
+        mainDicomTags.reserve(request.set_resources_content().tags().size());
+        
+        for (int i = 0; i < request.set_resources_content().tags().size(); i++)
+        {
+          if (request.set_resources_content().tags(i).group() > 0xffffu ||
+              request.set_resources_content().tags(i).element() > 0xffffu)
+          {
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+          }
+          
+          OrthancPluginResourcesContentTags tag;
+          tag.resource = request.set_resources_content().tags(i).resource_id();
+          tag.group = request.set_resources_content().tags(i).group();
+          tag.element = request.set_resources_content().tags(i).element();
+          tag.value = request.set_resources_content().tags(i).value().c_str();
+
+          if (request.set_resources_content().tags(i).is_identifier())
+          {
+            identifierTags.push_back(tag);
+          }
+          else
+          {
+            mainDicomTags.push_back(tag);
+          }
+        }
+        
+        std::vector<OrthancPluginResourcesContentMetadata> metadata;
+        metadata.reserve(request.set_resources_content().metadata().size());
+        
+        for (int i = 0; i < request.set_resources_content().metadata().size(); i++)
+        {
+          OrthancPluginResourcesContentMetadata item;
+          item.resource = request.set_resources_content().metadata(i).resource_id();
+          item.metadata = request.set_resources_content().metadata(i).metadata();
+          item.value = request.set_resources_content().metadata(i).value().c_str();
+          metadata.push_back(item);
+        }
+
+        backend.SetResourcesContent(manager,
+                                    identifierTags.size(), (identifierTags.empty() ? NULL : &identifierTags[0]),
+                                    mainDicomTags.size(), (mainDicomTags.empty() ? NULL : &mainDicomTags[0]),
+                                    metadata.size(), (metadata.empty() ? NULL : &metadata[0]));
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_GET_CHILDREN_METADATA:
+      {
+        std::list<std::string> values;
+        backend.GetChildrenMetadata(values, manager, request.get_children_metadata().id(), request.get_children_metadata().metadata());
+
+        response.mutable_get_children_metadata()->mutable_values()->Reserve(values.size());
+        for (std::list<std::string>::const_iterator it = values.begin(); it != values.end(); ++it)
+        {
+          response.mutable_get_children_metadata()->add_values(*it);
+        }
+
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_GET_LAST_CHANGE_INDEX:
+      {
+        response.mutable_get_last_change_index()->set_result(backend.GetLastChangeIndex(manager));
+        break;
+      }
+      
+      case Orthanc::DatabasePluginMessages::OPERATION_LOOKUP_RESOURCE_AND_PARENT:
+      {
+        int64_t id;
+        OrthancPluginResourceType type;
+        std::string parent;
+        
+        if (backend.LookupResourceAndParent(id, type, parent, manager, request.lookup_resource_and_parent().public_id().c_str()))
+        {
+          response.mutable_lookup_resource_and_parent()->set_found(true);
+          response.mutable_lookup_resource_and_parent()->set_id(id);
+          response.mutable_lookup_resource_and_parent()->set_type(Convert(type));
+
+          switch (type)
+          {
+            case OrthancPluginResourceType_Study:
+            case OrthancPluginResourceType_Series:
+            case OrthancPluginResourceType_Instance:
+              if (parent.empty())
+              {
+                throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+              }
+              else
+              {
+                response.mutable_lookup_resource_and_parent()->set_parent_public_id(parent);
+              }
+              break;
+
+            case OrthancPluginResourceType_Patient:
+              if (!parent.empty())
+              {
+                throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+              }
+              break;
+
+            default:
+              throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+          }
+        }
+        else
+        {
+          response.mutable_lookup_resource_and_parent()->set_found(false);
+        }
+
+        break;
+      }
+      
+      default:
+        LOG(ERROR) << "Not implemented transaction operation from protobuf: " << request.operation();
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+  
+  static OrthancPluginErrorCode CallBackend(OrthancPluginMemoryBuffer64* serializedResponse,
+                                            void* rawPool,
+                                            const void* requestData,
+                                            uint64_t requestSize)
+  {
+    Orthanc::DatabasePluginMessages::Request request;
+    if (!request.ParseFromArray(requestData, requestSize))
+    {
+      LOG(ERROR) << "Cannot parse message from the Orthanc core using protobuf";
+      return OrthancPluginErrorCode_InternalError;
+    }
+
+    if (rawPool == NULL)
+    {
+      LOG(ERROR) << "Received a NULL pointer from the database";
+      return OrthancPluginErrorCode_InternalError;
+    }
+
+    IndexConnectionsPool& pool = *reinterpret_cast<IndexConnectionsPool*>(rawPool);
+
+    try
+    {
+      Orthanc::DatabasePluginMessages::Response response;
+      
+      switch (request.type())
+      {
+        case Orthanc::DatabasePluginMessages::REQUEST_DATABASE:
+          ProcessDatabaseOperation(*response.mutable_database_response(), request.database_request(), pool);
+          break;
+          
+        case Orthanc::DatabasePluginMessages::REQUEST_TRANSACTION:
+        {
+          IndexConnectionsPool::Accessor& transaction = *reinterpret_cast<IndexConnectionsPool::Accessor*>(request.transaction_request().transaction());
+          ProcessTransactionOperation(*response.mutable_transaction_response(), request.transaction_request(),
+                                      transaction.GetBackend(), transaction.GetManager());
+          break;
+        }
+          
+        default:
+          LOG(ERROR) << "Not implemented request type from protobuf: " << request.type();
+          break;
+      }
+
+      std::string s;
+      if (!response.SerializeToString(&s))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Cannot serialize to protobuf");
+      }
+
+      if (OrthancPluginCreateMemoryBuffer64(pool.GetContext(), serializedResponse, s.size()) != OrthancPluginErrorCode_Success)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory, "Cannot allocate a memory buffer");
+      }
+
+      if (!s.empty())
+      {
+        assert(serializedResponse->size == s.size());
+        memcpy(serializedResponse->data, s.c_str(), s.size());
+      }
+
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (::Orthanc::OrthancException& e)
+    {
+      LOG(ERROR) << "Exception in database back-end: " << e.What();
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (::std::runtime_error& e)
+    {
+      LOG(ERROR) << "Exception in database back-end: " << e.what();
+      return OrthancPluginErrorCode_DatabasePlugin;
+    }
+    catch (...)
+    {
+      LOG(ERROR) << "Native exception";
+      return OrthancPluginErrorCode_DatabasePlugin;
+    }
+  }
+
+  static void FinalizeBackend(void* rawPool)
+  {
+    if (rawPool != NULL)
+    {
+      IndexConnectionsPool* pool = reinterpret_cast<IndexConnectionsPool*>(rawPool);
+      
+      if (isBackendInUse_)
+      {
+        isBackendInUse_ = false;
+      }
+      else
+      {
+        LOG(ERROR) << "More than one index backend was registered, internal error";
+      }
+
+      delete pool;
+    }
+    else
+    {
+      LOG(ERROR) << "Received a null pointer from the Orthanc core, internal error";
+    }
+  }
+
+  
+  void DatabaseBackendAdapterV4::Register(IndexBackend* backend,
+                                          size_t countConnections,
+                                          unsigned int maxDatabaseRetries)
+  {
+    std::unique_ptr<IndexConnectionsPool> pool(new IndexConnectionsPool(backend, countConnections));
+    
+    if (isBackendInUse_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    OrthancPluginContext* context = backend->GetContext();
+ 
+    if (OrthancPluginRegisterDatabaseBackendV4(context, pool.release(), maxDatabaseRetries,
+                                               CallBackend, FinalizeBackend) != OrthancPluginErrorCode_Success)
+    {
+      delete backend;
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Unable to register the database backend");
+    }
+
+    isBackendInUse_ = true;
+  }
+
+
+  void DatabaseBackendAdapterV4::Finalize()
+  {
+    if (isBackendInUse_)
+    {
+      LOG(ERROR) << "The Orthanc core has not destructed the index backend, internal error";
+    }
+  }
+}
+
+#  endif
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Plugins/DatabaseBackendAdapterV4.h	Mon Apr 03 17:12:08 2023 +0200
@@ -0,0 +1,59 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2022 Osimis S.A., Belgium
+ * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+#pragma once
+
+#include "IndexBackend.h"
+
+
+#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)         // Macro introduced in Orthanc 1.3.1
+#  if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 0)
+
+namespace OrthancDatabases
+{  
+  /**
+   * @brief Bridge between C and C++ database engines.
+   * 
+   * Class creating the bridge between the C low-level primitives for
+   * custom database engines, and the high-level IDatabaseBackend C++
+   * interface, through ProtocolBuffers for Orthanc >= 1.12.0.
+   **/
+  class DatabaseBackendAdapterV4
+  {
+  private:
+    // This class cannot be instantiated
+    DatabaseBackendAdapterV4()
+    {
+    }
+
+  public:
+    static void Register(IndexBackend* backend,
+                         size_t countConnections,
+                         unsigned int maxDatabaseRetries);
+
+    static void Finalize();
+  };
+}
+
+#  endif
+#endif
--- a/Framework/Plugins/IDatabaseBackend.h	Tue Mar 14 09:08:45 2023 +0100
+++ b/Framework/Plugins/IDatabaseBackend.h	Mon Apr 03 17:12:08 2023 +0200
@@ -92,15 +92,15 @@
     virtual void GetAllPublicIds(std::list<std::string>& target,
                                  DatabaseManager& manager,
                                  OrthancPluginResourceType resourceType,
-                                 uint64_t since,
-                                 uint64_t limit) = 0;
+                                 int64_t since,
+                                 uint32_t limit) = 0;
 
     /* Use GetOutput().AnswerChange() */
     virtual void GetChanges(IDatabaseBackendOutput& output,
                             bool& done /*out*/,
                             DatabaseManager& manager,
                             int64_t since,
-                            uint32_t maxResults) = 0;
+                            uint32_t limit) = 0;
 
     virtual void GetChildrenInternalId(std::list<int64_t>& target /*out*/,
                                        DatabaseManager& manager,
@@ -115,7 +115,7 @@
                                       bool& done /*out*/,
                                       DatabaseManager& manager,
                                       int64_t since,
-                                      uint32_t maxResults) = 0;
+                                      uint32_t limit) = 0;
 
     /* Use GetOutput().AnswerChange() */
     virtual void GetLastChange(IDatabaseBackendOutput& output,
@@ -164,7 +164,14 @@
                            const char* date) = 0;
     
     virtual void LogExportedResource(DatabaseManager& manager,
-                                     const OrthancPluginExportedResource& resource) = 0;
+                                     OrthancPluginResourceType resourceType,
+                                     const char* publicId,
+                                     const char* modality,
+                                     const char* date,
+                                     const char* patientId,
+                                     const char* studyInstanceUid,
+                                     const char* seriesInstanceUid,
+                                     const char* sopInstanceUid) = 0;
     
     /* Use GetOutput().AnswerAttachment() */
     virtual bool LookupAttachment(IDatabaseBackendOutput& output,
--- a/Framework/Plugins/IndexBackend.cpp	Tue Mar 14 09:08:45 2023 +0100
+++ b/Framework/Plugins/IndexBackend.cpp	Mon Apr 03 17:12:08 2023 +0200
@@ -28,6 +28,7 @@
 #include "../Common/Utf8StringValue.h"
 #include "DatabaseBackendAdapterV2.h"
 #include "DatabaseBackendAdapterV3.h"
+#include "DatabaseBackendAdapterV4.h"
 #include "GlobalProperties.h"
 
 #include <Compatibility.h>  // For std::unique_ptr<>
@@ -115,13 +116,13 @@
                                          DatabaseManager& manager,
                                          DatabaseManager::CachedStatement& statement,
                                          const Dictionary& args,
-                                         uint32_t maxResults)
+                                         uint32_t limit)
   {
     statement.Execute(args);
 
     uint32_t count = 0;
 
-    while (count < maxResults &&
+    while (count < limit &&
            !statement.IsDone())
     {
       output.AnswerChange(
@@ -135,7 +136,7 @@
       count++;
     }
 
-    done = (count < maxResults ||
+    done = (count < limit ||
             statement.IsDone());
   }
 
@@ -144,13 +145,13 @@
                                                    bool& done,
                                                    DatabaseManager::CachedStatement& statement,
                                                    const Dictionary& args,
-                                                   uint32_t maxResults)
+                                                   uint32_t limit)
   {
     statement.Execute(args);
 
     uint32_t count = 0;
 
-    while (count < maxResults &&
+    while (count < limit &&
            !statement.IsDone())
     {
       int64_t seq = statement.ReadInteger64(0);
@@ -172,7 +173,7 @@
       count++;
     }
 
-    done = (count < maxResults ||
+    done = (count < limit ||
             statement.IsDone());
   }
 
@@ -518,8 +519,8 @@
   void IndexBackend::GetAllPublicIds(std::list<std::string>& target,
                                      DatabaseManager& manager,
                                      OrthancPluginResourceType resourceType,
-                                     uint64_t since,
-                                     uint64_t limit)
+                                     int64_t since,
+                                     uint32_t limit)
   {
     std::string suffix;
     if (manager.GetDialect() == Dialect_MSSQL)
@@ -555,7 +556,7 @@
                                 bool& done /*out*/,
                                 DatabaseManager& manager,
                                 int64_t since,
-                                uint32_t maxResults)
+                                uint32_t limit)
   {
     std::string suffix;
     if (manager.GetDialect() == Dialect_MSSQL)
@@ -578,10 +579,10 @@
     statement.SetParameterType("since", ValueType_Integer64);
 
     Dictionary args;
-    args.SetIntegerValue("limit", maxResults + 1);
+    args.SetIntegerValue("limit", limit + 1);
     args.SetIntegerValue("since", since);
 
-    ReadChangesInternal(output, done, manager, statement, args, maxResults);
+    ReadChangesInternal(output, done, manager, statement, args, limit);
   }
 
     
@@ -628,7 +629,7 @@
                                           bool& done /*out*/,
                                           DatabaseManager& manager,
                                           int64_t since,
-                                          uint32_t maxResults)
+                                          uint32_t limit)
   {
     std::string suffix;
     if (manager.GetDialect() == Dialect_MSSQL)
@@ -649,10 +650,10 @@
     statement.SetParameterType("since", ValueType_Integer64);
 
     Dictionary args;
-    args.SetIntegerValue("limit", maxResults + 1);
+    args.SetIntegerValue("limit", limit + 1);
     args.SetIntegerValue("since", since);
 
-    ReadExportedResourcesInternal(output, done, statement, args, maxResults);
+    ReadExportedResourcesInternal(output, done, statement, args, limit);
   }
 
     
@@ -1009,7 +1010,14 @@
 
     
   void IndexBackend::LogExportedResource(DatabaseManager& manager,
-                                         const OrthancPluginExportedResource& resource)
+                                         OrthancPluginResourceType resourceType,
+                                         const char* publicId,
+                                         const char* modality,
+                                         const char* date,
+                                         const char* patientId,
+                                         const char* studyInstanceUid,
+                                         const char* seriesInstanceUid,
+                                         const char* sopInstanceUid)
   {
     DatabaseManager::CachedStatement statement(
       STATEMENT_FROM_HERE, manager,
@@ -1026,14 +1034,14 @@
     statement.SetParameterType("date", ValueType_Utf8String);
 
     Dictionary args;
-    args.SetIntegerValue("type", resource.resourceType);
-    args.SetUtf8Value("publicId", resource.publicId);
-    args.SetUtf8Value("modality", resource.modality);
-    args.SetUtf8Value("patient", resource.patientId);
-    args.SetUtf8Value("study", resource.studyInstanceUid);
-    args.SetUtf8Value("series", resource.seriesInstanceUid);
-    args.SetUtf8Value("instance", resource.sopInstanceUid);
-    args.SetUtf8Value("date", resource.date);
+    args.SetIntegerValue("type", resourceType);
+    args.SetUtf8Value("publicId", publicId);
+    args.SetUtf8Value("modality", modality);
+    args.SetUtf8Value("patient", patientId);
+    args.SetUtf8Value("study", studyInstanceUid);
+    args.SetUtf8Value("series", seriesInstanceUid);
+    args.SetUtf8Value("instance", sopInstanceUid);
+    args.SetUtf8Value("date", date);
 
     statement.Execute(args);
   }
@@ -2610,26 +2618,31 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
     }
     
-    bool hasLoadedV3 = false;
+    LOG(WARNING) << "The index plugin will use " << countConnections << " connection(s) to the database, "
+                 << "and will retry up to " << maxDatabaseRetries << " time(s) in the case of a collision";
       
 #if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)         // Macro introduced in Orthanc 1.3.1
+#  if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 0)
+    if (OrthancPluginCheckVersionAdvanced(backend->GetContext(), 1, 12, 0) == 1)
+    {
+      OrthancDatabases::DatabaseBackendAdapterV4::Register(backend, countConnections, maxDatabaseRetries);
+      return;
+    }
+#  endif
+#endif
+
+#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)         // Macro introduced in Orthanc 1.3.1
 #  if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 9, 2)
     if (OrthancPluginCheckVersionAdvanced(backend->GetContext(), 1, 9, 2) == 1)
     {
-      LOG(WARNING) << "The index plugin will use " << countConnections << " connection(s) to the database, "
-                   << "and will retry up to " << maxDatabaseRetries << " time(s) in the case of a collision";
-      
       OrthancDatabases::DatabaseBackendAdapterV3::Register(backend, countConnections, maxDatabaseRetries);
-      hasLoadedV3 = true;
+      return;
     }
 #  endif
 #endif
 
-    if (!hasLoadedV3)
-    {
-      LOG(WARNING) << "Performance warning: Your version of the Orthanc core or SDK doesn't support multiple readers/writers";
-      OrthancDatabases::DatabaseBackendAdapterV2::Register(backend);
-    }
+    LOG(WARNING) << "Performance warning: Your version of the Orthanc core or SDK doesn't support multiple readers/writers";
+    OrthancDatabases::DatabaseBackendAdapterV2::Register(backend);
   }
 
 
@@ -2679,6 +2692,12 @@
     OrthancDatabases::DatabaseBackendAdapterV3::Finalize();
 #  endif
 #endif
+
+#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)         // Macro introduced in Orthanc 1.3.1
+#  if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 0)
+    OrthancDatabases::DatabaseBackendAdapterV4::Finalize();
+#  endif
+#endif
   }
 
 
--- a/Framework/Plugins/IndexBackend.h	Tue Mar 14 09:08:45 2023 +0100
+++ b/Framework/Plugins/IndexBackend.h	Mon Apr 03 17:12:08 2023 +0200
@@ -62,13 +62,13 @@
                              DatabaseManager& manager,
                              DatabaseManager::CachedStatement& statement,
                              const Dictionary& args,
-                             uint32_t maxResults);
+                             uint32_t limit);
 
     void ReadExportedResourcesInternal(IDatabaseBackendOutput& output,
                                        bool& done,
                                        DatabaseManager::CachedStatement& statement,
                                        const Dictionary& args,
-                                       uint32_t maxResults);
+                                       uint32_t limit);
 
   public:
     explicit IndexBackend(OrthancPluginContext* context);
@@ -119,14 +119,14 @@
     virtual void GetAllPublicIds(std::list<std::string>& target,
                                  DatabaseManager& manager,
                                  OrthancPluginResourceType resourceType,
-                                 uint64_t since,
-                                 uint64_t limit) ORTHANC_OVERRIDE;
+                                 int64_t since,
+                                 uint32_t limit) ORTHANC_OVERRIDE;
     
     virtual void GetChanges(IDatabaseBackendOutput& output,
                             bool& done /*out*/,
                             DatabaseManager& manager,
                             int64_t since,
-                            uint32_t maxResults) ORTHANC_OVERRIDE;
+                            uint32_t limit) ORTHANC_OVERRIDE;
     
     virtual void GetChildrenInternalId(std::list<int64_t>& target /*out*/,
                                        DatabaseManager& manager,
@@ -140,7 +140,7 @@
                                       bool& done /*out*/,
                                       DatabaseManager& manager,
                                       int64_t since,
-                                      uint32_t maxResults) ORTHANC_OVERRIDE;
+                                      uint32_t limit) ORTHANC_OVERRIDE;
     
     virtual void GetLastChange(IDatabaseBackendOutput& output,
                                DatabaseManager& manager) ORTHANC_OVERRIDE;
@@ -186,7 +186,14 @@
                            const char* date) ORTHANC_OVERRIDE;
     
     virtual void LogExportedResource(DatabaseManager& manager,
-                                     const OrthancPluginExportedResource& resource) ORTHANC_OVERRIDE;
+                                     OrthancPluginResourceType resourceType,
+                                     const char* publicId,
+                                     const char* modality,
+                                     const char* date,
+                                     const char* patientId,
+                                     const char* studyInstanceUid,
+                                     const char* seriesInstanceUid,
+                                     const char* sopInstanceUid) ORTHANC_OVERRIDE;
     
     virtual bool LookupAttachment(IDatabaseBackendOutput& output,
                                   int64_t& revision /*out*/,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Plugins/IndexConnectionsPool.cpp	Mon Apr 03 17:12:08 2023 +0200
@@ -0,0 +1,173 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2022 Osimis S.A., Belgium
+ * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "IndexConnectionsPool.h"
+
+namespace OrthancDatabases
+{
+  class IndexConnectionsPool::ManagerReference : public Orthanc::IDynamicObject
+  {
+  private:
+    DatabaseManager*  manager_;
+
+  public:
+    ManagerReference(DatabaseManager& manager) :
+      manager_(&manager)
+    {
+    }
+
+    DatabaseManager& GetManager()
+    {
+      assert(manager_ != NULL);
+      return *manager_;
+    }
+  };
+
+
+  IndexConnectionsPool::IndexConnectionsPool(IndexBackend* backend,
+                                             size_t countConnections) :
+    backend_(backend),
+    countConnections_(countConnections)
+  {
+    if (countConnections == 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                      "There must be a non-zero number of connections to the database");
+    }
+    else if (backend == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+    else
+    {
+      context_ = backend_->GetContext();
+    }
+  }
+
+  
+  IndexConnectionsPool::~IndexConnectionsPool()
+  {
+    for (std::list<DatabaseManager*>::iterator
+           it = connections_.begin(); it != connections_.end(); ++it)
+    {
+      assert(*it != NULL);
+      delete *it;
+    }
+  }
+
+
+  void IndexConnectionsPool::OpenConnections()
+  {
+    boost::unique_lock<boost::shared_mutex>  lock(connectionsMutex_);
+
+    if (connections_.size() == 0)
+    {
+      assert(backend_.get() != NULL);
+
+      {
+        std::unique_ptr<DatabaseManager> manager(new DatabaseManager(backend_->CreateDatabaseFactory()));
+        manager->GetDatabase();  // Make sure to open the database connection
+          
+        backend_->ConfigureDatabase(*manager);
+        connections_.push_back(manager.release());
+      }
+
+      for (size_t i = 1; i < countConnections_; i++)
+      {
+        connections_.push_back(new DatabaseManager(backend_->CreateDatabaseFactory()));
+        connections_.back()->GetDatabase();  // Make sure to open the database connection
+      }
+
+      for (std::list<DatabaseManager*>::iterator
+             it = connections_.begin(); it != connections_.end(); ++it)
+      {
+        assert(*it != NULL);
+        availableConnections_.Enqueue(new ManagerReference(**it));
+      }        
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  void IndexConnectionsPool::CloseConnections()
+  {
+    boost::unique_lock<boost::shared_mutex>  lock(connectionsMutex_);
+
+    if (connections_.size() != countConnections_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else if (availableConnections_.GetSize() != countConnections_)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_Database, "Some connections are still in use, bug in the Orthanc core");
+    }
+    else
+    {
+      for (std::list<DatabaseManager*>::iterator
+             it = connections_.begin(); it != connections_.end(); ++it)
+      {
+        assert(*it != NULL);
+        (*it)->Close();
+      }
+    }
+  }
+
+
+  IndexConnectionsPool::Accessor::Accessor(IndexConnectionsPool& pool) :
+    lock_(pool.connectionsMutex_),
+    pool_(pool),
+    manager_(NULL)
+  {
+    for (;;)
+    {
+      std::unique_ptr<Orthanc::IDynamicObject> manager(pool.availableConnections_.Dequeue(100));
+      if (manager.get() != NULL)
+      {
+        manager_ = &dynamic_cast<ManagerReference&>(*manager).GetManager();
+        return;
+      }
+    }
+  }
+
+  
+  IndexConnectionsPool::Accessor::~Accessor()
+  {
+    assert(manager_ != NULL);
+    pool_.availableConnections_.Enqueue(new ManagerReference(*manager_));
+  }
+
+  
+  IndexBackend& IndexConnectionsPool::Accessor::GetBackend() const
+  {
+    return *pool_.backend_;
+  }
+
+  
+  DatabaseManager& IndexConnectionsPool::Accessor::GetManager() const
+  {
+    assert(manager_ != NULL);
+    return *manager_;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Plugins/IndexConnectionsPool.h	Mon Apr 03 17:12:08 2023 +0200
@@ -0,0 +1,77 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2022 Osimis S.A., Belgium
+ * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IndexBackend.h"
+
+#include <MultiThreading/SharedMessageQueue.h>
+
+#include <list>
+
+namespace OrthancDatabases
+{
+  class IndexConnectionsPool : public boost::noncopyable
+  {
+  private:
+    class ManagerReference;
+
+    std::unique_ptr<IndexBackend>  backend_;
+    OrthancPluginContext*          context_;
+    boost::shared_mutex            connectionsMutex_;
+    size_t                         countConnections_;
+    std::list<DatabaseManager*>    connections_;
+    Orthanc::SharedMessageQueue    availableConnections_;
+
+  public:
+    IndexConnectionsPool(IndexBackend* backend /* takes ownership */,
+                         size_t countConnections);
+
+    ~IndexConnectionsPool();
+
+    OrthancPluginContext* GetContext() const
+    {
+      return context_;
+    }
+
+    void OpenConnections();
+
+    void CloseConnections();
+
+    class Accessor : public boost::noncopyable
+    {
+    private:
+      boost::shared_lock<boost::shared_mutex>  lock_;
+      IndexConnectionsPool&                    pool_;
+      DatabaseManager*                         manager_;
+      
+    public:
+      Accessor(IndexConnectionsPool& pool);
+
+      ~Accessor();
+
+      IndexBackend& GetBackend() const;
+
+      DatabaseManager& GetManager() const;
+    };
+  };
+}
--- a/Framework/Plugins/IndexUnitTests.h	Tue Mar 14 09:08:45 2023 +0100
+++ b/Framework/Plugins/IndexUnitTests.h	Mon Apr 03 17:12:08 2023 +0200
@@ -510,20 +510,19 @@
   ASSERT_EQ(0u, ci.size());
 
 
-  OrthancPluginExportedResource exp;
-  exp.seq = -1;
-  exp.resourceType = OrthancPluginResourceType_Study;
-  exp.publicId = "id";
-  exp.modality = "remote";
-  exp.date = "date";
-  exp.patientId = "patient";
-  exp.studyInstanceUid = "study";
-  exp.seriesInstanceUid = "series";
-  exp.sopInstanceUid = "instance";
-  db.LogExportedResource(*manager, exp);
+  db.LogExportedResource(*manager, OrthancPluginResourceType_Study, "id", "remote", "date",
+                         "patient", "study", "series", "instance");
 
   expectedExported.reset(new OrthancPluginExportedResource());
-  *expectedExported = exp;
+  expectedExported->seq = -1;
+  expectedExported->resourceType = OrthancPluginResourceType_Study;
+  expectedExported->publicId = "id";
+  expectedExported->modality = "remote";
+  expectedExported->date = "date";
+  expectedExported->patientId = "patient";
+  expectedExported->studyInstanceUid = "study";
+  expectedExported->seriesInstanceUid = "series";
+  expectedExported->sopInstanceUid = "instance";
 
   bool done;
   db.GetExportedResources(*output, done, *manager, 0, 10);
--- a/MySQL/CMakeLists.txt	Tue Mar 14 09:08:45 2023 +0100
+++ b/MySQL/CMakeLists.txt	Mon Apr 03 17:12:08 2023 +0200
@@ -24,14 +24,14 @@
 set(ORTHANC_PLUGIN_VERSION "mainline")
 
 set(ORTHANC_OPTIMAL_VERSION_MAJOR    1)
-set(ORTHANC_OPTIMAL_VERSION_MINOR    9)
-set(ORTHANC_OPTIMAL_VERSION_REVISION 2)
+set(ORTHANC_OPTIMAL_VERSION_MINOR    12)
+set(ORTHANC_OPTIMAL_VERSION_REVISION 0)
 
 if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline")
   set(ORTHANC_FRAMEWORK_VERSION "mainline")
   set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
 else()
-  set(ORTHANC_FRAMEWORK_VERSION "1.9.6")
+  set(ORTHANC_FRAMEWORK_VERSION "1.12.0")
   set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
 endif()
 
@@ -81,6 +81,24 @@
   MYSQL_CREATE_INSTANCE        ${CMAKE_SOURCE_DIR}/Plugins/CreateInstance.sql
   )
 
+if (EXISTS ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto)
+  add_custom_command(
+    COMMAND
+    ${PROTOC_EXECUTABLE} ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto --cpp_out=${AUTOGENERATED_DIR} -I${ORTHANC_SDK_ROOT}/orthanc/
+    DEPENDS
+    ProtobufCompiler
+    ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto
+    OUTPUT
+    ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc
+    ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.h
+    )
+
+  list(APPEND AUTOGENERATED_SOURCES
+    ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc
+    )
+endif()
+
+
 add_custom_target(
   AutogeneratedTarget
   DEPENDS 
--- a/MySQL/NEWS	Tue Mar 14 09:08:45 2023 +0100
+++ b/MySQL/NEWS	Mon Apr 03 17:12:08 2023 +0200
@@ -1,6 +1,8 @@
 Pending changes in the mainline
 ===============================
 
+* Compatibility with Orthanc SDK 1.12.0 (communications between the
+  Orthanc core and the database plugin using Google Protocol Buffers)
 * Upgraded dependencies for static builds (notably on Windows and LSB):
   - openssl 3.0.1
 
--- a/MySQL/Plugins/IndexPlugin.cpp	Tue Mar 14 09:08:45 2023 +0100
+++ b/MySQL/Plugins/IndexPlugin.cpp	Mon Apr 03 17:12:08 2023 +0200
@@ -28,11 +28,15 @@
 #include <Logging.h>
 #include <Toolbox.h>
 
+#include <google/protobuf/any.h>
+
 
 extern "C"
 {
   ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
   {
+    GOOGLE_PROTOBUF_VERIFY_VERSION;
+
     if (!OrthancDatabases::InitializePlugin(context, "MySQL", true))
     {
       return -1;
@@ -93,6 +97,7 @@
     OrthancDatabases::MySQLDatabase::GlobalFinalization();
     Orthanc::HttpClient::GlobalFinalize();
     Orthanc::Toolbox::FinalizeOpenSsl();
+    google::protobuf::ShutdownProtobufLibrary();
   }
 
 
--- a/PostgreSQL/CMakeLists.txt	Tue Mar 14 09:08:45 2023 +0100
+++ b/PostgreSQL/CMakeLists.txt	Mon Apr 03 17:12:08 2023 +0200
@@ -24,14 +24,14 @@
 set(ORTHANC_PLUGIN_VERSION "mainline")
 
 set(ORTHANC_OPTIMAL_VERSION_MAJOR    1)
-set(ORTHANC_OPTIMAL_VERSION_MINOR    9)
-set(ORTHANC_OPTIMAL_VERSION_REVISION 2)
+set(ORTHANC_OPTIMAL_VERSION_MINOR    12)
+set(ORTHANC_OPTIMAL_VERSION_REVISION 0)
 
 if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline")
   set(ORTHANC_FRAMEWORK_VERSION "mainline")
   set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
 else()
-  set(ORTHANC_FRAMEWORK_VERSION "1.9.6")
+  set(ORTHANC_FRAMEWORK_VERSION "1.12.0")
   set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
 endif()
 
@@ -84,6 +84,25 @@
   POSTGRESQL_GET_LAST_CHANGE_INDEX  ${CMAKE_SOURCE_DIR}/Plugins/GetLastChangeIndex.sql
   )
 
+
+if (EXISTS ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto)
+  add_custom_command(
+    COMMAND
+    ${PROTOC_EXECUTABLE} ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto --cpp_out=${AUTOGENERATED_DIR} -I${ORTHANC_SDK_ROOT}/orthanc/
+    DEPENDS
+    ProtobufCompiler
+    ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto
+    OUTPUT
+    ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc
+    ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.h
+    )
+
+  list(APPEND AUTOGENERATED_SOURCES
+    ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc
+    )
+endif()
+
+
 add_custom_target(
   AutogeneratedTarget
   DEPENDS 
--- a/PostgreSQL/NEWS	Tue Mar 14 09:08:45 2023 +0100
+++ b/PostgreSQL/NEWS	Mon Apr 03 17:12:08 2023 +0200
@@ -1,6 +1,8 @@
 Pending changes in the mainline
 ===============================
 
+* Compatibility with Orthanc SDK 1.12.0 (communications between the
+  Orthanc core and the database plugin using Google Protocol Buffers)
 * Upgraded dependencies for static builds (notably on Windows and LSB):
   - openssl 3.0.1
 
--- a/PostgreSQL/Plugins/IndexPlugin.cpp	Tue Mar 14 09:08:45 2023 +0100
+++ b/PostgreSQL/Plugins/IndexPlugin.cpp	Mon Apr 03 17:12:08 2023 +0200
@@ -26,11 +26,15 @@
 #include <Logging.h>
 #include <Toolbox.h>
 
+#include <google/protobuf/any.h>
+
 
 extern "C"
 {
   ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
   {
+    GOOGLE_PROTOBUF_VERIFY_VERSION;
+
     if (!OrthancDatabases::InitializePlugin(context, "PostgreSQL", true))
     {
       return -1;
@@ -87,6 +91,7 @@
     LOG(WARNING) << "PostgreSQL index is finalizing";
     OrthancDatabases::IndexBackend::Finalize();
     Orthanc::Toolbox::FinalizeOpenSsl();
+    google::protobuf::ShutdownProtobufLibrary();
   }
 
 
--- a/Resources/CMake/DatabasesFrameworkParameters.cmake	Tue Mar 14 09:08:45 2023 +0100
+++ b/Resources/CMake/DatabasesFrameworkParameters.cmake	Mon Apr 03 17:12:08 2023 +0200
@@ -52,3 +52,7 @@
 set(ENABLE_ODBC_BACKEND OFF)
 set(ENABLE_POSTGRESQL_BACKEND OFF)
 set(ENABLE_SQLITE_BACKEND OFF)
+
+# Interfacing with Orthanc 1.12.0
+set(ENABLE_PROTOBUF ON)
+set(ENABLE_PROTOBUF_COMPILER ON)
--- a/Resources/CMake/DatabasesPluginConfiguration.cmake	Tue Mar 14 09:08:45 2023 +0100
+++ b/Resources/CMake/DatabasesPluginConfiguration.cmake	Mon Apr 03 17:12:08 2023 +0200
@@ -23,35 +23,47 @@
 include(${CMAKE_CURRENT_LIST_DIR}/../Orthanc/CMake/AutoGeneratedCode.cmake)
 include(${CMAKE_CURRENT_LIST_DIR}/../Orthanc/Plugins/OrthancPluginsExports.cmake)
 
-
 if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK)
   if (ORTHANC_SDK_VERSION STREQUAL "0.9.5")
-    include_directories(${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-0.9.5)
+    set(ORTHANC_SDK_ROOT ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-0.9.5)
   elseif (ORTHANC_SDK_VERSION STREQUAL "1.4.0")
-    include_directories(${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.4.0)
+    set(ORTHANC_SDK_ROOT ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.4.0)
   elseif (ORTHANC_SDK_VERSION STREQUAL "1.5.2")
-    include_directories(${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.5.2)
+    set(ORTHANC_SDK_ROOT ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.5.2)
   elseif (ORTHANC_SDK_VERSION STREQUAL "1.5.4")
-    include_directories(${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.5.4)
+    set(ORTHANC_SDK_ROOT ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.5.4)
   elseif (ORTHANC_SDK_VERSION STREQUAL "1.9.2")
-    include_directories(${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.9.2)
+    set(ORTHANC_SDK_ROOT ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.9.2)
+  elseif (ORTHANC_SDK_VERSION STREQUAL "1.12.0")
+    set(ORTHANC_SDK_ROOT ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Sdk-1.12.0)
   elseif (ORTHANC_SDK_VERSION STREQUAL "framework")
     set(tmp ${ORTHANC_FRAMEWORK_ROOT}/../../OrthancServer/Plugins/Include/)
     message(${tmp})
     if (NOT EXISTS ${tmp}/orthanc/OrthancCDatabasePlugin.h)
       message(FATAL_ERROR "Your copy of the Orthanc framework doesn't contain the Orthanc plugin SDK")
     endif()    
-    include_directories(${tmp})
+    set(ORTHANC_SDK_ROOT ${tmp})
   else()
     message(FATAL_ERROR "Unsupported version of the Orthanc plugin SDK: ${ORTHANC_SDK_VERSION}")
   endif()
 else ()
-  CHECK_INCLUDE_FILE_CXX(orthanc/OrthancCDatabasePlugin.h HAVE_ORTHANC_H)
+  find_path(ORTHANC_SDK_ROOT orthanc/OrthancCDatabasePlugin.h
+    /usr/include
+    /usr/local/include
+    )
+
+  if (NOT ORTHANC_SDK_ROOT)
+    message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK")
+  endif()
+  
+  check_include_file(${ORTHANC_SDK_ROOT}/orthanc/OrthancCDatabasePlugin.h HAVE_ORTHANC_H)
   if (NOT HAVE_ORTHANC_H)
     message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK")
   endif()
 endif()
 
+include_directories(${ORTHANC_SDK_ROOT})
+
 
 if (NOT DEFINED ORTHANC_OPTIMAL_VERSION_MAJOR)
   message(FATAL_ERROR "ORTHANC_OPTIMAL_VERSION_MAJOR is not defined")
@@ -80,7 +92,9 @@
   ${ORTHANC_CORE_SOURCES}
   ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/DatabaseBackendAdapterV2.cpp
   ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/DatabaseBackendAdapterV3.cpp
+  ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/DatabaseBackendAdapterV4.cpp
   ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/IndexBackend.cpp
+  ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/IndexConnectionsPool.cpp
   ${ORTHANC_DATABASES_ROOT}/Framework/Plugins/StorageBackend.cpp
   ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Databases/DatabaseConstraint.cpp
   ${ORTHANC_DATABASES_ROOT}/Resources/Orthanc/Databases/ISqlLookupFormatter.cpp
--- a/Resources/CMake/DatabasesPluginParameters.cmake	Tue Mar 14 09:08:45 2023 +0100
+++ b/Resources/CMake/DatabasesPluginParameters.cmake	Mon Apr 03 17:12:08 2023 +0200
@@ -26,7 +26,7 @@
 
 # Advanced parameters to fine-tune linking against system libraries
 set(USE_SYSTEM_ORTHANC_SDK ON CACHE BOOL "Use the system version of the Orthanc plugin SDK")
-set(ORTHANC_SDK_VERSION "1.9.2" CACHE STRING "Version of the Orthanc plugin SDK to use, if not using the system version (can be \"0.9.5\", \"1.4.0\", \"1.5.2\", \"1.5.4\", \"1.9.2\" or \"framework\")")
+set(ORTHANC_SDK_VERSION "1.12.0" CACHE STRING "Version of the Orthanc plugin SDK to use, if not using the system version (can be \"0.9.5\", \"1.4.0\", \"1.5.2\", \"1.5.4\", \"1.9.2\", \"1.12.0\" or \"framework\")")
 
 include(${CMAKE_CURRENT_LIST_DIR}/DatabasesFrameworkParameters.cmake)
 
--- a/SQLite/CMakeLists.txt	Tue Mar 14 09:08:45 2023 +0100
+++ b/SQLite/CMakeLists.txt	Mon Apr 03 17:12:08 2023 +0200
@@ -24,14 +24,14 @@
 set(ORTHANC_PLUGIN_VERSION "mainline")
 
 set(ORTHANC_OPTIMAL_VERSION_MAJOR    1)
-set(ORTHANC_OPTIMAL_VERSION_MINOR    9)
-set(ORTHANC_OPTIMAL_VERSION_REVISION 2)
+set(ORTHANC_OPTIMAL_VERSION_MINOR    12)
+set(ORTHANC_OPTIMAL_VERSION_REVISION 0)
 
 if (ORTHANC_PLUGIN_VERSION STREQUAL "mainline")
   set(ORTHANC_FRAMEWORK_VERSION "mainline")
   set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
 else()
-  set(ORTHANC_FRAMEWORK_VERSION "1.9.6")
+  set(ORTHANC_FRAMEWORK_VERSION "1.12.0")
   set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
 endif()
 
@@ -45,6 +45,23 @@
   SQLITE_PREPARE_INDEX ${CMAKE_SOURCE_DIR}/Plugins/PrepareIndex.sql
   )
 
+if (EXISTS ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto)
+  add_custom_command(
+    COMMAND
+    ${PROTOC_EXECUTABLE} ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto --cpp_out=${AUTOGENERATED_DIR} -I${ORTHANC_SDK_ROOT}/orthanc/
+    DEPENDS
+    ProtobufCompiler
+    ${ORTHANC_SDK_ROOT}/orthanc/OrthancDatabasePlugin.proto
+    OUTPUT
+    ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc
+    ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.h
+    )
+
+  list(APPEND AUTOGENERATED_SOURCES
+    ${AUTOGENERATED_DIR}/OrthancDatabasePlugin.pb.cc
+    )
+endif()
+
 add_custom_target(
   AutogeneratedTarget
   DEPENDS 
--- a/SQLite/Plugins/IndexPlugin.cpp	Tue Mar 14 09:08:45 2023 +0100
+++ b/SQLite/Plugins/IndexPlugin.cpp	Mon Apr 03 17:12:08 2023 +0200
@@ -25,11 +25,15 @@
 
 #include <Logging.h>
 
+#include <google/protobuf/any.h>
+
 
 extern "C"
 {
   ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
   {
+    GOOGLE_PROTOBUF_VERIFY_VERSION;
+
     if (!OrthancDatabases::InitializePlugin(context, "SQLite", true))
     {
       return -1;
@@ -84,6 +88,7 @@
   {
     LOG(WARNING) << "SQLite index is finalizing";
     OrthancDatabases::IndexBackend::Finalize();
+    google::protobuf::ShutdownProtobufLibrary();
   }