changeset 6145:055addebab2a attach-custom-data

support for blobs in key-value stores and queues
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 30 May 2025 15:38:47 +0200
parents f9d6955e22dd
children 9db300bcf873
files OrthancFramework/Sources/SQLite/Statement.cpp OrthancFramework/Sources/SQLite/Statement.h OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp OrthancServer/Plugins/Engine/OrthancPlugins.cpp OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h OrthancServer/Sources/Database/IDatabaseWrapper.h OrthancServer/Sources/Database/InstallKeyValueStoresAndQueues.sql OrthancServer/Sources/Database/PrepareDatabase.sql OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp OrthancServer/Sources/Database/StatelessDatabaseOperations.h OrthancServer/UnitTestsSources/ServerIndexTests.cpp
diffstat 17 files changed, 218 insertions(+), 108 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancFramework/Sources/SQLite/Statement.cpp	Fri May 30 14:28:41 2025 +0200
+++ b/OrthancFramework/Sources/SQLite/Statement.cpp	Fri May 30 15:38:47 2025 +0200
@@ -236,10 +236,17 @@
       BindString(col, UTF16ToUTF8(value));
       }*/
 
-    void Statement::BindBlob(int col, const void* val, int val_len) 
+    void Statement::BindBlob(int col, const void* val, size_t val_len)
     {
-      CheckOk(sqlite3_bind_blob(GetStatement(), col + 1, val, val_len, SQLITE_TRANSIENT),
-              ErrorCode_BadParameterType);
+      if (static_cast<size_t>(static_cast<int>(val_len)) != val_len)
+      {
+        throw OrthancSQLiteException(ErrorCode_SQLiteBindOutOfRange);
+      }
+      else
+      {
+        CheckOk(sqlite3_bind_blob(GetStatement(), col + 1, val, static_cast<int>(val_len), SQLITE_TRANSIENT),
+                ErrorCode_BadParameterType);
+      }
     }
 
 
--- a/OrthancFramework/Sources/SQLite/Statement.h	Fri May 30 14:28:41 2025 +0200
+++ b/OrthancFramework/Sources/SQLite/Statement.h	Fri May 30 15:38:47 2025 +0200
@@ -130,7 +130,7 @@
       void BindCString(int col, const char* val);
       void BindString(int col, const std::string& val);
       //void BindString16(int col, const string16& value);
-      void BindBlob(int col, const void* value, int value_len);
+      void BindBlob(int col, const void* value, size_t value_len);
 
 
       // Retrieving ----------------------------------------------------------------
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp	Fri May 30 14:28:41 2025 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp	Fri May 30 15:38:47 2025 +0200
@@ -1453,7 +1453,8 @@
 
     virtual void StoreKeyValue(const std::string& storeId,
                                const std::string& key,
-                               const std::string& value) ORTHANC_OVERRIDE
+                               const void* value,
+                               size_t valueSize) ORTHANC_OVERRIDE
     {
       throw OrthancException(ErrorCode_InternalError);  // Not supported
     }
@@ -1482,7 +1483,8 @@
     }
 
     virtual void EnqueueValue(const std::string& queueId,
-                              const std::string& value) ORTHANC_OVERRIDE
+                              const void* value,
+                              size_t valueSize) ORTHANC_OVERRIDE
     {
       throw OrthancException(ErrorCode_InternalError);  // Not supported
     }
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp	Fri May 30 14:28:41 2025 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp	Fri May 30 15:38:47 2025 +0200
@@ -1065,7 +1065,8 @@
 
     virtual void StoreKeyValue(const std::string& storeId,
                                const std::string& key,
-                               const std::string& value) ORTHANC_OVERRIDE
+                               const void* value,
+                               size_t valueSize) ORTHANC_OVERRIDE
     {
       throw OrthancException(ErrorCode_InternalError);  // Not supported
     }
