changeset 661:3b36405ccecd attach-custom-data

KVS + Queues
author Alain Mazy <am@orthanc.team>
date Wed, 21 May 2025 21:43:34 +0200
parents a40b6b48dbcd
children 133e785a87f5
files Framework/Plugins/DatabaseBackendAdapterV4.cpp Framework/Plugins/IDatabaseBackend.h Framework/Plugins/IndexBackend.cpp Framework/Plugins/IndexBackend.h Framework/Plugins/MessagesToolbox.h MySQL/Plugins/MySQLIndex.h Odbc/Plugins/OdbcIndex.h PostgreSQL/Plugins/PostgreSQLIndex.h PostgreSQL/Plugins/SQL/Downgrades/Rev5ToRev4.sql PostgreSQL/Plugins/SQL/PrepareIndex.sql SQLite/Plugins/SQLiteIndex.h
diffstat 11 files changed, 547 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Plugins/DatabaseBackendAdapterV4.cpp	Mon May 19 16:26:18 2025 +0200
+++ b/Framework/Plugins/DatabaseBackendAdapterV4.cpp	Wed May 21 21:43:34 2025 +0200
@@ -453,6 +453,18 @@
         response.mutable_get_system_information()->set_has_extended_changes(accessor.GetBackend().HasExtendedChanges());
 #endif
 
+#if ORTHANC_PLUGINS_HAS_ATTACHMENTS_CUSTOM_DATA == 1
+        response.mutable_get_system_information()->set_has_attachment_custom_data(accessor.GetBackend().HasAttachmentCustomDataSupport());
+#endif
+
+#if ORTHANC_PLUGINS_HAS_QUEUES == 1
+        response.mutable_get_system_information()->set_supports_queues(accessor.GetBackend().HasQueues());
+#endif
+
+#if ORTHANC_PLUGINS_HAS_KEY_VALUE_STORES == 1
+        response.mutable_get_system_information()->set_supports_key_value_stores(accessor.GetBackend().HasKeyValueStores());
+#endif
+
         break;
       }
 
@@ -1346,6 +1358,96 @@
       }
 #endif
 
+#if ORTHANC_PLUGINS_HAS_KEY_VALUE_STORES == 1
+      case Orthanc::DatabasePluginMessages::OPERATION_STORE_KEY_VALUE:
+      {
+        backend.StoreKeyValue(manager, 
+                              request.store_key_value().store_id(),
+                              request.store_key_value().key(),
+                              request.store_key_value().value());
+      }; break;
+
+      case Orthanc::DatabasePluginMessages::OPERATION_GET_KEY_VALUE:
+      {
+        std::string value;
+        bool found = backend.GetKeyValue(manager, 
+                                         value,
+                                         request.store_key_value().store_id(),
+                                         request.store_key_value().key());
+        response.mutable_get_key_value()->set_found(found);
+
+        if (found)
+        {
+          response.mutable_get_key_value()->set_value(value);
+        }
+      }; break;
+
+      case Orthanc::DatabasePluginMessages::OPERATION_DELETE_KEY_VALUE:
+      {
+        backend.DeleteKeyValue(manager, 
+                               request.store_key_value().store_id(),
+                               request.store_key_value().key());
+      }; break;
+
+      case Orthanc::DatabasePluginMessages::OPERATION_LIST_KEY_VALUES:
+      {
+        backend.ListKeysValues(response, 
+                               manager, 
+                               request.list_keys_values());
+      }; break;
+
+#endif
+
+#if ORTHANC_PLUGINS_HAS_QUEUES == 1
+      case Orthanc::DatabasePluginMessages::OPERATION_ENQUEUE_VALUE:
+      {
+        backend.EnqueueValue(manager,
+                             request.enqueue_value().queue_id(),
+                             request.enqueue_value().value());
+      }; break;
+
+      case Orthanc::DatabasePluginMessages::OPERATION_DEQUEUE_VALUE:
+      {
+        std::string value;
+        bool found = backend.DequeueValue(manager,
+                                          value,
+                                          request.dequeue_value().queue_id(),
+                                          request.dequeue_value().origin() == Orthanc::DatabasePluginMessages::QUEUE_ORIGIN_FRONT);
+        response.mutable_dequeue_value()->set_found(found);
+        
+        if (found)
+        {
+          response.mutable_dequeue_value()->set_value(value);
+        }
+      }; break;
+
+      case Orthanc::DatabasePluginMessages::OPERATION_GET_QUEUE_SIZE:
+      {
+        uint64_t size = backend.GetQueueSize(manager,
+                                             request.dequeue_value().queue_id());
+        response.mutable_get_queue_size()->set_size(size);
+      }; break;
+
+#endif
+
+#if ORTHANC_PLUGINS_HAS_ATTACHMENTS_CUSTOM_DATA == 1
+      case Orthanc::DatabasePluginMessages::OPERATION_GET_ATTACHMENT:
+      {
+        bool found = backend.GetAttachment(response,
+                                           manager,
+                                           request.get_attachment());
+        response.mutable_get_attachment()->set_found(found);
+      }; break;
+
+      case Orthanc::DatabasePluginMessages::OPERATION_UPDATE_ATTACHMENT_CUSTOM_DATA:
+      {
+        backend.UpdateAttachmentCustomData(manager,
+                                           request.update_attachment_custom_data().uuid(),
+                                           request.update_attachment_custom_data().custom_data());
+      }; break;
+
+#endif
+
       default:
         LOG(ERROR) << "Not implemented transaction operation from protobuf: " << request.operation();
         throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
