changeset 207:d9ef3f16e6a2

wrapping transactions in API v3
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 19 Mar 2021 15:11:45 +0100
parents 6dcf57074dd4
children 696bc0c9fddb
files Framework/Common/DatabaseManager.cpp Framework/Common/DatabaseManager.h Framework/Common/DatabasesEnumerations.h Framework/Plugins/DatabaseBackendAdapterV2.cpp Framework/Plugins/DatabaseBackendAdapterV2.h Framework/Plugins/IDatabaseBackend.h Framework/Plugins/IndexBackend.h Framework/Plugins/IndexUnitTests.h Framework/Plugins/StorageBackend.cpp MySQL/Plugins/IndexPlugin.cpp MySQL/UnitTests/UnitTestsMain.cpp PostgreSQL/Plugins/IndexPlugin.cpp PostgreSQL/UnitTests/PostgreSQLTests.cpp SQLite/Plugins/IndexPlugin.cpp
diffstat 14 files changed, 470 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Common/DatabaseManager.cpp	Fri Mar 19 10:11:17 2021 +0100
+++ b/Framework/Common/DatabaseManager.cpp	Fri Mar 19 15:11:45 2021 +0100
@@ -212,8 +212,10 @@
   }
 
   
-  void DatabaseManager::StartTransaction()
+  void DatabaseManager::StartTransaction(TransactionType type)
   {
+    // TODO - Deal with TransactionType
+    
     boost::recursive_mutex::scoped_lock lock(mutex_);
 
     try
@@ -284,13 +286,14 @@
   }
 
 
-  DatabaseManager::Transaction::Transaction(DatabaseManager& manager) :
+  DatabaseManager::Transaction::Transaction(DatabaseManager& manager,
+                                            TransactionType type) :
     lock_(manager.mutex_),
     manager_(manager),
     database_(manager.GetDatabase()),
     committed_(false)
   {
-    manager_.StartTransaction();
+    manager_.StartTransaction(type);
   }
 
 
--- a/Framework/Common/DatabaseManager.h	Fri Mar 19 10:11:17 2021 +0100
+++ b/Framework/Common/DatabaseManager.h	Fri Mar 19 15:11:45 2021 +0100
@@ -77,7 +77,7 @@
 
     void Close();
     
-    void StartTransaction();
+    void StartTransaction(TransactionType type);
 
     void CommitTransaction();
     
@@ -94,7 +94,8 @@
       bool                                 committed_;
 
     public:
-      explicit Transaction(DatabaseManager& manager);
+      explicit Transaction(DatabaseManager& manager,
+                           TransactionType type);
 
       ~Transaction();
 
--- a/Framework/Common/DatabasesEnumerations.h	Fri Mar 19 10:11:17 2021 +0100
+++ b/Framework/Common/DatabasesEnumerations.h	Fri Mar 19 15:11:45 2021 +0100
@@ -39,4 +39,10 @@
     Dialect_PostgreSQL,
     Dialect_SQLite
   };
+
+  enum TransactionType
+  {
+    TransactionType_ReadOnly,
+    TransactionType_ReadWrite
+  };
 }
--- a/Framework/Plugins/DatabaseBackendAdapterV2.cpp	Fri Mar 19 10:11:17 2021 +0100
+++ b/Framework/Plugins/DatabaseBackendAdapterV2.cpp	Fri Mar 19 15:11:45 2021 +0100
@@ -1109,7 +1109,7 @@
 
     try
     {
-      backend->StartTransaction();
+      backend->StartTransaction(TransactionType_ReadWrite);
       return OrthancPluginErrorCode_Success;
     }
     ORTHANC_PLUGINS_DATABASE_CATCH;
@@ -1426,8 +1426,7 @@
   }
 
 
