changeset 5455:176bc05f85f4 pg-transactions

DB: new Capabilities class to manage future new methods from DB plugins + Added IncrementGlobalProperty
author Alain Mazy <am@osimis.io>
date Thu, 07 Dec 2023 12:04:11 +0100
parents 099d45f49fe1
children 06eb8bc9f024
files OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp OrthancServer/Plugins/Engine/OrthancPluginDatabase.h OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.h OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.h OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto OrthancServer/Sources/Database/BaseDatabaseWrapper.h OrthancServer/Sources/Database/IDatabaseWrapper.h OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp OrthancServer/Sources/Database/SQLiteDatabaseWrapper.h OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp OrthancServer/Sources/Database/StatelessDatabaseOperations.h OrthancServer/Sources/ServerIndex.cpp OrthancServer/Sources/main.cpp
diffstat 15 files changed, 266 insertions(+), 154 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp	Wed Dec 06 14:20:33 2023 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp	Thu Dec 07 12:04:11 2023 +0100
@@ -43,7 +43,7 @@
 namespace Orthanc
 {
   class OrthancPluginDatabase::Transaction :
-    public IDatabaseWrapper::ITransaction,
+    public BaseDatabaseWrapper::BaseTransaction,
     public Compatibility::ICreateInstance,
     public Compatibility::IGetChildrenMetadata,
     public Compatibility::ILookupResources,
@@ -243,6 +243,11 @@
       that_.activeTransaction_ = NULL;
     }
 