--- a/Framework/Plugins/IDatabaseBackend.h	Mon May 19 16:26:18 2025 +0200
+++ b/Framework/Plugins/IDatabaseBackend.h	Wed May 21 21:43:34 2025 +0200
@@ -61,6 +61,10 @@
 
     virtual bool HasAttachmentCustomDataSupport() const = 0;
 
+    virtual bool HasKeyValueStores() const = 0;
+
+    virtual bool HasQueues() const = 0;
+
     virtual void AddAttachment(DatabaseManager& manager,
                                int64_t id,
                                const OrthancPluginAttachment& attachment,
@@ -409,6 +413,50 @@
                               const Orthanc::DatabasePluginMessages::Find_Request& request) = 0;
 #endif
 
+#if ORTHANC_PLUGINS_HAS_KEY_VALUE_STORES == 1
+    virtual void StoreKeyValue(DatabaseManager& manager,
+                                const std::string& storeId,
+                                const std::string& key,
+                                const std::string& value) = 0;
+
+    virtual void DeleteKeyValue(DatabaseManager& manager,
+                                const std::string& storeId,
+                                const std::string& key) = 0;
+
+    virtual bool GetKeyValue(DatabaseManager& manager,
+                              std::string& value,
+                              const std::string& storeId,
+                              const std::string& key) = 0;
+
+    virtual void ListKeysValues(Orthanc::DatabasePluginMessages::TransactionResponse& response,
+                                DatabaseManager& manager,
+                                const Orthanc::DatabasePluginMessages::ListKeysValues_Request& request) = 0;
+#endif
+
+#if ORTHANC_PLUGINS_HAS_QUEUES == 1
+    virtual void EnqueueValue(DatabaseManager& manager,
+                              const std::string& queueId,
+                              const std::string& value) = 0;
+
+    virtual bool DequeueValue(DatabaseManager& manager,
+                              std::string& value,
+                              const std::string& queueId,
+                              bool fromFront) = 0;
+
+    virtual uint64_t GetQueueSize(DatabaseManager& manager,
+                                  const std::string& queueId) = 0;
+#endif
+
+#if ORTHANC_PLUGINS_HAS_ATTACHMENTS_CUSTOM_DATA == 1
+    virtual bool GetAttachment(Orthanc::DatabasePluginMessages::TransactionResponse& response,
+                               DatabaseManager& manager,
+                               const Orthanc::DatabasePluginMessages::GetAttachment_Request& request) = 0;
+
+    virtual void UpdateAttachmentCustomData(DatabaseManager& manager,
+                                            const std::string& attachmentUuid,
+                                            const std::string& customData) = 0;
+#endif
+
     virtual bool HasPerformDbHousekeeping() = 0;
 
     virtual void PerformDbHousekeeping(DatabaseManager& manager) = 0;