@@ -1094,7 +1095,8 @@
     }
 
     virtual void EnqueueValue(const std::string& queueId,
-                              const std::string& value) ORTHANC_OVERRIDE
+                              const void* value,
+                              size_t valueSize) ORTHANC_OVERRIDE
     {
       throw OrthancException(ErrorCode_InternalError);  // Not supported
     }
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp	Fri May 30 14:28:41 2025 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp	Fri May 30 15:38:47 2025 +0200
@@ -41,7 +41,7 @@
 #include "OrthancDatabasePlugin.pb.h"  // Auto-generated file
 
 #include <cassert>
-
+#include <limits>
 
 namespace Orthanc
 {
@@ -1860,14 +1860,22 @@
 
     virtual void StoreKeyValue(const std::string& storeId,
                                const std::string& key,
-                               const std::string& value) ORTHANC_OVERRIDE
+                               const void* value,
+                               size_t valueSize) ORTHANC_OVERRIDE
     {
+      // In protobuf, bytes "may contain any arbitrary sequence of bytes no longer than 2^32"
+      // https://protobuf.dev/programming-guides/proto3/
+      if (valueSize > std::numeric_limits<uint32_t>::max())
+      {
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+      }
+
       if (database_.GetDatabaseCapabilities().HasKeyValueStoresSupport())
       {
         DatabasePluginMessages::TransactionRequest request;
         request.mutable_store_key_value()->set_store_id(storeId);
         request.mutable_store_key_value()->set_key(key);
-        request.mutable_store_key_value()->set_value(value);
+        request.mutable_store_key_value()->set_value(value, valueSize);
 
         ExecuteTransaction(DatabasePluginMessages::OPERATION_STORE_KEY_VALUE, request);
       }
@@ -1958,13 +1966,21 @@
     }
 
     virtual void EnqueueValue(const std::string& queueId,
-                              const std::string& value) ORTHANC_OVERRIDE
+                              const void* value,
+                              size_t valueSize) ORTHANC_OVERRIDE
     {
+      // In protobuf, bytes "may contain any arbitrary sequence of bytes no longer than 2^32"
+      // https://protobuf.dev/programming-guides/proto3/
+      if (valueSize > std::numeric_limits<uint32_t>::max())
+      {
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+      }
+
       if (database_.GetDatabaseCapabilities().HasQueuesSupport())
       {
         DatabasePluginMessages::TransactionRequest request;
         request.mutable_enqueue_value()->set_queue_id(queueId);
-        request.mutable_enqueue_value()->set_value(value);
+        request.mutable_enqueue_value()->set_value(value, valueSize);
 
         ExecuteTransaction(DatabasePluginMessages::OPERATION_ENQUEUE_VALUE, request);
       }
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Fri May 30 14:28:41 2025 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Fri May 30 15:38:47 2025 +0200
@@ -1963,7 +1963,7 @@
 
       std::string dicom;
       currentQuery_->SaveToMemoryBuffer(dicom);
-      CopyToMemoryBuffer(target, dicom.c_str(), dicom.size());
+      CopyToMemoryBuffer(target, dicom);
     }
 
     bool IsMatch(const void* dicom,
@@ -3951,7 +3951,7 @@
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    CopyToMemoryBuffer(*p.target, compressed.size() > 0 ? compressed.c_str() : NULL, compressed.size());
+    CopyToMemoryBuffer(*p.target, compressed);
   }
 
 
@@ -4675,8 +4675,8 @@
 
       ServerContext::StoreResult result = lock.GetContext().AdoptAttachment(resultPublicId, *dicom, StoreInstanceMode_Default, adoptedFile);
 
-      CopyToMemoryBuffer(*parameters.attachmentUuid, adoptedFile.GetUuid().size() > 0 ? adoptedFile.GetUuid().c_str() : NULL, adoptedFile.GetUuid().size());
-      CopyToMemoryBuffer(*parameters.createdResourceId, resultPublicId.size() > 0 ? resultPublicId.c_str() : NULL, resultPublicId.size());
+      CopyToMemoryBuffer(*parameters.attachmentUuid, adoptedFile.GetUuid());
+      CopyToMemoryBuffer(*parameters.createdResourceId, resultPublicId);
       *(parameters.storeStatus) = Plugins::Convert(result.GetStatus());
     }
   }