+    virtual const IDatabaseWrapper::Capabilities& GetDatabaseCapabilities() const ORTHANC_OVERRIDE
+    {
+      return that_.GetDatabaseCapabilities();
+    }
+
     IDatabaseListener& GetDatabaseListener() const
     {
       return listener_;
@@ -1472,7 +1477,8 @@
     payload_(payload),
     activeTransaction_(NULL),
     fastGetTotalSize_(false),
-    currentDiskSize_(0)
+    currentDiskSize_(0),
+    dbCapabilities_(false, false, false, false)
   {
     static const char* const MISSING = "  Missing extension in database index plugin: ";
     
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabase.h	Wed Dec 06 14:20:33 2023 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabase.h	Thu Dec 07 12:04:11 2023 +0100
@@ -25,7 +25,7 @@
 #if ORTHANC_ENABLE_PLUGINS == 1
 
 #include "../../../OrthancFramework/Sources/SharedLibrary.h"
-#include "../../Sources/Database/IDatabaseWrapper.h"
+#include "../../Sources/Database/BaseDatabaseWrapper.h"
 #include "../Include/orthanc/OrthancCDatabasePlugin.h"
 #include "PluginsErrorDictionary.h"
 
@@ -45,7 +45,7 @@
    * able to rollback the modifications. Read-only accesses didn't
    * start a transaction, as they were protected by the global mutex.
    **/
-  class OrthancPluginDatabase : public IDatabaseWrapper
+  class OrthancPluginDatabase : public BaseDatabaseWrapper
   {
   private:
     class Transaction;
@@ -65,6 +65,7 @@
     Transaction*                    activeTransaction_;
     bool                            fastGetTotalSize_;
     uint64_t                        currentDiskSize_;
+    IDatabaseWrapper::Capabilities  dbCapabilities_;
 
     OrthancPluginDatabaseContext* GetContext()
     {
@@ -94,11 +95,6 @@
     {
     }
 
-    virtual bool HasFlushToDisk() const ORTHANC_OVERRIDE
-    {
-      return false;
-    }
-
     virtual IDatabaseWrapper::ITransaction* StartTransaction(TransactionType type,
                                                              IDatabaseListener& listener)
       ORTHANC_OVERRIDE;
@@ -108,15 +104,11 @@
     virtual void Upgrade(unsigned int targetVersion,
                          IStorageArea& storageArea) ORTHANC_OVERRIDE;    
 
-    virtual bool HasRevisionsSupport() const ORTHANC_OVERRIDE
+    const IDatabaseWrapper::Capabilities& GetDatabaseCapabilities() const ORTHANC_OVERRIDE
     {
-      return false;  // No support for revisions in old API
+      return dbCapabilities_;
     }
 
-    virtual bool HasLabelsSupport() const ORTHANC_OVERRIDE
-    {
-      return false;
-    }
 
     void AnswerReceived(const _OrthancPluginDatabaseAnswer& answer);
   };
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp	Wed Dec 06 14:20:33 2023 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp	Thu Dec 07 12:04:11 2023 +0100
@@ -45,7 +45,7 @@
 
 namespace Orthanc
 {
-  class OrthancPluginDatabaseV3::Transaction : public IDatabaseWrapper::ITransaction
+  class OrthancPluginDatabaseV3::Transaction : public BaseDatabaseWrapper::BaseTransaction
   {
   private:
     OrthancPluginDatabaseV3&           that_;
@@ -278,6 +278,10 @@
       }
     }
     
+    virtual const IDatabaseWrapper::Capabilities& GetDatabaseCapabilities() const ORTHANC_OVERRIDE
+    {
+      return that_.GetDatabaseCapabilities();
+    }
 
     virtual void Rollback() ORTHANC_OVERRIDE
     {
@@ -1083,7 +1087,8 @@
     library_(library),
     errorDictionary_(errorDictionary),
     database_(database),
-    serverIdentifier_(serverIdentifier)
+    serverIdentifier_(serverIdentifier),
+    dbCapabilities_(false, false /* revision support is updated in open() */, false, false)
   {
     CLOG(INFO, PLUGINS) << "Identifier of this Orthanc server for the global properties "
                         << "of the custom database: \"" << serverIdentifier << "\"";
@@ -1190,6 +1195,11 @@
   void OrthancPluginDatabaseV3::Open()
   {
     CheckSuccess(backend_.open(database_));
+
+    // update the db capabilities
+    uint8_t hasRevisions;
+    CheckSuccess(backend_.hasRevisionsSupport(database_, &hasRevisions));
+    dbCapabilities_.hasRevisionsSupport_ = (hasRevisions != 0);
   }
 
 
@@ -1250,12 +1260,4 @@
     }
   }
 
-  
-  bool OrthancPluginDatabaseV3::HasRevisionsSupport() const
-  {
-    // WARNING: This method requires "Open()" to have been called
-    uint8_t hasRevisions;
-    CheckSuccess(backend_.hasRevisionsSupport(database_, &hasRevisions));
-    return (hasRevisions != 0);
-  }
 }
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.h	Wed Dec 06 14:20:33 2023 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.h	Thu Dec 07 12:04:11 2023 +0100
@@ -25,13 +25,13 @@
 #if ORTHANC_ENABLE_PLUGINS == 1
 
 #include "../../../OrthancFramework/Sources/SharedLibrary.h"
-#include "../../Sources/Database/IDatabaseWrapper.h"
+#include "../../Sources/Database/BaseDatabaseWrapper.h"
 #include "../Include/orthanc/OrthancCDatabasePlugin.h"
 #include "PluginsErrorDictionary.h"
 
 namespace Orthanc
 {
-  class OrthancPluginDatabaseV3 : public IDatabaseWrapper
+  class OrthancPluginDatabaseV3 : public BaseDatabaseWrapper
   {
   private:
     class Transaction;
@@ -41,6 +41,7 @@
     OrthancPluginDatabaseBackendV3  backend_;
     void*                           database_;
     std::string                     serverIdentifier_;
+    IDatabaseWrapper::Capabilities  dbCapabilities_;
 
     void CheckSuccess(OrthancPluginErrorCode code) const;
 
@@ -67,11 +68,6 @@
     {
     }
 
-    virtual bool HasFlushToDisk() const ORTHANC_OVERRIDE
-    {
-      return false;
-    }
-
     virtual IDatabaseWrapper::ITransaction* StartTransaction(TransactionType type,
                                                              IDatabaseListener& listener)
       ORTHANC_OVERRIDE;
@@ -81,12 +77,11 @@
     virtual void Upgrade(unsigned int targetVersion,
                          IStorageArea& storageArea) ORTHANC_OVERRIDE;    
 
-    virtual bool HasRevisionsSupport() const ORTHANC_OVERRIDE;
+    const IDatabaseWrapper::Capabilities& GetDatabaseCapabilities() const ORTHANC_OVERRIDE
+    {
+      return dbCapabilities_;
+    }
 
-    virtual bool HasLabelsSupport() const ORTHANC_OVERRIDE
-    {
-      return false;
-    }
   };
 }
 
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp	Wed Dec 06 14:20:33 2023 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp	Thu Dec 07 12:04:11 2023 +0100
@@ -229,7 +229,7 @@
                             bool isSingleResource,
                             int64_t resource)
     {
-      if (database_.HasLabelsSupport())
+      if (database_.GetDatabaseCapabilities().HasLabelsSupport())
       {
         DatabasePluginMessages::TransactionRequest request;
         request.mutable_list_labels()->set_single_resource(isSingleResource);
@@ -305,6 +305,10 @@
       }
     }
 
+    virtual const IDatabaseWrapper::Capabilities& GetDatabaseCapabilities() const ORTHANC_OVERRIDE
+    {
+      return database_.GetDatabaseCapabilities();
+    }
 
     void* GetTransactionObject()
     {
@@ -772,6 +776,21 @@
       }
     }
 