--- a/Framework/Plugins/IndexBackend.cpp	Mon May 19 16:26:18 2025 +0200
+++ b/Framework/Plugins/IndexBackend.cpp	Wed May 21 21:43:34 2025 +0200
@@ -4366,4 +4366,282 @@
     }    
   }
 #endif
+
+#if ORTHANC_PLUGINS_HAS_KEY_VALUE_STORES == 1
+    void IndexBackend::StoreKeyValue(DatabaseManager& manager,
+                                     const std::string& storeId,
+                                     const std::string& key,
+                                     const std::string& value)
+    {
+      DatabaseManager::CachedStatement statement(
+        STATEMENT_FROM_HERE, manager,
+        "INSERT INTO KeyValueStores VALUES(${storeId}, ${key}, ${value})");
+
+      Dictionary args;
+
+      statement.SetParameterType("storeId", ValueType_Utf8String);
+      statement.SetParameterType("key", ValueType_Utf8String);
+      statement.SetParameterType("value", ValueType_Utf8String);
+
+      args.SetUtf8Value("storeId", storeId);
+      args.SetUtf8Value("key", key);
+      args.SetUtf8Value("value", value);
+
+      statement.Execute(args);
+    }
+
+    void IndexBackend::DeleteKeyValue(DatabaseManager& manager,
+                                      const std::string& storeId,
+                                      const std::string& key)
+    {
+      DatabaseManager::CachedStatement statement(
+        STATEMENT_FROM_HERE, manager,
+        "DELETE FROM KeyValueStores WHERE storeId = ${storeId} AND key = ${key}");
+
+      Dictionary args;
+
+      statement.SetParameterType("storeId", ValueType_Utf8String);
+      statement.SetParameterType("key", ValueType_Utf8String);
+
+      args.SetUtf8Value("storeId", storeId);
+      args.SetUtf8Value("key", key);
+
+      statement.Execute(args);
+    }
+
+    bool IndexBackend::GetKeyValue(DatabaseManager& manager,
+                                   std::string& value,
+                                   const std::string& storeId,
+                                  const std::string& key)
+    {
+      DatabaseManager::CachedStatement statement(
+        STATEMENT_FROM_HERE, manager,
+        "SELECT value FROM KeyValueStores WHERE storeId = ${storeId} AND key = ${key}");
+        
+      statement.SetReadOnly(true);
+      
+      Dictionary args;
+      statement.SetParameterType("storeId", ValueType_Utf8String);
+      statement.SetParameterType("key", ValueType_Utf8String);
+
+      args.SetUtf8Value("storeId", storeId);
+      args.SetUtf8Value("key", key);
+
+      statement.Execute(args);
+
+      if (statement.IsDone())
+      {
+        return false;
+      }
+      else
+      {
+        value = statement.ReadString(0);
+        return true;
+      }
+    }                  
+
+    void IndexBackend::ListKeysValues(Orthanc::DatabasePluginMessages::TransactionResponse& response,
+                                      DatabaseManager& manager,
+                                      const Orthanc::DatabasePluginMessages::ListKeysValues_Request& request)
+    {
+      LookupFormatter formatter(manager.GetDialect());
+
+      std::unique_ptr<DatabaseManager::CachedStatement> statement;
+      
+      std::string storeIdParameter = formatter.GenerateParameter(request.store_id());
+
+      if (request.from_first())
+      {
+        statement.reset(new DatabaseManager::CachedStatement(
+                        STATEMENT_FROM_HERE, manager,
+                        "SELECT key, value FROM KeyValueStores WHERE storeId= " + storeIdParameter + " ORDER BY ASC " + formatter.FormatLimits(0, request.limit())));
+      }
+      else
+      {
+        std::string fromKeyParameter = formatter.GenerateParameter(request.from_key());
+
+        statement.reset(new DatabaseManager::CachedStatement(
+                        STATEMENT_FROM_HERE, manager,
+                        "SELECT key, value FROM KeyValueStores WHERE storeId= " + storeIdParameter + " AND key > " + fromKeyParameter + " ORDER BY ASC " + formatter.FormatLimits(0, request.limit())));
+      }
+        
+      statement->Execute(formatter.GetDictionary());
+        
+      if (!statement->IsDone())
+      {
+        if (statement->GetResultFieldsCount() != 2)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+        
+        statement->SetResultFieldType(0, ValueType_Utf8String);
+        statement->SetResultFieldType(1, ValueType_Utf8String);
+
+        while (!statement->IsDone())
+        {
+          Orthanc::DatabasePluginMessages::ListKeysValues_Response_KeyValue* kv = response.mutable_list_keys_values()->add_keys_values();
+          kv->set_key(statement->ReadString(0));
+          kv->set_value(statement->ReadString(1));
+
+          statement->Next();
+        }
+      }
+
+    }
+#endif
+
+#if ORTHANC_PLUGINS_HAS_QUEUES == 1
+    void IndexBackend::EnqueueValue(DatabaseManager& manager,
+                                    const std::string& queueId,
+                                    const std::string& value)
+    {
+      DatabaseManager::CachedStatement statement(
+        STATEMENT_FROM_HERE, manager,
+        "INSERT INTO Queues VALUES(${queueId}, ${value})");
+
+      Dictionary args;
+
+      statement.SetParameterType("queueId", ValueType_Utf8String);
+      statement.SetParameterType("value", ValueType_Utf8String);
+
+      args.SetUtf8Value("queueId", queueId);
+      args.SetUtf8Value("value", value);
+
+      statement.Execute(args);
+    }
+
+    bool IndexBackend::DequeueValue(DatabaseManager& manager,
+                                    std::string& value,
+                                    const std::string& queueId,
+                                    bool fromFront)
+    {
+      LookupFormatter formatter(manager.GetDialect());
+
+      std::unique_ptr<DatabaseManager::CachedStatement> statement;
+      
+      std::string queueIdParameter = formatter.GenerateParameter(queueId);
+
+      if (fromFront)
+      {
+        statement.reset(new DatabaseManager::CachedStatement(
+                        STATEMENT_FROM_HERE, manager,
+                        "SELECT id, value FROM Queues WHERE storeId= " + queueIdParameter + " ORDER BY ASC " + formatter.FormatLimits(0, 1)));
+      }
+      else
+      {
+        statement.reset(new DatabaseManager::CachedStatement(
+                        STATEMENT_FROM_HERE, manager,
+                        "SELECT id, value FROM Queues WHERE storeId= " + queueIdParameter + " ORDER BY DESC " + formatter.FormatLimits(0, 1)));
+      }
+        
+      statement->Execute(formatter.GetDictionary());
+
+      if (statement->IsDone())
+      {
+        return false;
+      }        
+      else
+      {
+        if (statement->GetResultFieldsCount() != 2)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+        }
+
+        statement->SetResultFieldType(0, ValueType_Integer64);
+        statement->SetResultFieldType(1, ValueType_Utf8String);
+
+        int64_t id = statement->ReadInteger64(0);
+        value = statement->ReadString(1);
+
+        DatabaseManager::CachedStatement statement2(
+          STATEMENT_FROM_HERE, manager,
+          "DELETE FROM Queues WHERE id = ${id}");
+
+        Dictionary args;
+
+        statement2.SetParameterType("id", ValueType_Integer64);
+        args.SetIntegerValue("id", id);
+
+        statement2.Execute(args);
+
+        return true;
+      }
+    }
+
+    uint64_t IndexBackend::GetQueueSize(DatabaseManager& manager,
+                                        const std::string& queueId)
+    {
+      DatabaseManager::CachedStatement statement(
+        STATEMENT_FROM_HERE, manager,
+        "SELECT COUNT(*) FROM KeyValueStores WHERE queueId = ${queueId}");
+        
+      statement.SetReadOnly(true);
+      
+      Dictionary args;
+      statement.SetParameterType("queueId", ValueType_Utf8String);
+      args.SetUtf8Value("queueId", queueId);
+
+      statement.Execute(args);
+
+      return statement.ReadInteger64(0);
+    }
+#endif
+
+#if ORTHANC_PLUGINS_HAS_ATTACHMENTS_CUSTOM_DATA == 1
+    bool IndexBackend::GetAttachment(Orthanc::DatabasePluginMessages::TransactionResponse& response,
+                                     DatabaseManager& manager,
+                                     const Orthanc::DatabasePluginMessages::GetAttachment_Request& request)
+    {
+      DatabaseManager::CachedStatement statement(
+        STATEMENT_FROM_HERE, manager,
+        "SELECT uuid, fileType, uncompressedSize, uncompressedHash, compressionType, "
+        "compressedSize, compressedHash, revision, customData FROM AttachedFiles WHERE uuid = ${uuid}");
+
+      statement.SetReadOnly(true);
+
+      Dictionary args;
+      statement.SetParameterType("uuid", ValueType_Utf8String);
+      args.SetUtf8Value("uuid", request.uuid());
+
+      statement.Execute(args);
+
+      if (statement.IsDone())
+      {
+        return false;
+      }
+      else
+      {
+        response.mutable_get_attachment()->set_revision(statement.ReadInteger64(7));
+        response.mutable_get_attachment()->mutable_attachment()->set_uuid(statement.ReadString(0));
+        response.mutable_get_attachment()->mutable_attachment()->set_content_type(statement.ReadInteger32(1));
+        response.mutable_get_attachment()->mutable_attachment()->set_uncompressed_size(statement.ReadInteger64(2));
+        response.mutable_get_attachment()->mutable_attachment()->set_uncompressed_hash(statement.ReadString(3));
+        response.mutable_get_attachment()->mutable_attachment()->set_compression_type(statement.ReadInteger32(4));
+        response.mutable_get_attachment()->mutable_attachment()->set_compressed_size(statement.ReadInteger64(5));
+        response.mutable_get_attachment()->mutable_attachment()->set_compressed_hash(statement.ReadString(6));
+        response.mutable_get_attachment()->mutable_attachment()->set_custom_data(statement.ReadString(8));
+
+        return true;        
+      }
+
+    }
+
+    void  IndexBackend::UpdateAttachmentCustomData(DatabaseManager& manager,
+                                                   const std::string& attachmentUuid,
+                                                   const std::string& customData)
+    {
+      DatabaseManager::CachedStatement statement(
+        STATEMENT_FROM_HERE, manager,
+        "UPDATE AttachedFiles SET customData = ${customData} WHERE uuid = ${uuid}");
+
+      statement.SetParameterType("uuid", ValueType_Utf8String);
+      statement.SetParameterType("customData", ValueType_Utf8String);
+
+      Dictionary args;
+      args.SetUtf8Value("uuid", attachmentUuid);
+      args.SetUtf8Value("customData", customData);
+      
+      statement.Execute(args);
+    }
+#endif
 }