-  void DatabaseBackendAdapterV2::Register(OrthancPluginContext* context,
-                                          IDatabaseBackend& backend)
+  void DatabaseBackendAdapterV2::Register(IDatabaseBackend& backend)
   {
     OrthancPluginDatabaseBackend  params;
     memset(&params, 0, sizeof(params));
@@ -1520,6 +1519,8 @@
 #  endif
 #endif
 
+    OrthancPluginContext* context = backend.GetContext();
+    
     if (performanceWarning)
     {
       char info[1024];
@@ -1539,7 +1540,7 @@
 
     OrthancPluginDatabaseContext* database =
       OrthancPluginRegisterDatabaseBackendV2(context, &params, &extensions, &backend);
-    if (!context)
+    if (database == NULL)
     {
       throw std::runtime_error("Unable to register the database backend");
     }
--- a/Framework/Plugins/DatabaseBackendAdapterV2.h	Fri Mar 19 10:11:17 2021 +0100
+++ b/Framework/Plugins/DatabaseBackendAdapterV2.h	Fri Mar 19 15:11:45 2021 +0100
@@ -78,7 +78,6 @@
      * @param backend Your custom database engine.
      **/
 
-    static void Register(OrthancPluginContext* context,
-                         IDatabaseBackend& backend);
+    static void Register(IDatabaseBackend& backend);
   };
 }
--- a/Framework/Plugins/IDatabaseBackend.h	Fri Mar 19 10:11:17 2021 +0100
+++ b/Framework/Plugins/IDatabaseBackend.h	Fri Mar 19 15:11:45 2021 +0100
@@ -23,6 +23,7 @@
 #pragma once
 
 #include "IDatabaseBackendOutput.h"
+#include "../Common/DatabasesEnumerations.h"
 
 #include <list>
 
@@ -189,7 +190,7 @@
     virtual void SetProtectedPatient(int64_t internalId, 
                                      bool isProtected) = 0;
 
-    virtual void StartTransaction() = 0;
+    virtual void StartTransaction(TransactionType type) = 0;
 
     virtual void RollbackTransaction() = 0;
 
--- a/Framework/Plugins/IndexBackend.h	Fri Mar 19 10:11:17 2021 +0100
+++ b/Framework/Plugins/IndexBackend.h	Fri Mar 19 15:11:45 2021 +0100
@@ -242,9 +242,9 @@
     virtual void SetProtectedPatient(int64_t internalId, 
                                      bool isProtected) ORTHANC_OVERRIDE;
     
-    virtual void StartTransaction() ORTHANC_OVERRIDE
+    virtual void StartTransaction(TransactionType type) ORTHANC_OVERRIDE
     {
-      manager_.StartTransaction();
+      manager_.StartTransaction(type);
     }
 
     
--- a/Framework/Plugins/IndexUnitTests.h	Fri Mar 19 10:11:17 2021 +0100
+++ b/Framework/Plugins/IndexUnitTests.h	Fri Mar 19 15:11:45 2021 +0100
@@ -424,7 +424,7 @@
     // A transaction is needed here for MySQL, as it was not possible
     // to implement recursive deletion of resources using pure SQL
     // statements
-    db.StartTransaction();    
+    db.StartTransaction(TransactionType_ReadWrite);    
     db.DeleteResource(*output, c);
     db.CommitTransaction();
   }