@@ -4689,7 +4689,7 @@
     
     if (lock.GetContext().GetIndex().GetAttachment(fileInfo, revision, parameters.attachmentUuid))
     {
-      CopyToMemoryBuffer(*parameters.customData, fileInfo.GetCustomData().size() > 0 ? fileInfo.GetCustomData().c_str() : NULL, fileInfo.GetCustomData().size());
+      CopyToMemoryBuffer(*parameters.customData, fileInfo.GetCustomData());
     }
     else
     {
@@ -4709,22 +4709,18 @@
   bool OrthancPlugins::HasKeyValueStoresSupport()
   {
     PImpl::ServerContextReference lock(*pimpl_);
-
     return lock.GetContext().GetIndex().HasKeyValueStoresSupport();
   }
 
   void OrthancPlugins::ApplyStoreKeyValue(const _OrthancPluginStoreKeyValue& parameters)
   {
     PImpl::ServerContextReference lock(*pimpl_);
-    std::string value(reinterpret_cast<const char*>(parameters.value), parameters.valueSize);
-
-    lock.GetContext().GetIndex().StoreKeyValue(parameters.storeId, parameters.key, value);
+    lock.GetContext().GetIndex().StoreKeyValue(parameters.storeId, parameters.key, parameters.value, parameters.valueSize);
   }
 
   void OrthancPlugins::ApplyDeleteKeyValue(const _OrthancPluginDeleteKeyValue& parameters)
   {
     PImpl::ServerContextReference lock(*pimpl_);
-
     lock.GetContext().GetIndex().DeleteKeyValue(parameters.storeId, parameters.key);
   }
 
@@ -4736,7 +4732,7 @@
 
     if (lock.GetContext().GetIndex().GetKeyValue(value, parameters.storeId, parameters.key))
     {
-      CopyToMemoryBuffer(*parameters.target, value.size() > 0 ? value.c_str() : NULL, value.size());
+      CopyToMemoryBuffer(*parameters.target, value);
       *parameters.found = true;
     }
     else
@@ -4748,16 +4744,13 @@
   bool OrthancPlugins::HasQueuesSupport()
   {
     PImpl::ServerContextReference lock(*pimpl_);
-
     return lock.GetContext().GetIndex().HasQueuesSupport();
   }
 
   void OrthancPlugins::ApplyEnqueueValue(const _OrthancPluginEnqueueValue& parameters)
   {
     PImpl::ServerContextReference lock(*pimpl_);
-    std::string value(reinterpret_cast<const char*>(parameters.value), parameters.valueSize);
-
-    lock.GetContext().GetIndex().EnqueueValue(parameters.queueId, value);
+    lock.GetContext().GetIndex().EnqueueValue(parameters.queueId, parameters.value, parameters.valueSize);
   }
 
   void OrthancPlugins::ApplyDequeueValue(const _OrthancPluginDequeueValue& parameters)
@@ -4768,7 +4761,7 @@
 
     if (lock.GetContext().GetIndex().DequeueValue(value, parameters.queueId, Plugins::Convert(parameters.origin)))
     {
-      CopyToMemoryBuffer(*parameters.target, value.size() > 0 ? value.c_str() : NULL, value.size());
+      CopyToMemoryBuffer(*parameters.target, value);
       *parameters.found = true;
     }
     else
@@ -5232,7 +5225,7 @@
 
         std::string content;
         SystemToolbox::ReadFile(content, p.path);
-        CopyToMemoryBuffer(*p.target, content.size() > 0 ? content.c_str() : NULL, content.size());
+        CopyToMemoryBuffer(*p.target, content);
 
         return true;
       }