--- a/Framework/Plugins/IndexBackend.h	Mon May 19 16:26:18 2025 +0200
+++ b/Framework/Plugins/IndexBackend.h	Wed May 21 21:43:34 2025 +0200
@@ -466,6 +466,52 @@
                               const Orthanc::DatabasePluginMessages::Find_Request& request) ORTHANC_OVERRIDE;
 #endif
 
+#if ORTHANC_PLUGINS_HAS_KEY_VALUE_STORES == 1
+    virtual void StoreKeyValue(DatabaseManager& manager,
+                                const std::string& storeId,
+                                const std::string& key,
+                                const std::string& value) ORTHANC_OVERRIDE;
+
+    virtual void DeleteKeyValue(DatabaseManager& manager,
+                                const std::string& storeId,
+                                const std::string& key) ORTHANC_OVERRIDE;
+
+    virtual bool GetKeyValue(DatabaseManager& manager,
+                              std::string& value,
+                              const std::string& storeId,
+                              const std::string& key) ORTHANC_OVERRIDE;
+
+    virtual void ListKeysValues(Orthanc::DatabasePluginMessages::TransactionResponse& response,
+                                DatabaseManager& manager,
+                                const Orthanc::DatabasePluginMessages::ListKeysValues_Request& request) ORTHANC_OVERRIDE;
+#endif
+
+#if ORTHANC_PLUGINS_HAS_QUEUES == 1
+    virtual void EnqueueValue(DatabaseManager& manager,
+                              const std::string& queueId,
+                              const std::string& value) ORTHANC_OVERRIDE;
+
+    virtual bool DequeueValue(DatabaseManager& manager,
+                              std::string& value,
+                              const std::string& queueId,
+                              bool fromFront) ORTHANC_OVERRIDE;
+
+    virtual uint64_t GetQueueSize(DatabaseManager& manager,
+                                  const std::string& queueId) ORTHANC_OVERRIDE;
+
+#endif
+
+#if ORTHANC_PLUGINS_HAS_ATTACHMENTS_CUSTOM_DATA == 1
+    virtual bool GetAttachment(Orthanc::DatabasePluginMessages::TransactionResponse& response,
+                                DatabaseManager& manager,
+                                const Orthanc::DatabasePluginMessages::GetAttachment_Request& request) ORTHANC_OVERRIDE;
+
+    virtual void UpdateAttachmentCustomData(DatabaseManager& manager,
+                                            const std::string& attachmentUuid,
+                                            const std::string& customData) ORTHANC_OVERRIDE;
+
+#endif
+
     virtual bool HasPerformDbHousekeeping() ORTHANC_OVERRIDE
     {
       return false;
--- a/Framework/Plugins/MessagesToolbox.h	Mon May 19 16:26:18 2025 +0200
+++ b/Framework/Plugins/MessagesToolbox.h	Wed May 21 21:43:34 2025 +0200
@@ -40,31 +40,29 @@
 
 
 #define ORTHANC_PLUGINS_HAS_INTEGRATED_FIND 0
+#define ORTHANC_PLUGINS_HAS_CHANGES_EXTENDED 0
 
 #if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
 #  if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5)
 #    undef  ORTHANC_PLUGINS_HAS_INTEGRATED_FIND
 #    define ORTHANC_PLUGINS_HAS_INTEGRATED_FIND 1
-#  endif
-#endif
-
-
-#define ORTHANC_PLUGINS_HAS_CHANGES_EXTENDED 0
-
-#if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
-#  if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 5)
 #    undef  ORTHANC_PLUGINS_HAS_CHANGES_EXTENDED
 #    define ORTHANC_PLUGINS_HAS_CHANGES_EXTENDED 1
 #  endif
 #endif
 
-
 #define ORTHANC_PLUGINS_HAS_ATTACHMENTS_CUSTOM_DATA 0
+#define ORTHANC_PLUGINS_HAS_KEY_VALUE_STORES 0
+#define ORTHANC_PLUGINS_HAS_QUEUES 0
 
 #if defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
-#  if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 7)
+#  if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 99)
 #    undef  ORTHANC_PLUGINS_HAS_ATTACHMENTS_CUSTOM_DATA
 #    define ORTHANC_PLUGINS_HAS_ATTACHMENTS_CUSTOM_DATA 1
+#    undef  ORTHANC_PLUGINS_HAS_KEY_VALUE_STORES
+#    define ORTHANC_PLUGINS_HAS_KEY_VALUE_STORES 1
+#    undef  ORTHANC_PLUGINS_HAS_QUEUES
+#    define ORTHANC_PLUGINS_HAS_QUEUES 1
 #  endif
 #endif
 
--- a/MySQL/Plugins/MySQLIndex.h	Mon May 19 16:26:18 2025 +0200
+++ b/MySQL/Plugins/MySQLIndex.h	Wed May 21 21:43:34 2025 +0200
@@ -66,6 +66,16 @@
       return true;
     }
     