+
+    virtual int64_t IncrementGlobalProperty(GlobalProperty property,
+                                            int64_t increment,
+                                            bool shared) ORTHANC_OVERRIDE
+    {
+      DatabasePluginMessages::TransactionRequest request;
+      request.mutable_increment_global_property()->set_server_id(shared ? "" : database_.GetServerIdentifier());
+      request.mutable_increment_global_property()->set_property(property);
+      request.mutable_increment_global_property()->set_increment(increment);
+
+      DatabasePluginMessages::TransactionResponse response;
+      ExecuteTransaction(response, DatabasePluginMessages::OPERATION_INCREMENT_GLOBAL_PROPERTY, request);
+
+      return response.increment_global_property().new_value();
+    }
     
     virtual bool LookupMetadata(std::string& target,
                                 int64_t& revision,
@@ -948,7 +967,7 @@
                                       LabelsConstraint labelsConstraint,
                                       uint32_t limit) ORTHANC_OVERRIDE
     {
-      if (!database_.HasLabelsSupport() &&
+      if (!database_.GetDatabaseCapabilities().HasLabelsSupport() &&
           !labels.empty())
       {
         throw OrthancException(ErrorCode_InternalError);
@@ -1197,7 +1216,7 @@
     virtual void AddLabel(int64_t resource,
                           const std::string& label) ORTHANC_OVERRIDE
     {
-      if (database_.HasLabelsSupport())
+      if (database_.GetDatabaseCapabilities().HasLabelsSupport())
       {
         DatabasePluginMessages::TransactionRequest request;
         request.mutable_add_label()->set_id(resource);
@@ -1216,7 +1235,7 @@
     virtual void RemoveLabel(int64_t resource,
                              const std::string& label) ORTHANC_OVERRIDE
     {
-      if (database_.HasLabelsSupport())
+      if (database_.GetDatabaseCapabilities().HasLabelsSupport())
       {
         DatabasePluginMessages::TransactionRequest request;
         request.mutable_remove_label()->set_id(resource);
@@ -1256,9 +1275,7 @@
     serverIdentifier_(serverIdentifier),
     open_(false),
     databaseVersion_(0),
-    hasFlushToDisk_(false),
-    hasRevisionsSupport_(false),
-    hasLabelsSupport_(false)
+    dbCapabilities_(false, false, false, false) // updated in Open()
   {
     CLOG(INFO, PLUGINS) << "Identifier of this Orthanc server for the global properties "
                         << "of the custom database: \"" << serverIdentifier << "\"";
@@ -1325,10 +1342,13 @@
       DatabasePluginMessages::DatabaseRequest request;
       DatabasePluginMessages::DatabaseResponse response;
       ExecuteDatabase(response, *this, DatabasePluginMessages::OPERATION_GET_SYSTEM_INFORMATION, request);
-      databaseVersion_ = response.get_system_information().database_version();
-      hasFlushToDisk_ = response.get_system_information().supports_flush_to_disk();
-      hasRevisionsSupport_ = response.get_system_information().supports_revisions();
-      hasLabelsSupport_ = response.get_system_information().supports_labels();
+      
+      const ::Orthanc::DatabasePluginMessages::GetSystemInformation_Response& systemInfo = response.get_system_information();
+      databaseVersion_ = systemInfo.database_version();
+      dbCapabilities_.hasFlushToDisk_ = systemInfo.supports_flush_to_disk();
+      dbCapabilities_.hasRevisionsSupport_ = systemInfo.supports_revisions();
+      dbCapabilities_.hasLabelsSupport_ = systemInfo.supports_labels();
+      dbCapabilities_.hasAtomicIncrementGlobalProperty_ = systemInfo.supports_increment_global_property();
     }
 
     open_ = true;
@@ -1350,23 +1370,11 @@
   }
   
 
-  bool OrthancPluginDatabaseV4::HasFlushToDisk() const
-  {
-    if (!open_)
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      return hasFlushToDisk_;
-    }
-  }
-
 
   void OrthancPluginDatabaseV4::FlushToDisk()
   {
     if (!open_ ||
-        !hasFlushToDisk_)
+        !GetDatabaseCapabilities().HasFlushToDisk())
     {
       throw OrthancException(ErrorCode_BadSequenceOfCalls);
     }
@@ -1438,8 +1446,8 @@
     }
   }
 
-  
-  bool OrthancPluginDatabaseV4::HasRevisionsSupport() const
+
+  const IDatabaseWrapper::Capabilities& OrthancPluginDatabaseV4::GetDatabaseCapabilities() const
   {
     if (!open_)
     {
@@ -1447,20 +1455,9 @@
     }
     else
     {
-      return hasRevisionsSupport_;
+      return dbCapabilities_;
     }
   }
 
-  
-  bool OrthancPluginDatabaseV4::HasLabelsSupport() const
-  {
-    if (!open_)
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-    else
-    {
-      return hasLabelsSupport_;
-    }
-  }
+
 }
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.h	Wed Dec 06 14:20:33 2023 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.h	Thu Dec 07 12:04:11 2023 +0100
@@ -42,9 +42,7 @@
     std::string                             serverIdentifier_;
     bool                                    open_;
     unsigned int                            databaseVersion_;
-    bool                                    hasFlushToDisk_;
-    bool                                    hasRevisionsSupport_;
-    bool                                    hasLabelsSupport_;
+    IDatabaseWrapper::Capabilities          dbCapabilities_;
 
     void CheckSuccess(OrthancPluginErrorCode code) const;
 
@@ -82,8 +80,6 @@
 
     virtual void FlushToDisk() ORTHANC_OVERRIDE;
 
-    virtual bool HasFlushToDisk() const ORTHANC_OVERRIDE;
-
     virtual IDatabaseWrapper::ITransaction* StartTransaction(TransactionType type,
                                                              IDatabaseListener& listener)
       ORTHANC_OVERRIDE;
@@ -93,9 +89,7 @@
     virtual void Upgrade(unsigned int targetVersion,
                          IStorageArea& storageArea) ORTHANC_OVERRIDE;    
 
-    virtual bool HasRevisionsSupport() const ORTHANC_OVERRIDE;
-
-    virtual bool HasLabelsSupport() const ORTHANC_OVERRIDE;
+    virtual const IDatabaseWrapper::Capabilities& GetDatabaseCapabilities() const ORTHANC_OVERRIDE;
   };
 }
 