@@ -5962,6 +5955,20 @@
         }
 
       case _OrthancPluginService_KeysValuesIteratorGetKey:
+        if (!HasKeyValueStoresSupport())
+        {
+          throw OrthancException(ErrorCode_NotImplemented, "The DB engine does not support key-value stores");
+        }
+        else
+        {
+          const _OrthancPluginKeysValuesIteratorGetKey& p =
+            *reinterpret_cast<const _OrthancPluginKeysValuesIteratorGetKey*>(parameters);
+
+          StatelessDatabaseOperations::KeysValuesIterator& iterator = *reinterpret_cast<StatelessDatabaseOperations::KeysValuesIterator*>(p.iterator);
+          *p.target = iterator.GetKey().c_str();
+          return true;
+        }
+
       case _OrthancPluginService_KeysValuesIteratorGetValue:
         if (!HasKeyValueStoresSupport())
         {
@@ -5969,25 +5976,12 @@
         }
         else
         {
-          const _OrthancPluginKeysValuesIteratorGetString& p =
-            *reinterpret_cast<const _OrthancPluginKeysValuesIteratorGetString*>(parameters);
+          const _OrthancPluginKeysValuesIteratorGetValue& p =
+            *reinterpret_cast<const _OrthancPluginKeysValuesIteratorGetValue*>(parameters);
 
           StatelessDatabaseOperations::KeysValuesIterator& iterator = *reinterpret_cast<StatelessDatabaseOperations::KeysValuesIterator*>(p.iterator);
-
-          if (service == _OrthancPluginService_KeysValuesIteratorGetKey)
-          {
-            *p.target = iterator.GetKey().c_str();
-            return true;
-          }
-          else if (service == _OrthancPluginService_KeysValuesIteratorGetValue)
-          {
-            *p.target = iterator.GetValue().c_str();
-            return true;
-          }
-          else
-          {
-            throw OrthancException(ErrorCode_InternalError);
-          }
+          CopyToMemoryBuffer(*p.target, iterator.GetValue());
+          return true;
         }
 
       case _OrthancPluginService_EnqueueValue:
--- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Fri May 30 14:28:41 2025 +0200
+++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Fri May 30 15:38:47 2025 +0200
@@ -484,7 +484,6 @@
     _OrthancPluginService_DequeueValue = 58,                        /* New in Orthanc 1.12.8 */
     _OrthancPluginService_GetQueueSize = 59,                        /* New in Orthanc 1.12.8 */
 
-
     /* Registration of callbacks */
     _OrthancPluginService_RegisterRestCallback = 1000,
     _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001,
@@ -10100,7 +10099,7 @@
   {
     const char**                      target;
     OrthancPluginKeysValuesIterator*  iterator;
-  } _OrthancPluginKeysValuesIteratorGetString;
+  } _OrthancPluginKeysValuesIteratorGetKey;
 
   /**
    * @brief Get the current key of an iterator over a key-value store.
@@ -10118,7 +10117,7 @@
   {
     const char* target = NULL;
 
-    _OrthancPluginKeysValuesIteratorGetString params;
+    _OrthancPluginKeysValuesIteratorGetKey params;
     params.target = &target;
     params.iterator = iterator;
 
@@ -10133,6 +10132,12 @@
   }
 
 
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*        target;
+    OrthancPluginKeysValuesIterator*  iterator;
+  } _OrthancPluginKeysValuesIteratorGetValue;
+
   /**
    * @brief Get the current value of an iterator over a key-value store.
    *
@@ -10140,27 +10145,21 @@
    * must have been called at least once.
    *
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target Memory buffer where to store the value that has been retrieved from the key-value store.
+   * It must be freed with OrthancPluginFreeMemoryBuffer().
    * @param iterator The iterator of interest.
    * @return The current value, or NULL in the case of an error.
    **/