+    virtual bool HasKeyValueStores() const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
+
+    virtual bool HasQueues() const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
+
     virtual int64_t CreateResource(DatabaseManager& manager,
                                    const char* publicId,
                                    OrthancPluginResourceType type)
--- a/Odbc/Plugins/OdbcIndex.h	Mon May 19 16:26:18 2025 +0200
+++ b/Odbc/Plugins/OdbcIndex.h	Wed May 21 21:43:34 2025 +0200
@@ -73,11 +73,21 @@
       return true;
     }
 
-    bool HasAttachmentCustomDataSupport() const ORTHANC_OVERRIDE
+    virtual bool HasAttachmentCustomDataSupport() const ORTHANC_OVERRIDE
     {
       return true;
     }
 
+    virtual bool HasKeyValueStores() const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
+
+    virtual bool HasQueues() const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
+
     virtual int64_t CreateResource(DatabaseManager& manager,
                                    const char* publicId,
                                    OrthancPluginResourceType type) ORTHANC_OVERRIDE;
--- a/PostgreSQL/Plugins/PostgreSQLIndex.h	Mon May 19 16:26:18 2025 +0200
+++ b/PostgreSQL/Plugins/PostgreSQLIndex.h	Wed May 21 21:43:34 2025 +0200
@@ -77,6 +77,16 @@
       return true;
     }
 