--- a/OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto	Wed Dec 06 14:20:33 2023 +0100
+++ b/OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto	Thu Dec 07 12:04:11 2023 +0100
@@ -136,6 +136,7 @@
     bool supports_flush_to_disk = 2;
     bool supports_revisions = 3;
     bool supports_labels = 4;
+    bool supports_increment_global_property = 5;
   }
 }
 
@@ -275,6 +276,7 @@
   OPERATION_ADD_LABEL = 45;        // New in Orthanc 1.12.0
   OPERATION_REMOVE_LABEL = 46;     // New in Orthanc 1.12.0
   OPERATION_LIST_LABELS = 47;      // New in Orthanc 1.12.0
+  OPERATION_INCREMENT_GLOBAL_PROPERTY = 48;      // New in Orthanc 1.12.X
 }
 
 message Rollback {
@@ -628,6 +630,17 @@
   }
 }
 
+message IncrementGlobalProperty {
+  message Request {
+    string server_id = 1;
+    int32 property = 2;
+    int64 increment = 3;
+  }
+  message Response {
+    int64 new_value = 1;
+  }
+}
+
 message ClearMainDicomTags {
   message Request {
     int64 id = 1;
@@ -834,6 +847,7 @@
   AddLabel.Request                        add_label = 145;
   RemoveLabel.Request                     remove_label = 146;
   ListLabels.Request                      list_labels = 147;
+  IncrementGlobalProperty.Request         increment_global_property = 148;
 }
 
 message TransactionResponse {
@@ -885,6 +899,7 @@
   AddLabel.Response                        add_label = 145;
   RemoveLabel.Response                     remove_label = 146;
   ListLabels.Response                      list_labels = 147;
+  IncrementGlobalProperty.Response         increment_global_property = 148;
 }
 
 enum RequestType {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/Database/BaseDatabaseWrapper.h	Thu Dec 07 12:04:11 2023 +0100
@@ -0,0 +1,48 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2023 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IDatabaseWrapper.h"
+#include "../../../OrthancFramework/Sources/OrthancException.h"
+
+namespace Orthanc
+{
+  /**
+   * This class provides a default "not implemented" implementation
+   * for all recent methods (1.12.X)
+   **/
+  class BaseDatabaseWrapper : public IDatabaseWrapper
+  {
+  public:
+    class BaseTransaction : public IDatabaseWrapper::ITransaction
+    {
+      virtual int64_t IncrementGlobalProperty(GlobalProperty property,
+                                              int64_t increment,
+                                              bool shared) ORTHANC_OVERRIDE
+      {
+        throw OrthancException(ErrorCode_NotImplemented);  // Not supported
+      }
+    };
+
+  };
+}
\ No newline at end of file
--- a/OrthancServer/Sources/Database/IDatabaseWrapper.h	Wed Dec 06 14:20:33 2023 +0100
+++ b/OrthancServer/Sources/Database/IDatabaseWrapper.h	Thu Dec 07 12:04:11 2023 +0100
@@ -39,10 +39,57 @@
   class DatabaseConstraint;
   class ResourcesContent;
 
+  class OrthancPluginDatabaseV3;
+  class OrthancPluginDatabaseV4;
   
   class IDatabaseWrapper : public boost::noncopyable
   {
   public:
+
+    struct Capabilities
+    {
+      friend OrthancPluginDatabaseV3;
+      friend OrthancPluginDatabaseV4;
+
+    protected:
+      bool hasFlushToDisk_;
+      bool hasRevisionsSupport_;
+      bool hasLabelsSupport_;
+      bool hasAtomicIncrementGlobalProperty_;
+
+    public:
+      Capabilities(bool hasFlushToDisk,
+                   bool hasRevisionsSupport,
+                   bool hasLabelsSupport,
+                   bool hasAtomicIncrementGlobalProperty)
+      : hasFlushToDisk_(hasFlushToDisk),
+        hasRevisionsSupport_(hasRevisionsSupport),
+        hasLabelsSupport_(hasLabelsSupport),
+        hasAtomicIncrementGlobalProperty_(hasAtomicIncrementGlobalProperty)
+      {
+      }
+
+      bool HasFlushToDisk() const
+      {
+        return hasFlushToDisk_;
+      }
+
+      bool HasRevisionsSupport() const
+      {
+        return hasRevisionsSupport_;
+      }
+
+      bool HasLabelsSupport() const
+      {
+        return hasLabelsSupport_;
+      }
+
+      bool HasAtomicIncrementGlobalProperty() const
+      {
+        return hasAtomicIncrementGlobalProperty_;
+      }
+    };
+
     struct CreateInstanceResult : public boost::noncopyable
     {
       bool     isNewPatient_;
@@ -257,6 +304,12 @@
 
       // List all the labels that are present in any resource
       virtual void ListAllLabels(std::set<std::string>& target) = 0;
+    
+      virtual const IDatabaseWrapper::Capabilities& GetDatabaseCapabilities() const = 0;
+
+      virtual int64_t IncrementGlobalProperty(GlobalProperty property,
+                                              int64_t increment,
+                                              bool shared) = 0;
     };
 
 
@@ -270,8 +323,6 @@
 
     virtual void FlushToDisk() = 0;
 
-    virtual bool HasFlushToDisk() const = 0;
-
     virtual ITransaction* StartTransaction(TransactionType type,
                                            IDatabaseListener& listener) = 0;
 
@@ -280,8 +331,6 @@
     virtual void Upgrade(unsigned int targetVersion,
                          IStorageArea& storageArea) = 0;
 
-    virtual bool HasRevisionsSupport() const = 0;
-
-    virtual bool HasLabelsSupport() const = 0;
+    virtual const IDatabaseWrapper::Capabilities& GetDatabaseCapabilities() const = 0;
   };
 }
--- a/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp	Wed Dec 06 14:20:33 2023 +0100
+++ b/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp	Thu Dec 07 12:04:11 2023 +0100
@@ -300,19 +300,27 @@
     boost::mutex::scoped_lock  lock_;
     IDatabaseListener&         listener_;
     SignalRemainingAncestor&   signalRemainingAncestor_;
+    const IDatabaseWrapper::Capabilities& dbCapabilities_;
 
   public:
     TransactionBase(boost::mutex& mutex,
                     SQLite::Connection& db,
                     IDatabaseListener& listener,
-                    SignalRemainingAncestor& signalRemainingAncestor) :
+                    SignalRemainingAncestor& signalRemainingAncestor,
+                    const IDatabaseWrapper::Capabilities& dbCapabilities) :
       UnitTestsTransaction(db),
       lock_(mutex),
       listener_(listener),