-  ORTHANC_PLUGIN_INLINE const char* OrthancPluginKeysValuesIteratorGetValue(
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginKeysValuesIteratorGetValue(
     OrthancPluginContext*             context,
-    OrthancPluginKeysValuesIterator*  iterator)
-  {
-    const char* target = NULL;
-
-    _OrthancPluginKeysValuesIteratorGetString params;
-    params.target = &target;
+    OrthancPluginMemoryBuffer*        target   /* out */,
+    OrthancPluginKeysValuesIterator*  iterator /* in */)
+  {
+    _OrthancPluginKeysValuesIteratorGetValue params;
+    params.target = target;
     params.iterator = iterator;
 
-    if (context->InvokeService(context, _OrthancPluginService_KeysValuesIteratorGetValue, &params) == OrthancPluginErrorCode_Success)
-    {
-      return target;
-    }
-    else
-    {
-      return NULL;
-    }
+    return context->InvokeService(context, _OrthancPluginService_KeysValuesIteratorGetValue, &params);
   }
 
 
@@ -10209,6 +10208,7 @@
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
    * @param found Pointer to a Boolean that is set to "true" iff. a value has been dequeued.
    * @param target Memory buffer where to store the value that has been retrieved from the queue.
+   * It must be freed with OrthancPluginFreeMemoryBuffer().
    * @param queueId A unique identifier identifying both the plugin and the queue.
    * @param origin The position from where the value is dequeued (back for LIFO, front for FIFO).
    * @return 0 if success, other value if error.
--- a/OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto	Fri May 30 14:28:41 2025 +0200
+++ b/OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto	Fri May 30 15:38:47 2025 +0200
@@ -997,7 +997,7 @@
   message Request {
     string store_id = 1;
     string key = 2;
-    string value = 3;
+    bytes value = 3;
   }
 
   message Response {
@@ -1022,7 +1022,7 @@
 
   message Response {
     bool found = 1;
-    string value = 2;
+    bytes value = 2;
   }
 }
 
@@ -1037,7 +1037,7 @@
   message Response {
     message KeyValue {
       string key = 1;
-      string value = 2;
+      bytes value = 2;
     }
     repeated KeyValue keys_values = 1;
   }
@@ -1046,7 +1046,7 @@
 message EnqueueValue {
   message Request {
     string queue_id = 1;
-    string value = 2;
+    bytes value = 2;
   }
 
   message Response {
@@ -1061,7 +1061,7 @@
 
   message Response {
     bool found = 1;
-    string value = 2;
+    bytes value = 2;
   }
 }
 
--- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Fri May 30 14:28:41 2025 +0200
+++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Fri May 30 15:38:47 2025 +0200
@@ -4404,16 +4404,18 @@
 
 
 #if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1
-  std::string KeyValueStore::Iterator::GetValue() const
-  {
-    const char* s = OrthancPluginKeysValuesIteratorGetValue(OrthancPlugins::GetGlobalContext(), iterator_);
-    if (s == NULL)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+  void KeyValueStore::Iterator::GetValue(std::string& value) const
+  {
+    OrthancPlugins::MemoryBuffer valueBuffer;
+    OrthancPluginErrorCode code = OrthancPluginKeysValuesIteratorGetValue(OrthancPlugins::GetGlobalContext(), *valueBuffer, iterator_);
+
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
     }
     else
     {
-      return s;
+      valueBuffer.ToString(value);
     }
   }
 #endif
--- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Fri May 30 14:28:41 2025 +0200
+++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Fri May 30 15:38:47 2025 +0200
@@ -1646,7 +1646,7 @@
 
       std::string GetKey() const;
 
-      std::string GetValue() const;
+      void GetValue(std::string& target) const;
     };
 
   private:
--- a/OrthancServer/Sources/Database/IDatabaseWrapper.h	Fri May 30 14:28:41 2025 +0200
+++ b/OrthancServer/Sources/Database/IDatabaseWrapper.h	Fri May 30 15:38:47 2025 +0200
@@ -437,7 +437,8 @@
       // New in Orthanc 1.12.8
       virtual void StoreKeyValue(const std::string& storeId,
                                  const std::string& key,
-                                 const std::string& value) = 0;
+                                 const void* value,
+                                 size_t valueSize) = 0;
 
       // New in Orthanc 1.12.8
       virtual void DeleteKeyValue(const std::string& storeId,
@@ -453,12 +454,13 @@
                                   std::list<std::string>& values /* out */,
                                   const std::string& storeId,
                                   bool first,
-                                  const std::string& from /* only used if "first == false" */,
+                                  const std::string& from /* exclusive bound, only used if "first == false" */,
                                   uint64_t limit /* maximum number of elements */) = 0;
 
       // New in Orthanc 1.12.8
       virtual void EnqueueValue(const std::string& queueId,
-                                const std::string& value) = 0;
+                                const void* value,
+                                size_t valueSize) = 0;
 
       // New in Orthanc 1.12.8
       virtual bool DequeueValue(std::string& value,
--- a/OrthancServer/Sources/Database/InstallKeyValueStoresAndQueues.sql	Fri May 30 14:28:41 2025 +0200
+++ b/OrthancServer/Sources/Database/InstallKeyValueStoresAndQueues.sql	Fri May 30 15:38:47 2025 +0200
@@ -22,14 +22,14 @@
 CREATE TABLE KeyValueStores(
        storeId TEXT NOT NULL,
        key TEXT NOT NULL,
-       value TEXT NOT NULL,
+       value BLOB NOT NULL,
        PRIMARY KEY(storeId, key)  -- Prevents duplicates
        );
 
 CREATE TABLE Queues (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        queueId TEXT NOT NULL,
-       value TEXT
+       value BLOB
 );
 
 CREATE INDEX QueuesIndex ON Queues (queueId, id);
--- a/OrthancServer/Sources/Database/PrepareDatabase.sql	Fri May 30 14:28:41 2025 +0200
+++ b/OrthancServer/Sources/Database/PrepareDatabase.sql	Fri May 30 15:38:47 2025 +0200
@@ -207,14 +207,14 @@
 CREATE TABLE KeyValueStores(
        storeId TEXT NOT NULL,
        key TEXT NOT NULL,
-       value TEXT NOT NULL,
+       value BLOB NOT NULL,
        PRIMARY KEY(storeId, key)  -- Prevents duplicates
        );
 
 CREATE TABLE Queues (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        queueId TEXT NOT NULL,
-       value TEXT
+       value BLOB
 );
 
 CREATE INDEX QueuesIndex ON Queues (queueId, id);
--- a/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp	Fri May 30 14:28:41 2025 +0200
+++ b/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp	Fri May 30 15:38:47 2025 +0200
@@ -2142,12 +2142,13 @@
 
     virtual void StoreKeyValue(const std::string& storeId,
                                const std::string& key,
-                               const std::string& value) ORTHANC_OVERRIDE
+                               const void* value,
+                               size_t valueSize) ORTHANC_OVERRIDE
     {
       SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO KeyValueStores (storeId, key, value) VALUES(?, ?, ?)");
       s.BindString(0, storeId);
       s.BindString(1, key);
-      s.BindString(2, value);
+      s.BindBlob(2, value, valueSize);
       s.Run();
     }
 