+    virtual bool HasKeyValueStores() const ORTHANC_OVERRIDE
+    {
+      return true;
+    }
+
+    virtual bool HasQueues() const ORTHANC_OVERRIDE
+    {
+      return true;
+    }
+
     virtual int64_t CreateResource(DatabaseManager& manager,
                                    const char* publicId,
                                    OrthancPluginResourceType type) ORTHANC_OVERRIDE;
--- a/PostgreSQL/Plugins/SQL/Downgrades/Rev5ToRev4.sql	Mon May 19 16:26:18 2025 +0200
+++ b/PostgreSQL/Plugins/SQL/Downgrades/Rev5ToRev4.sql	Wed May 21 21:43:34 2025 +0200
@@ -50,6 +50,12 @@
 EXECUTE PROCEDURE AttachedFileDeletedFunc();
 
 
+DROP TABLE IF EXISTS KeyValueStores;
+
+DROP TABLE IF EXISTS Queues;
+
+DROP INDEX IF EXISTS QueuesIndex;
+
 
 DELETE FROM GlobalProperties WHERE property IN (4);
 INSERT INTO GlobalProperties VALUES (4, 4); -- GlobalProperty_DatabasePatchLevel
--- a/PostgreSQL/Plugins/SQL/PrepareIndex.sql	Mon May 19 16:26:18 2025 +0200
+++ b/PostgreSQL/Plugins/SQL/PrepareIndex.sql	Wed May 21 21:43:34 2025 +0200
@@ -721,6 +721,23 @@
 EXECUTE PROCEDURE UpdateChildCount();
 
 