-      signalRemainingAncestor_(signalRemainingAncestor)
+      signalRemainingAncestor_(signalRemainingAncestor),
+      dbCapabilities_(dbCapabilities)
     {
     }
 
+    virtual const IDatabaseWrapper::Capabilities& GetDatabaseCapabilities() const ORTHANC_OVERRIDE
+    {
+      return dbCapabilities_;
+    }
+
     IDatabaseListener& GetListener() const
     {
       return listener_;
@@ -1137,6 +1145,7 @@
         target.insert(s.ColumnString(0));
       }
     }
+
   };
 
 
@@ -1234,7 +1243,7 @@
   public:
     ReadWriteTransaction(SQLiteDatabaseWrapper& that,
                          IDatabaseListener& listener) :
-      TransactionBase(that.mutex_, that.db_, listener, *that.signalRemainingAncestor_),
+      TransactionBase(that.mutex_, that.db_, listener, *that.signalRemainingAncestor_, that.GetDatabaseCapabilities()),
       that_(that),
       transaction_(new SQLite::Transaction(that_.db_))
     {
@@ -1288,7 +1297,7 @@
   public:
     ReadOnlyTransaction(SQLiteDatabaseWrapper& that,
                         IDatabaseListener& listener) :
-      TransactionBase(that.mutex_, that.db_, listener, *that.signalRemainingAncestor_),
+      TransactionBase(that.mutex_, that.db_, listener, *that.signalRemainingAncestor_, that.GetDatabaseCapabilities()),
       that_(that)
     {
       if (that_.activeTransaction_ != NULL)
@@ -1322,7 +1331,8 @@
   SQLiteDatabaseWrapper::SQLiteDatabaseWrapper(const std::string& path) : 
     activeTransaction_(NULL), 
     signalRemainingAncestor_(NULL),
-    version_(0)
+    version_(0),
+    dbCapabilities_(true, false /* TODO: implement revisions in SQLite */, true, false)
   {
     db_.Open(path);
   }
@@ -1331,7 +1341,8 @@
   SQLiteDatabaseWrapper::SQLiteDatabaseWrapper() : 
     activeTransaction_(NULL), 
     signalRemainingAncestor_(NULL),
-    version_(0)
+    version_(0),
+    dbCapabilities_(true, false /* TODO: implement revisions in SQLite */, true, false)
   {
     db_.OpenInMemory();
   }
--- a/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.h	Wed Dec 06 14:20:33 2023 +0100
+++ b/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.h	Thu Dec 07 12:04:11 2023 +0100
@@ -22,7 +22,7 @@
 
 #pragma once
 
-#include "IDatabaseWrapper.h"
+#include "BaseDatabaseWrapper.h"
 
 #include "../../../OrthancFramework/Sources/SQLite/Connection.h"
 
@@ -35,7 +35,7 @@
    * translates low-level requests into SQL statements. Mutual
    * exclusion MUST be implemented at a higher level.
    **/
-  class SQLiteDatabaseWrapper : public IDatabaseWrapper
+  class SQLiteDatabaseWrapper : public BaseDatabaseWrapper
   {
   private:
     class TransactionBase;
@@ -51,6 +51,7 @@
     TransactionBase*          activeTransaction_;
     SignalRemainingAncestor*  signalRemainingAncestor_;
     unsigned int              version_;
+    IDatabaseWrapper::Capabilities  dbCapabilities_;
 
     void GetChangesInternal(std::list<ServerIndexChange>& target,
                             bool& done,
@@ -79,11 +80,6 @@
 
     virtual void FlushToDisk() ORTHANC_OVERRIDE;
 
-    virtual bool HasFlushToDisk() const ORTHANC_OVERRIDE
-    {
-      return true;
-    }
-
     virtual unsigned int GetDatabaseVersion() ORTHANC_OVERRIDE
     {
       return version_;
@@ -92,24 +88,18 @@
     virtual void Upgrade(unsigned int targetVersion,
                          IStorageArea& storageArea) ORTHANC_OVERRIDE;
 
-    virtual bool HasRevisionsSupport() const ORTHANC_OVERRIDE
+    virtual const IDatabaseWrapper::Capabilities& GetDatabaseCapabilities() const ORTHANC_OVERRIDE
     {
-      return false;  // TODO - REVISIONS
+      return dbCapabilities_;
     }
 
-    virtual bool HasLabelsSupport() const ORTHANC_OVERRIDE
-    {
-      return true;
-    }
-
-
     /**
      * The "StartTransaction()" method is guaranteed to return a class
      * derived from "UnitTestsTransaction". The methods of
      * "UnitTestsTransaction" give access to additional information
      * about the underlying SQLite database to be used in unit tests.
      **/
-    class UnitTestsTransaction : public ITransaction
+    class UnitTestsTransaction : public BaseDatabaseWrapper::BaseTransaction
     {
     protected:
       SQLite::Connection& db_;
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Wed Dec 06 14:20:33 2023 +0100
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Thu Dec 07 12:04:11 2023 +0100
@@ -607,7 +607,7 @@
           
           Transaction transaction(db_, *factory_, TransactionType_ReadOnly);  // TODO - Only if not "TransactionType_Implicit"
           {
-            ReadOnlyTransaction t(transaction.GetDatabaseTransaction(), transaction.GetContext(), db_.HasLabelsSupport());
+            ReadOnlyTransaction t(transaction.GetDatabaseTransaction(), transaction.GetContext(), db_.GetDatabaseCapabilities());
             readOperations->Apply(t);
           }
           transaction.Commit();
@@ -618,7 +618,7 @@
           
           Transaction transaction(db_, *factory_, TransactionType_ReadWrite);
           {
-            ReadWriteTransaction t(transaction.GetDatabaseTransaction(), transaction.GetContext(), db_.HasLabelsSupport());
+            ReadWriteTransaction t(transaction.GetDatabaseTransaction(), transaction.GetContext(), db_.GetDatabaseCapabilities());
             writeOperations->Apply(t);
           }
           transaction.Commit();
@@ -654,7 +654,6 @@
   StatelessDatabaseOperations::StatelessDatabaseOperations(IDatabaseWrapper& db) : 
     db_(db),
     mainDicomTagsRegistry_(new MainDicomTagsRegistry),
-    hasFlushToDisk_(db.HasFlushToDisk()),
     maxRetries_(0)
   {
   }
@@ -939,7 +938,7 @@
           }
 
           if ((expandFlags & ExpandResourceFlags_IncludeLabels) &&
-              transaction.HasLabelsSupport())
+              transaction.GetDatabaseCapabilities().HasLabelsSupport())
           {
             transaction.ListLabels(target.labels_, internalId);
           }
@@ -1961,7 +1960,7 @@
     };
 
     if (!labels.empty() &&
-        !db_.HasLabelsSupport())
+        !db_.GetDatabaseCapabilities().HasLabelsSupport())
     {
       throw OrthancException(ErrorCode_NotImplemented, "The database backend doesn't support labels");
     }
@@ -2431,32 +2430,39 @@
 
       virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE
       {
-        std::string oldString;
-
-        if (transaction.LookupGlobalProperty(oldString, sequence_, shared_))
+        if (transaction.GetDatabaseCapabilities().HasAtomicIncrementGlobalProperty())
         {
-          uint64_t oldValue;
-      
-          try
-          {
-            oldValue = boost::lexical_cast<uint64_t>(oldString);
-          }
-          catch (boost::bad_lexical_cast&)
-          {
-            LOG(ERROR) << "Cannot read the global sequence "
-                       << boost::lexical_cast<std::string>(sequence_) << ", resetting it";
-            oldValue = 0;
-          }
-
-          newValue_ = oldValue + 1;
+          newValue_ = static_cast<uint64_t>(transaction.IncrementGlobalProperty(sequence_, shared_, 1));
         }
         else
         {
-          // Initialize the sequence at "1"
-          newValue_ = 1;
+          std::string oldString;
+
+          if (transaction.LookupGlobalProperty(oldString, sequence_, shared_))
+          {
+            uint64_t oldValue;
+        
+            try
+            {
+              oldValue = boost::lexical_cast<uint64_t>(oldString);
+            }
+            catch (boost::bad_lexical_cast&)
+            {
+              LOG(ERROR) << "Cannot read the global sequence "
+                        << boost::lexical_cast<std::string>(sequence_) << ", resetting it";
+              oldValue = 0;
+            }
+
+            newValue_ = oldValue + 1;
+          }
+          else
+          {
+            // Initialize the sequence at "1"
+            newValue_ = 1;
+          }
+
+          transaction.SetGlobalProperty(sequence_, shared_, boost::lexical_cast<std::string>(newValue_));
         }
-
-        transaction.SetGlobalProperty(sequence_, shared_, boost::lexical_cast<std::string>(newValue_));
       }
     };
 
@@ -3683,6 +3689,6 @@
   bool StatelessDatabaseOperations::HasLabelsSupport()
   {
     boost::shared_lock<boost::shared_mutex> lock(mutex_);
-    return db_.HasLabelsSupport();
+    return db_.GetDatabaseCapabilities().HasLabelsSupport();
   }
 }
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.h	Wed Dec 06 14:20:33 2023 +0100
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.h	Thu Dec 07 12:04:11 2023 +0100
@@ -176,17 +176,17 @@
     {
     private:
       ITransactionContext&  context_;
-      bool                  hasLabelsSupport_;
-      
+      const IDatabaseWrapper::Capabilities& dbCapabilities_;
+
     protected:
       IDatabaseWrapper::ITransaction&  transaction_;
       
     public:
       explicit ReadOnlyTransaction(IDatabaseWrapper::ITransaction& transaction,
                                    ITransactionContext& context,
-                                   bool hasLabelsSupport) :
+                                   const IDatabaseWrapper::Capabilities& dbCapabilities) :
         context_(context),