@@ -2221,12 +2222,18 @@
 
     // New in Orthanc 1.12.8
     virtual void EnqueueValue(const std::string& queueId,
-                              const std::string& value) ORTHANC_OVERRIDE
+                              const void* value,
+                              size_t valueSize) ORTHANC_OVERRIDE
     {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+      if (static_cast<size_t>(static_cast<int>(valueSize)) != valueSize)
+      {
+        throw OrthancException(ErrorCode_NotEnoughMemory, "Value is too large for a SQLite database");
+      }
+
+      SQLite::Statement s(db_, SQLITE_FROM_HERE,
                           "INSERT INTO Queues (queueId, value) VALUES (?, ?)");
       s.BindString(0, queueId);
-      s.BindString(1, value);
+      s.BindBlob(1, value, valueSize);
       s.Run();
     }
 
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Fri May 30 14:28:41 2025 +0200
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Fri May 30 15:38:47 2025 +0200
@@ -3335,32 +3335,42 @@
 
   void StatelessDatabaseOperations::StoreKeyValue(const std::string& storeId,
                                                   const std::string& key,
-                                                  const std::string& value)
+                                                  const void* value,
+                                                  size_t valueSize)
   {
+    if (value == NULL &&
+        valueSize > 0)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+
     class Operations : public IReadWriteOperations
     {
     private:
       const std::string& storeId_;
       const std::string& key_;
-      const std::string& value_;
+      const void* value_;
+      size_t valueSize_;
 
     public:
       Operations(const std::string& storeId,
                  const std::string& key,
-                 const std::string& value) :
+                 const void* value,
+                 size_t valueSize) :
         storeId_(storeId),
         key_(key),
-        value_(value)
+        value_(value),
+        valueSize_(valueSize)
       {
       }
 
       virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE
       {
-        transaction.StoreKeyValue(storeId_, key_, value_);
+        transaction.StoreKeyValue(storeId_, key_, value_, valueSize_);
       }
     };
 