+-- new in 1.12.99
+
+CREATE TABLE KeyValueStores(
+       storeId TEXT NOT NULL,
+       key TEXT NOT NULL,
+       value TEXT NOT NULL,
+       PRIMARY KEY(storeId, key)  -- Prevents duplicates
+       );
+
+CREATE TABLE Queues (
+       id BIGSERIAL NOT NULL PRIMARY KEY,
+       queueId TEXT NOT NULL,
+       value TEXT
+);
+
+CREATE INDEX QueuesIndex ON Queues (queueId, id);
+
 
 -- set the global properties that actually documents the DB version, revision and some of the capabilities
 DELETE FROM GlobalProperties WHERE property IN (1, 4, 6, 10, 11, 12, 13, 14);
--- a/SQLite/Plugins/SQLiteIndex.h	Mon May 19 16:26:18 2025 +0200
+++ b/SQLite/Plugins/SQLiteIndex.h	Wed May 21 21:43:34 2025 +0200
@@ -55,11 +55,21 @@
       return true;
     }
     
-    bool HasAttachmentCustomDataSupport() const ORTHANC_OVERRIDE
+    virtual bool HasAttachmentCustomDataSupport() const ORTHANC_OVERRIDE
     {
       return true;
     }
 
+    virtual bool HasKeyValueStores() const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
+
+    virtual bool HasQueues() const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
+
     virtual int64_t CreateResource(DatabaseManager& manager,
                                    const char* publicId,
                                    OrthancPluginResourceType type) ORTHANC_OVERRIDE;