-        hasLabelsSupport_(hasLabelsSupport),
+        dbCapabilities_(dbCapabilities),
         transaction_(transaction)
       {
       }
@@ -196,9 +196,9 @@
         return context_;
       }
 
-      bool HasLabelsSupport() const
+      const IDatabaseWrapper::Capabilities& GetDatabaseCapabilities() const
       {
-        return hasLabelsSupport_;
+        return dbCapabilities_;
       }
 
       /**
@@ -392,8 +392,8 @@
     public:
       ReadWriteTransaction(IDatabaseWrapper::ITransaction& transaction,
                            ITransactionContext& context,
-                           bool hasLabelsSupport) :
-        ReadOnlyTransaction(transaction, context, hasLabelsSupport)
+                           const IDatabaseWrapper::Capabilities& dbCapabilities) :
+        ReadOnlyTransaction(transaction, context, dbCapabilities)
       {
       }
 
@@ -463,6 +463,13 @@
         transaction_.SetGlobalProperty(property, shared, value);
       }
 
+      int64_t IncrementGlobalProperty(GlobalProperty sequence,
+                                      bool shared,
+                                      int64_t increment)
+      {
+        return transaction_.IncrementGlobalProperty(sequence, shared, increment);
+      }
+
       void SetMetadata(int64_t id,
                        MetadataType type,
                        const std::string& value,
@@ -540,7 +547,6 @@
 
     IDatabaseWrapper&                            db_;
     boost::shared_ptr<MainDicomTagsRegistry>     mainDicomTagsRegistry_;  // "shared_ptr" because of PImpl
-    bool                                         hasFlushToDisk_;
 
     // Mutex to protect the configuration options
     boost::shared_mutex                          mutex_;
@@ -575,12 +581,13 @@
       return db_.GetDatabaseVersion();
     }
 
+    const IDatabaseWrapper::Capabilities& GetDatabaseCapabilities() const
+    {
+      return db_.GetDatabaseCapabilities();
+    }
+
     void FlushToDisk();
 
-    bool HasFlushToDisk() const
-    {
-      return hasFlushToDisk_;
-    }
 
     void Apply(IReadOnlyOperations& operations);
   
--- a/OrthancServer/Sources/ServerIndex.cpp	Wed Dec 06 14:20:33 2023 +0100
+++ b/OrthancServer/Sources/ServerIndex.cpp	Thu Dec 07 12:04:11 2023 +0100
@@ -331,7 +331,7 @@
     // execution of Orthanc
     StandaloneRecycling(maximumStorageMode_, maximumStorageSize_, maximumPatients_);
 
-    if (HasFlushToDisk())
+    if (GetDatabaseCapabilities().HasFlushToDisk())
     {
       flushThread_ = boost::thread(FlushThread, this, threadSleepGranularityMilliseconds);
     }
--- a/OrthancServer/Sources/main.cpp	Wed Dec 06 14:20:33 2023 +0100
+++ b/OrthancServer/Sources/main.cpp	Thu Dec 07 12:04:11 2023 +0100
@@ -1643,7 +1643,7 @@
     
     if (lock.GetConfiguration().GetBooleanParameter(CHECK_REVISIONS, false))
     {
-      if (database.HasRevisionsSupport())
+      if (database.GetDatabaseCapabilities().HasRevisionsSupport())
       {
         LOG(INFO) << "Handling of revisions is enabled, and the custom database back-end *has* "
                   << "support for revisions of metadata and attachments";
@@ -1666,7 +1666,7 @@
     }
   }
 
-  if (!database.HasLabelsSupport())
+  if (!database.GetDatabaseCapabilities().HasLabelsSupport())
   {
     LOG(WARNING) << "The custom database back-end has *no* support for labels";
   }