-    Operations operations(storeId, key, value);
+    Operations operations(storeId, key, value, valueSize);
     Apply(operations);
   }
 
@@ -3422,29 +3432,39 @@
   }
 
   void StatelessDatabaseOperations::EnqueueValue(const std::string& queueId,
-                                                 const std::string& value)
+                                                 const void* value,
+                                                 size_t valueSize)
   {
+    if (value == NULL &&
+        valueSize > 0)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+
     class Operations : public IReadWriteOperations
     {
     private:
       const std::string& queueId_;
-      const std::string& value_;
+      const void* value_;
+      size_t valueSize_;
 
     public:
       Operations(const std::string& queueId,
-                 const std::string& value) :
+                 const void* value,
+                 size_t valueSize) :
         queueId_(queueId),
-        value_(value)
+        value_(value),
+        valueSize_(valueSize)
       {
       }
 
       virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE
       {
-        transaction.EnqueueValue(queueId_, value_);
+        transaction.EnqueueValue(queueId_, value_, valueSize_);
       }
     };
 
-    Operations operations(queueId, value);
+    Operations operations(queueId, value, valueSize);
     Apply(operations);
   }
 
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.h	Fri May 30 14:28:41 2025 +0200
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.h	Fri May 30 15:38:47 2025 +0200
@@ -460,9 +460,10 @@
 
       void StoreKeyValue(const std::string& storeId,
                          const std::string& key,
-                         const std::string& value)
+                         const void* value,
+                         size_t valueSize)
       {
-        transaction_.StoreKeyValue(storeId, key, value);
+        transaction_.StoreKeyValue(storeId, key, value, valueSize);
       }
 
       void DeleteKeyValue(const std::string& storeId,
@@ -472,9 +473,10 @@
       }
 
       void EnqueueValue(const std::string& queueId,
-                        const std::string& value)
+                        const void* value,
+                        size_t valueSize)
       {
-        transaction_.EnqueueValue(queueId, value);
+        transaction_.EnqueueValue(queueId, value, valueSize);
       }
 
       bool DequeueValue(std::string& value,
@@ -800,7 +802,15 @@
 
     void StoreKeyValue(const std::string& storeId,
                        const std::string& key,
-                       const std::string& value);
+                       const void* value,
+                       size_t valueSize);
+
+    void StoreKeyValue(const std::string& storeId,
+                       const std::string& key,
+                       const std::string& value)
+    {
+      StoreKeyValue(storeId, key, value.empty() ? NULL : value.c_str(), value.size());
+    }
 
     void DeleteKeyValue(const std::string& storeId,
                         const std::string& key);