--- a/Framework/Plugins/StorageBackend.cpp	Fri Mar 19 10:11:17 2021 +0100
+++ b/Framework/Plugins/StorageBackend.cpp	Fri Mar 19 15:11:45 2021 +0100
@@ -161,7 +161,7 @@
   {
     try
     {
-      DatabaseManager::Transaction transaction(backend_->GetManager());
+      DatabaseManager::Transaction transaction(backend_->GetManager(), TransactionType_ReadWrite);
       backend_->Create(transaction, uuid, content, static_cast<size_t>(size), type);
       transaction.Commit();
       return OrthancPluginErrorCode_Success;
@@ -180,7 +180,7 @@
       StorageAreaBuffer buffer(context_);
 
       {
-        DatabaseManager::Transaction transaction(backend_->GetManager());
+        DatabaseManager::Transaction transaction(backend_->GetManager(), TransactionType_ReadOnly);
         backend_->Read(buffer, transaction, uuid, type);
         transaction.Commit();
       }
@@ -222,7 +222,7 @@
   {
     try
     {
-      DatabaseManager::Transaction transaction(backend_->GetManager());
+      DatabaseManager::Transaction transaction(backend_->GetManager(), TransactionType_ReadWrite);
       backend_->Remove(transaction, uuid, type);
       transaction.Commit();
       return OrthancPluginErrorCode_Success;
--- a/MySQL/Plugins/IndexPlugin.cpp	Fri Mar 19 10:11:17 2021 +0100
+++ b/MySQL/Plugins/IndexPlugin.cpp	Fri Mar 19 15:11:45 2021 +0100
@@ -71,7 +71,7 @@
       backend_.reset(new OrthancDatabases::MySQLIndex(context, parameters));
 
       /* Register the MySQL index into Orthanc */
-      OrthancDatabases::DatabaseBackendAdapterV2::Register(context, *backend_);
+      OrthancDatabases::DatabaseBackendAdapterV2::Register(*backend_);
     }
     catch (Orthanc::OrthancException& e)
     {
--- a/MySQL/UnitTests/UnitTestsMain.cpp	Fri Mar 19 10:11:17 2021 +0100
+++ b/MySQL/UnitTests/UnitTestsMain.cpp	Fri Mar 19 15:11:45 2021 +0100
@@ -150,7 +150,7 @@
   storageArea.SetClearAll(true);
 
   {
-    OrthancDatabases::DatabaseManager::Transaction transaction(storageArea.GetManager());
+    OrthancDatabases::DatabaseManager::Transaction transaction(storageArea.GetManager(), OrthancDatabases::TransactionType_ReadWrite);
     OrthancDatabases::MySQLDatabase& db = 
       dynamic_cast<OrthancDatabases::MySQLDatabase&>(transaction.GetDatabase());
 
--- a/PostgreSQL/Plugins/IndexPlugin.cpp	Fri Mar 19 10:11:17 2021 +0100
+++ b/PostgreSQL/Plugins/IndexPlugin.cpp	Fri Mar 19 15:11:45 2021 +0100
@@ -65,7 +65,7 @@
       backend_.reset(new OrthancDatabases::PostgreSQLIndex(context, parameters));
 
       /* Register the PostgreSQL index into Orthanc */
-      OrthancDatabases::DatabaseBackendAdapterV2::Register(context, *backend_);
+      OrthancDatabases::DatabaseBackendAdapterV2::Register(*backend_);
     }
     catch (Orthanc::OrthancException& e)
     {
--- a/PostgreSQL/UnitTests/PostgreSQLTests.cpp	Fri Mar 19 10:11:17 2021 +0100
+++ b/PostgreSQL/UnitTests/PostgreSQLTests.cpp	Fri Mar 19 15:11:45 2021 +0100
@@ -343,7 +343,7 @@
   storageArea.SetClearAll(true);
 
   {
-    DatabaseManager::Transaction transaction(storageArea.GetManager());
+    DatabaseManager::Transaction transaction(storageArea.GetManager(), TransactionType_ReadWrite);
     PostgreSQLDatabase& db = 
       dynamic_cast<PostgreSQLDatabase&>(transaction.GetDatabase());
 
--- a/SQLite/Plugins/IndexPlugin.cpp	Fri Mar 19 10:11:17 2021 +0100
+++ b/SQLite/Plugins/IndexPlugin.cpp	Fri Mar 19 15:11:45 2021 +0100
@@ -32,24 +32,54 @@
 #if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)         // Macro introduced in Orthanc 1.3.1
 #  if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 10, 0)
 
+
+#define ORTHANC_PLUGINS_DATABASE_CATCH(context)                         \
+  catch (::Orthanc::OrthancException& e)                                \
+  {                                                                     \
+    return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());       \
+  }                                                                     \
+  catch (::std::runtime_error& e)                                       \
+  {                                                                     \
+    const std::string message = "Exception in database back-end: " + std::string(e.what()); \
+    OrthancPluginLogError(context, message.c_str());                    \
+    return OrthancPluginErrorCode_DatabasePlugin;                       \
+  }                                                                     \
+  catch (...)                                                           \
+  {                                                                     \
+    OrthancPluginLogError(context, "Native exception");                 \
+    return OrthancPluginErrorCode_DatabasePlugin;                       \
+  }
+
+
 namespace OrthancDatabases
 {
   class Output : public IDatabaseBackendOutput
   {
   private:
+    struct Metadata
+    {
+      int32_t      metadata;
+      const char*  value;
+    };
+    
     _OrthancPluginDatabaseAnswerType            answerType_;
-    std::list<std::string>                      strings_;
+    std::list<std::string>                      stringsStore_;
     
     std::vector<OrthancPluginAttachment>        attachments_;
     std::vector<OrthancPluginChange>            changes_;
     std::vector<OrthancPluginDicomTag>          tags_;
     std::vector<OrthancPluginExportedResource>  exported_;
     std::vector<OrthancPluginDatabaseEvent>     events_;
+    std::vector<int32_t>                        integers32_;
+    std::vector<int64_t>                        integers64_;
+    std::vector<OrthancPluginMatchingResource>  matches_;
+    std::vector<Metadata>                       metadata_;
+    std::vector<std::string>                    stringAnswers_;
     
     const char* StoreString(const std::string& s)
     {
-      strings_.push_back(s);
-      return strings_.back().c_str();
+      stringsStore_.push_back(s);
+      return stringsStore_.back().c_str();
     }
 
     void SetupAnswerType(_OrthancPluginDatabaseAnswerType type)
@@ -74,13 +104,18 @@
     void Clear()
     {
       answerType_ = _OrthancPluginDatabaseAnswerType_None;
-      strings_.clear();
+      stringsStore_.clear();
       
       attachments_.clear();
       changes_.clear();
       tags_.clear();
       exported_.clear();
       events_.clear();
+      integers32_.clear();
+      integers64_.clear();
+      matches_.clear();
+      metadata_.clear();
+      stringAnswers_.clear();
     }
 
 
@@ -113,6 +148,26 @@
           size = that.exported_.size();
           break;
         
+        case _OrthancPluginDatabaseAnswerType_Int32:
+          size = that.integers32_.size();
+          break;
+        
+        case _OrthancPluginDatabaseAnswerType_Int64:
+          size = that.integers64_.size();
+          break;
+        
+        case _OrthancPluginDatabaseAnswerType_MatchingResource:
+          size = that.matches_.size();
+          break;
+        
+        case _OrthancPluginDatabaseAnswerType_Metadata:
+          size = that.metadata_.size();
+          break;
+        
+        case _OrthancPluginDatabaseAnswerType_String:
+          size = that.stringAnswers_.size();
+          break;
+        
         default:
           return OrthancPluginErrorCode_InternalError;
       }
@@ -199,6 +254,99 @@
     }
 
 
+    static OrthancPluginErrorCode ReadAnswerInt32(OrthancPluginDatabaseTransaction* transaction,
+                                                  int32_t* target,
+                                                  uint32_t index)
+    {
+      const Output& that = *reinterpret_cast<const Output*>(transaction);
+
+      if (index < that.integers32_.size())
+      {
+        *target = that.integers32_[index];
+        return OrthancPluginErrorCode_Success;        
+      }
+      else
+      {
+        return OrthancPluginErrorCode_ParameterOutOfRange;        
+      }
+    }
+
+
+    static OrthancPluginErrorCode ReadAnswerInt64(OrthancPluginDatabaseTransaction* transaction,
+                                                  int64_t* target,
+                                                  uint32_t index)
+    {
+      const Output& that = *reinterpret_cast<const Output*>(transaction);
+
+      if (index < that.integers64_.size())
+      {
+        *target = that.integers64_[index];
+        return OrthancPluginErrorCode_Success;        
+      }
+      else
+      {
+        return OrthancPluginErrorCode_ParameterOutOfRange;        
+      }
+    }
+
+
+    static OrthancPluginErrorCode ReadAnswerMatchingResource(OrthancPluginDatabaseTransaction* transaction,
+                                                             OrthancPluginMatchingResource* target,
+                                                             uint32_t index)
+    {
+      const Output& that = *reinterpret_cast<const Output*>(transaction);
+
+      if (index < that.matches_.size())
+      {
+        *target = that.matches_[index];
+        return OrthancPluginErrorCode_Success;        
+      }
+      else
+      {
+        return OrthancPluginErrorCode_ParameterOutOfRange;        
+      }
+    }
+
+
+    static OrthancPluginErrorCode ReadAnswerMetadata(OrthancPluginDatabaseTransaction* transaction,
+                                                     int32_t* metadata,
+                                                     const char** value,
+                                                     uint32_t index)
+    {
+      const Output& that = *reinterpret_cast<const Output*>(transaction);
+
+      if (index < that.metadata_.size())
+      {
+        const Metadata& tmp = that.metadata_[index];
+        *metadata = tmp.metadata;
+        *value = tmp.value;
+        return OrthancPluginErrorCode_Success;        
+      }
+      else
+      {
+        return OrthancPluginErrorCode_ParameterOutOfRange;        
+      }
+    }
+
+
+    static OrthancPluginErrorCode ReadAnswerString(OrthancPluginDatabaseTransaction* transaction,
+                                                   const char** target,
+                                                   uint32_t index)
+    {
+      const Output& that = *reinterpret_cast<const Output*>(transaction);
+
+      if (index < that.stringAnswers_.size())
+      {
+        *target = that.stringAnswers_[index].c_str();
+        return OrthancPluginErrorCode_Success;        
+      }
+      else
+      {
+        return OrthancPluginErrorCode_ParameterOutOfRange;        
+      }
+    }
+
+
     static OrthancPluginErrorCode ReadEventsCount(OrthancPluginDatabaseTransaction* transaction,
                                                   uint32_t* target /* out */)
     {
@@ -358,14 +506,57 @@
     
     virtual void AnswerMatchingResource(const std::string& resourceId) ORTHANC_OVERRIDE
     {
+      SetupAnswerType(_OrthancPluginDatabaseAnswerType_MatchingResource);
 
+      OrthancPluginMatchingResource match;
+      match.resourceId = StoreString(resourceId);
+      match.someInstanceId = NULL;
+        
+      matches_.push_back(match);
     }
     
     
     virtual void AnswerMatchingResource(const std::string& resourceId,
                                         const std::string& someInstanceId) ORTHANC_OVERRIDE
     {
+      SetupAnswerType(_OrthancPluginDatabaseAnswerType_MatchingResource);
 
+      OrthancPluginMatchingResource match;
+      match.resourceId = StoreString(resourceId);
+      match.someInstanceId = StoreString(someInstanceId);
+        
+      matches_.push_back(match);
+    }
+
+    
+    void AnswerIntegers32(const std::list<int32_t>& values)
+    {
+      SetupAnswerType(_OrthancPluginDatabaseAnswerType_Int32);
+
+      integers32_.reserve(values.size());
+      std::copy(std::begin(values), std::end(values), std::back_inserter(integers32_));
+    }
+
+    
+    void AnswerIntegers64(const std::list<int64_t>& values)
+    {
+      SetupAnswerType(_OrthancPluginDatabaseAnswerType_Int64);
+
+      integers64_.reserve(values.size());
+      std::copy(std::begin(values), std::end(values), std::back_inserter(integers64_));
+    }
+
+
+    void AnswerMetadata(int32_t metadata,
+                        const std::string& value)
+    {
+      SetupAnswerType(_OrthancPluginDatabaseAnswerType_Metadata);
+
+      Metadata tmp;
+      tmp.metadata = metadata;
+      tmp.value = StoreString(value);
+
+      metadata_.push_back(tmp);
     }
   };
 
@@ -383,20 +574,235 @@
     }
   };
 
+
+  class Transaction : public boost::noncopyable
+  {
+  private:
+    IDatabaseBackend&        backend_;
+    std::unique_ptr<Output>  output_;
+
+  public:
+    Transaction(IDatabaseBackend& backend) :
+      backend_(backend),
+      output_(new Output)
+    {
+    }
+
+    IDatabaseBackend& GetBackend() const
+    {
+      return backend_;
+    }
+
+    Output& GetOutput() const
+    {
+      return *output_;
+    }
+
+    OrthancPluginContext* GetContext() const
+    {
+      return backend_.GetContext();
+    }
+  };
+
   
-  static void Register()
+  static OrthancPluginErrorCode Open(void* database)
+  {
+    IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(database);
+
+    try
+    {
+      backend->Open();
+      return OrthancPluginErrorCode_Success;
+    }
+    ORTHANC_PLUGINS_DATABASE_CATCH(backend->GetContext());
+  }
+
+  
+  static OrthancPluginErrorCode Close(void* database)
   {
-    OrthancPluginDatabaseBackendV3 backend;
-    memset(&backend, 0, sizeof(backend));
+    IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(database);
+
+    try
+    {
+      backend->Close();
+      return OrthancPluginErrorCode_Success;
+    }
+    ORTHANC_PLUGINS_DATABASE_CATCH(backend->GetContext());
+  }
+
+  
+  static OrthancPluginErrorCode DestructDatabase(void* database)
+  {
+    // Nothing to delete, as this plugin uses a singleton to store backend
+    if (database == NULL)
+    {
+      return OrthancPluginErrorCode_InternalError;
+    }
+    else
+    {
+      return OrthancPluginErrorCode_Success;
+    }
+  }
+
+  
+  static OrthancPluginErrorCode GetDatabaseVersion(void* database,
+                                                   uint32_t* version)
+  {
+    IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(database);
+      
+    try
+    {
+      *version = backend->GetDatabaseVersion();
+      return OrthancPluginErrorCode_Success;
+    }
+    ORTHANC_PLUGINS_DATABASE_CATCH(backend->GetContext());
+  }
+
+
+  static OrthancPluginErrorCode UpgradeDatabase(void* database,
+                                                OrthancPluginStorageArea* storageArea,
+                                                uint32_t  targetVersion)
+  {
+    IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(database);
+      
+    try
+    {
+      backend->UpgradeDatabase(targetVersion, storageArea);
+      return OrthancPluginErrorCode_Success;
+    }
+    ORTHANC_PLUGINS_DATABASE_CATCH(backend->GetContext());
+  }
+
 
-    backend.readAnswersCount = Output::ReadAnswersCount;
-    backend.readAnswerAttachment = Output::ReadAnswerAttachment;
-    backend.readAnswerChange = Output::ReadAnswerChange;
-    backend.readAnswerDicomTag = Output::ReadAnswerDicomTag;
-    backend.readAnswerExportedResource = Output::ReadAnswerExportedResource;
+  static OrthancPluginErrorCode StartTransaction(void* database,
+                                                 OrthancPluginDatabaseTransaction** target /* out */,
+                                                 OrthancPluginDatabaseTransactionType type)
+  {
+    IDatabaseBackend* backend = reinterpret_cast<IDatabaseBackend*>(database);
+      
+    try
+    {
+      std::unique_ptr<Transaction> transaction(new Transaction(*backend));
+      
+      switch (type)
+      {
+        case OrthancPluginDatabaseTransactionType_ReadOnly:
+          backend->StartTransaction(TransactionType_ReadOnly);
+          break;
+
+        case OrthancPluginDatabaseTransactionType_ReadWrite:
+          backend->StartTransaction(TransactionType_ReadWrite);
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+      }
+      
+      *target = reinterpret_cast<OrthancPluginDatabaseTransaction*>(transaction.release());
+      
+      return OrthancPluginErrorCode_Success;
+    }
+    ORTHANC_PLUGINS_DATABASE_CATCH(backend->GetContext());
+  }
+
+  
+  static OrthancPluginErrorCode DestructTransaction(OrthancPluginDatabaseTransaction* transaction)
+  {
+    if (transaction == NULL)
+    {
+      return OrthancPluginErrorCode_NullPointer;
+    }
+    else
+    {
+      delete reinterpret_cast<Output*>(transaction);
+      return OrthancPluginErrorCode_Success;
+    }
+  }
+
+  
+  static OrthancPluginErrorCode Rollback(OrthancPluginDatabaseTransaction* transaction)
+  {
+    Transaction* t = reinterpret_cast<Transaction*>(transaction);
+
+    try
+    {
+      t->GetBackend().RollbackTransaction();
+      return OrthancPluginErrorCode_Success;
+    }
+    ORTHANC_PLUGINS_DATABASE_CATCH(t->GetContext());
+  }
+
+  
+  static OrthancPluginErrorCode Commit(OrthancPluginDatabaseTransaction* transaction,
+                                       int64_t fileSizeDelta /* TODO - not used? */)
+  {
+    Transaction* t = reinterpret_cast<Transaction*>(transaction);
+
+    try
+    {
+      t->GetBackend().CommitTransaction();
+      return OrthancPluginErrorCode_Success;
+    }
+    ORTHANC_PLUGINS_DATABASE_CATCH(t->GetContext());
+  }
+  
+
+  static OrthancPluginErrorCode AddAttachment(OrthancPluginDatabaseTransaction* transaction,
+                                              int64_t id,
+                                              const OrthancPluginAttachment* attachment)
+  {
+    Transaction* t = reinterpret_cast<Transaction*>(transaction);
+
+    try
+    {
+      t->GetOutput().Clear();
+      t->GetBackend().AddAttachment(id, *attachment);
+      return OrthancPluginErrorCode_Success;
+    }
+    ORTHANC_PLUGINS_DATABASE_CATCH(t->GetContext());
+  }
+
+  
+
+  static void RegisterV3(IDatabaseBackend& database)
+  {
+    OrthancPluginDatabaseBackendV3 params;
+    memset(&params, 0, sizeof(params));
+
+    params.readAnswersCount = Output::ReadAnswersCount;
+    params.readAnswerAttachment = Output::ReadAnswerAttachment;
+    params.readAnswerChange = Output::ReadAnswerChange;
+    params.readAnswerDicomTag = Output::ReadAnswerDicomTag;
+    params.readAnswerExportedResource = Output::ReadAnswerExportedResource;
+    params.readAnswerInt32 = Output::ReadAnswerInt32;
+    params.readAnswerInt64 = Output::ReadAnswerInt64;
+    params.readAnswerMatchingResource = Output::ReadAnswerMatchingResource;
+    params.readAnswerMetadata = Output::ReadAnswerMetadata;
+    params.readAnswerString = Output::ReadAnswerString;
     
-    backend.readEventsCount = Output::ReadEventsCount;
-    backend.readEvent = Output::ReadEvent;
+    params.readEventsCount = Output::ReadEventsCount;
+    params.readEvent = Output::ReadEvent;
+
+    params.open = Open;
+    params.close = Close;
+    params.destructDatabase = DestructDatabase;
+    params.getDatabaseVersion = GetDatabaseVersion;
+    params.upgradeDatabase = UpgradeDatabase;
+    params.startTransaction = StartTransaction;
+    params.destructTransaction = DestructTransaction;
+    params.rollback = Rollback;
+    params.commit = Commit;
+
+    params.addAttachment = AddAttachment;
+
+    OrthancPluginContext* context = database.GetContext();
+ 
+    if (OrthancPluginRegisterDatabaseBackendV3(context, &params, sizeof(params), &database) != OrthancPluginErrorCode_Success)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Unable to register the database backend");
+    }
+
+    database.SetOutputFactory(new Factory);
   }
 }
 
@@ -442,7 +848,23 @@
       backend_.reset(new OrthancDatabases::SQLiteIndex(context, "index.db"));  // TODO parameter
 
       /* Register the SQLite index into Orthanc */
-      OrthancDatabases::DatabaseBackendAdapterV2::Register(context, *backend_);
+
+      bool hasLoadedV3 = false;
+      
+#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)         // Macro introduced in Orthanc 1.3.1
+#  if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 10, 0)
+      if (OrthancPluginCheckVersionAdvanced(context, 1, 10, 0) == 1)
+      {
+        RegisterV3(*backend_);
+        hasLoadedV3 = true;
+      }
+#  endif
+#endif
+
+      if (!hasLoadedV3)
+      {
+        OrthancDatabases::DatabaseBackendAdapterV2::Register(*backend_);
+      }
     }
     catch (Orthanc::OrthancException& e)
     {