@@ -810,7 +820,14 @@
                      const std::string& key);
 
     void EnqueueValue(const std::string& queueId,
-                      const std::string& value);
+                      const void* value,
+                      size_t valueSize);
+
+    void EnqueueValue(const std::string& queueId,
+                      const std::string& value)
+    {
+      EnqueueValue(queueId, value.empty() ? NULL : value.c_str(), value.size());
+    }
 
     bool DequeueValue(std::string& value,
                       const std::string& queueId,
--- a/OrthancServer/UnitTestsSources/ServerIndexTests.cpp	Fri May 30 14:28:41 2025 +0200
+++ b/OrthancServer/UnitTestsSources/ServerIndexTests.cpp	Fri May 30 15:38:47 2025 +0200
@@ -1218,6 +1218,24 @@
       it.SetLimit(limit);
       ASSERT_FALSE(it.Next());
     }
+
+    {
+      std::string blob;
+      blob.push_back(0);
+      blob.push_back(1);
+      blob.push_back(0);
+      blob.push_back(2);
+      op.StoreKeyValue("test", "blob", blob); // Storing binary values
+    }
+
+    ASSERT_TRUE(op.GetKeyValue(s, "test", "blob"));
+    ASSERT_EQ(4u, s.size());
+    ASSERT_EQ(0, static_cast<uint8_t>(s[0]));
+    ASSERT_EQ(1, static_cast<uint8_t>(s[1]));
+    ASSERT_EQ(0, static_cast<uint8_t>(s[2]));
+    ASSERT_EQ(2, static_cast<uint8_t>(s[3]));
+    op.DeleteKeyValue("test", "blob");
+    ASSERT_FALSE(op.GetKeyValue(s, "test", "blob"));
   }
 
   db.Close();
@@ -1241,14 +1259,37 @@
 
     std::string s;
     ASSERT_TRUE(op.DequeueValue(s, "test", QueueOrigin_Back));  ASSERT_EQ("world", s);
+    ASSERT_EQ(1u, op.GetQueueSize("test"));
     ASSERT_TRUE(op.DequeueValue(s, "test", QueueOrigin_Back));  ASSERT_EQ("hello", s);
+    ASSERT_EQ(0u, op.GetQueueSize("test"));
     ASSERT_FALSE(op.DequeueValue(s, "test", QueueOrigin_Back));
 
     op.EnqueueValue("test", "hello");
     op.EnqueueValue("test", "world");
+    ASSERT_EQ(2u, op.GetQueueSize("test"));
 
     ASSERT_TRUE(op.DequeueValue(s, "test", QueueOrigin_Front));  ASSERT_EQ("hello", s);
     ASSERT_TRUE(op.DequeueValue(s, "test", QueueOrigin_Front));  ASSERT_EQ("world", s);
+    ASSERT_EQ(0u, op.GetQueueSize("test"));
+    ASSERT_FALSE(op.DequeueValue(s, "test", QueueOrigin_Front));
+
+    {
+      std::string blob;
+      blob.push_back(0);
+      blob.push_back(1);
+      blob.push_back(0);
+      blob.push_back(2);
+      op.EnqueueValue("test", blob); // Storing binary values
+    }
+
+    ASSERT_EQ(1u, op.GetQueueSize("test"));
+    ASSERT_TRUE(op.DequeueValue(s, "test", QueueOrigin_Front));
+    ASSERT_EQ(0u, op.GetQueueSize("test"));
+    ASSERT_EQ(4u, s.size());
+    ASSERT_EQ(0, static_cast<uint8_t>(s[0]));
+    ASSERT_EQ(1, static_cast<uint8_t>(s[1]));
+    ASSERT_EQ(0, static_cast<uint8_t>(s[2]));
+    ASSERT_EQ(2, static_cast<uint8_t>(s[3]));
     ASSERT_FALSE(op.DequeueValue(s, "test", QueueOrigin_Front));
   }