Mercurial > hg > orthanc
changeset 6102:b1764e7248e0 attach-custom-data
wip: key-value store for plugins
line wrap: on
line diff
--- a/NEWS Mon Apr 07 13:23:11 2025 +0200 +++ b/NEWS Wed May 07 19:27:06 2025 +0200 @@ -13,7 +13,7 @@ Plugins ------- -* New database plugin SDK (vX) to handle customData for attachments. +* New database plugin SDK (vX) to handle customData for attachments and key-value store. * New storage plugin SDK (v3) to handle customData for attachments.
--- a/OrthancServer/CMakeLists.txt Mon Apr 07 13:23:11 2025 +0200 +++ b/OrthancServer/CMakeLists.txt Wed May 07 19:27:06 2025 +0200 @@ -253,6 +253,7 @@ INSTALL_TRACK_ATTACHMENTS_SIZE ${CMAKE_SOURCE_DIR}/Sources/Database/InstallTrackAttachmentsSize.sql INSTALL_LABELS_TABLE ${CMAKE_SOURCE_DIR}/Sources/Database/InstallLabelsTable.sql INSTALL_REVISION_AND_CUSTOM_DATA ${CMAKE_SOURCE_DIR}/Sources/Database/InstallRevisionAndCustomData.sql + INSTALL_KEY_VALUE_STORE ${CMAKE_SOURCE_DIR}/Sources/Database/InstallKeyValueStore.sql ) if (STANDALONE_BUILD)
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp Mon Apr 07 13:23:11 2025 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp Wed May 07 19:27:06 2025 +0200 @@ -1450,6 +1450,27 @@ { throw OrthancException(ErrorCode_InternalError); // Not supported } + + virtual void StoreKeyValue(const std::string& pluginId, + const std::string& key, + const std::string& value) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_InternalError); // Not supported + } + + virtual void DeleteKeyValue(const std::string& pluginId, + const std::string& key) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_InternalError); // Not supported + } + + virtual bool GetKeyValue(std::string& value, + const std::string& pluginId, + const std::string& key) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_InternalError); // Not supported + } + };
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp Mon Apr 07 13:23:11 2025 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV3.cpp Wed May 07 19:27:06 2025 +0200 @@ -1063,6 +1063,27 @@ { throw OrthancException(ErrorCode_InternalError); // Not supported } + + virtual void StoreKeyValue(const std::string& pluginId, + const std::string& key, + const std::string& value) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_InternalError); // Not supported + } + + virtual void DeleteKeyValue(const std::string& pluginId, + const std::string& key) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_InternalError); // Not supported + } + + virtual bool GetKeyValue(std::string& value, + const std::string& pluginId, + const std::string& key) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_InternalError); // Not supported + } + };
--- a/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp Mon Apr 07 13:23:11 2025 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPluginDatabaseV4.cpp Wed May 07 19:27:06 2025 +0200 @@ -1807,6 +1807,27 @@ find.ExecuteExpand(response, capabilities, request, identifier); } } + + virtual void StoreKeyValue(const std::string& pluginId, + const std::string& key, + const std::string& value) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_NotImplemented); // TODO_ATTACH_CUSTOM_DATA + } + + virtual void DeleteKeyValue(const std::string& pluginId, + const std::string& key) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_NotImplemented); // TODO_ATTACH_CUSTOM_DATA + } + + virtual bool GetKeyValue(std::string& value, + const std::string& pluginId, + const std::string& key) ORTHANC_OVERRIDE + { + throw OrthancException(ErrorCode_NotImplemented); // TODO_ATTACH_CUSTOM_DATA + } + }; @@ -1897,6 +1918,7 @@ dbCapabilities_.SetMeasureLatency(systemInfo.has_measure_latency()); dbCapabilities_.SetHasExtendedChanges(systemInfo.has_extended_changes()); dbCapabilities_.SetHasFindSupport(systemInfo.supports_find()); + dbCapabilities_.SetHasKeyValueStore(systemInfo.has_key_value_store()); } open_ = true;
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Mon Apr 07 13:23:11 2025 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Wed May 07 19:27:06 2025 +0200 @@ -4675,7 +4675,48 @@ ServerContext::StoreResult result = lock.GetContext().AdoptAttachment(resultPublicId, *dicom, StoreInstanceMode_Default, adoptedFile); - // TODO_ATTACH_CUSTOM_DATA: handle result + 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()); + *(parameters.storeStatus) = Plugins::Convert(result.GetStatus()); + } + } + + bool OrthancPlugins::HasKeyValueStore() + { + PImpl::ServerContextReference lock(*pimpl_); + + return lock.GetContext().GetIndex().HasKeyValueStore(); + } + + void OrthancPlugins::ApplyStoreKeyValue(const _OrthancPluginStoreKeyValue& parameters) + { + PImpl::ServerContextReference lock(*pimpl_); + std::string value(parameters.value, parameters.valueSize); + + lock.GetContext().GetIndex().StoreKeyValue(parameters.pluginIdentifier, parameters.key, value); + } + + void OrthancPlugins::ApplyDeleteKeyValue(const _OrthancPluginDeleteKeyValue& parameters) + { + PImpl::ServerContextReference lock(*pimpl_); + + lock.GetContext().GetIndex().DeleteKeyValue(parameters.pluginIdentifier, parameters.key); + } + + bool OrthancPlugins::ApplyGetKeyValue(const _OrthancPluginGetKeyValue& parameters) + { + PImpl::ServerContextReference lock(*pimpl_); + + std::string value; + + if (lock.GetContext().GetIndex().GetKeyValue(value, parameters.pluginIdentifier, parameters.key)) + { + CopyToMemoryBuffer(*parameters.value, value.size() > 0 ? value.c_str() : NULL, value.size()); + return true; + } + else + { + return false; } } @@ -5755,6 +5796,54 @@ return true; } + case _OrthancPluginService_StoreKeyValue: + { + if (!HasKeyValueStore()) + { + LOG(ERROR) << "The DB engine does not support Key Value Store"; + return false; + } + + const _OrthancPluginStoreKeyValue& p = + *reinterpret_cast<const _OrthancPluginStoreKeyValue*>(parameters); + ApplyStoreKeyValue(p); + return true; + } + + case _OrthancPluginService_DeleteKeyValue: + { + if (!HasKeyValueStore()) + { + LOG(ERROR) << "The DB engine does not support Key Value Store"; + return false; + } + + const _OrthancPluginDeleteKeyValue& p = + *reinterpret_cast<const _OrthancPluginDeleteKeyValue*>(parameters); + ApplyDeleteKeyValue(p); + return true; + } + + case _OrthancPluginService_GetKeyValue: + { + if (!HasKeyValueStore()) + { + LOG(ERROR) << "The DB engine does not support Key Value Store"; + return false; + } + + const _OrthancPluginGetKeyValue& p = + *reinterpret_cast<const _OrthancPluginGetKeyValue*>(parameters); + if (ApplyGetKeyValue(p)) + { + return true; + } + else + { + throw OrthancException(ErrorCode_UnknownResource); + } + } + default: return false; }
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.h Mon Apr 07 13:23:11 2025 +0200 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.h Wed May 07 19:27:06 2025 +0200 @@ -225,6 +225,14 @@ void ApplyAdoptAttachment(const _OrthancPluginAdoptAttachment& parameters); + bool HasKeyValueStore(); + + void ApplyStoreKeyValue(const _OrthancPluginStoreKeyValue& parameters); + + void ApplyDeleteKeyValue(const _OrthancPluginDeleteKeyValue& parameters); + + bool ApplyGetKeyValue(const _OrthancPluginGetKeyValue& parameters); + void ComputeHash(_OrthancPluginService service, const void* parameters);
--- a/OrthancServer/Plugins/Engine/PluginsEnumerations.cpp Mon Apr 07 13:23:11 2025 +0200 +++ b/OrthancServer/Plugins/Engine/PluginsEnumerations.cpp Wed May 07 19:27:06 2025 +0200 @@ -696,5 +696,54 @@ } } + OrthancPluginStoreStatus Convert(StoreStatus status) + { + switch (status) + { + case StoreStatus_Success: + return OrthancPluginStoreStatus_Success; + + case StoreStatus_AlreadyStored: + return OrthancPluginStoreStatus_AlreadyStored; + + case StoreStatus_Failure: + return OrthancPluginStoreStatus_Failure; + + case StoreStatus_FilteredOut: + return OrthancPluginStoreStatus_FilteredOut; + + case StoreStatus_StorageFull: + return OrthancPluginStoreStatus_StorageFull; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + StoreStatus Convert(OrthancPluginStoreStatus status) + { + switch (status) + { + case OrthancPluginStoreStatus_Success: + return StoreStatus_Success; + + case OrthancPluginStoreStatus_AlreadyStored: + return StoreStatus_AlreadyStored; + + case OrthancPluginStoreStatus_Failure: + return StoreStatus_Failure; + + case OrthancPluginStoreStatus_FilteredOut: + return StoreStatus_FilteredOut; + + case OrthancPluginStoreStatus_StorageFull: + return StoreStatus_StorageFull; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + } }
--- a/OrthancServer/Plugins/Engine/PluginsEnumerations.h Mon Apr 07 13:23:11 2025 +0200 +++ b/OrthancServer/Plugins/Engine/PluginsEnumerations.h Wed May 07 19:27:06 2025 +0200 @@ -77,6 +77,10 @@ OrthancPluginCompressionType Convert(CompressionType type); CompressionType Convert(OrthancPluginCompressionType type); + + OrthancPluginStoreStatus Convert(StoreStatus type); + + StoreStatus Convert(OrthancPluginStoreStatus type); } }
--- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Mon Apr 07 13:23:11 2025 +0200 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Wed May 07 19:27:06 2025 +0200 @@ -469,7 +469,10 @@ _OrthancPluginService_SetMetricsIntegerValue = 43, /* New in Orthanc 1.12.1 */ _OrthancPluginService_SetCurrentThreadName = 44, /* New in Orthanc 1.12.2 */ _OrthancPluginService_LogMessage = 45, /* New in Orthanc 1.12.4 */ - _OrthancPluginService_AdoptAttachment = 46, /* New in Orthanc 99 */ + _OrthancPluginService_AdoptAttachment = 46, /* New in Orthanc 1.12.99 */ + _OrthancPluginService_StoreKeyValue = 47, /* New in Orthanc 1.12.99 */ + _OrthancPluginService_DeleteKeyValue = 48, /* New in Orthanc 1.12.99 */ + _OrthancPluginService_GetKeyValue = 49, /* New in Orthanc 1.12.99 */ /* Registration of callbacks */ @@ -1133,6 +1136,18 @@ /** + * The store status response to AdoptAttachment. + **/ + typedef enum + { + OrthancPluginStoreStatus_Success = 0, /*!< The file has been stored/adopted */ + OrthancPluginStoreStatus_AlreadyStored = 1, /*!< The file has already been stored/adopted (only if OverwriteInstances is set to false)*/ + OrthancPluginStoreStatus_Failure = 2, /*!< The file could not be stored/adopted */ + OrthancPluginStoreStatus_FilteredOut = 3, /*!< The file has been filtered out by a lua script or a plugin */ + OrthancPluginStoreStatus_StorageFull = 4 /*!< The storage is full (only if MaximumStorageSize/MaximumPatientCount is set and MaximumStorageMode is Reject)*/ + } OrthancPluginStoreStatus; + + /** * @brief A 32-bit memory buffer allocated by the core system of Orthanc. * * A memory buffer allocated by the core system of Orthanc. When the @@ -9772,6 +9787,7 @@ const char* attachToResourceId; /* in */ OrthancPluginMemoryBuffer* createdResourceId; /* out */ OrthancPluginMemoryBuffer* attachmentUuid; /* out */ + OrthancPluginStoreStatus* storeStatus; /* out */ } _OrthancPluginAdoptAttachment; /** @@ -9789,7 +9805,9 @@ OrthancPluginResourceType attachToResourceType, const char* attachToResourceId, OrthancPluginMemoryBuffer* createdResourceId, /* out */ - OrthancPluginMemoryBuffer* attachmentUuid) /* out */ + OrthancPluginMemoryBuffer* attachmentUuid, /* out */ + OrthancPluginStoreStatus* storeStatus /* out */ + ) { _OrthancPluginAdoptAttachment params; params.buffer = buffer; @@ -9800,10 +9818,92 @@ params.attachToResourceId = attachToResourceId; params.createdResourceId = createdResourceId; params.attachmentUuid = attachmentUuid; + params.storeStatus = storeStatus; return context->InvokeService(context, _OrthancPluginService_AdoptAttachment, ¶ms); } + typedef struct + { + const char* pluginIdentifier; + const char* key; + const char* value; + uint64_t valueSize; + } _OrthancPluginStoreKeyValue; + + /** + * @brief Tell Orthanc to store a key-value in its store. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). +TODO_ATTACH_CUSTOM_DATA TODO TODO + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStoreKeyValue( + OrthancPluginContext* context, + const char* pluginIdentifier, /* in */ + const char* key, /* in */ + const char* value, /* in */ + uint64_t valueSize /* in */) + { + _OrthancPluginStoreKeyValue params; + params.pluginIdentifier = pluginIdentifier; + params.key = key; + params.value = value; + params.valueSize = valueSize; + + return context->InvokeService(context, _OrthancPluginService_StoreKeyValue, ¶ms); + } + + typedef struct + { + const char* pluginIdentifier; + const char* key; + } _OrthancPluginDeleteKeyValue; + + /** + * @brief Tell Orthanc to delete a key-value from its store. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). +TODO_ATTACH_CUSTOM_DATA TODO TODO + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginDeleteKeyValue( + OrthancPluginContext* context, + const char* pluginIdentifier, /* in */ + const char* key /* in */) + { + _OrthancPluginDeleteKeyValue params; + params.pluginIdentifier = pluginIdentifier; + params.key = key; + + return context->InvokeService(context, _OrthancPluginService_DeleteKeyValue, ¶ms); + } + + typedef struct + { + const char* pluginIdentifier; + const char* key; + OrthancPluginMemoryBuffer* value; + } _OrthancPluginGetKeyValue; + + /** + * @brief Get the value associated to this key in the key-value store. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). +TODO_ATTACH_CUSTOM_DATA TODO TODO + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginGetKeyValue( + OrthancPluginContext* context, + const char* pluginIdentifier, /* in */ + const char* key, /* in */ + OrthancPluginMemoryBuffer* value /* out */) + { + _OrthancPluginGetKeyValue params; + params.pluginIdentifier = pluginIdentifier; + params.key = key; + params.value = value; + + return context->InvokeService(context, _OrthancPluginService_GetKeyValue, ¶ms); + } + #ifdef __cplusplus } #endif
--- a/OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto Mon Apr 07 13:23:11 2025 +0200 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancDatabasePlugin.proto Wed May 07 19:27:06 2025 +0200 @@ -167,6 +167,7 @@ bool has_measure_latency = 7; bool supports_find = 8; // New in Orthanc 1.12.5 bool has_extended_changes = 9; // New in Orthanc 1.12.5 + bool has_key_value_store = 10; // New in Orthanc 1.12.99 } } @@ -322,6 +323,9 @@ OPERATION_FIND = 50; // New in Orthanc 1.12.5 OPERATION_GET_CHANGES_EXTENDED = 51; // New in Orthanc 1.12.5 OPERATION_COUNT_RESOURCES = 52; // New in Orthanc 1.12.5 + OPERATION_STORE_KEY_VALUE = 53; // New in Orthanc 1.12.99 + OPERATION_DELETE_KEY_VALUE = 54; // New in Orthanc 1.12.99 + OPERATION_GET_KEY_VALUE = 55; // New in Orthanc 1.12.99 } message Rollback { @@ -975,6 +979,39 @@ } } +message StoreKeyValue { + message Request { + string plugin_id = 1; + string key = 2; + string value = 3; + } + + message Response { + } +} + +message DeleteKeyValue { + message Request { + string plugin_id = 1; + string key = 2; + } + + message Response { + } +} + +message GetKeyValue { + message Request { + string plugin_id = 1; + string key = 2; + } + + message Response { + string value = 1; + } +} + + message TransactionRequest { sfixed64 transaction = 1; TransactionOperation operation = 2; @@ -1032,6 +1069,9 @@ Find.Request find = 150; GetChangesExtended.Request get_changes_extended = 151; Find.Request count_resources = 152; + StoreKeyValue.Request store_key_value = 153; + DeleteKeyValue.Request delete_key_value = 154; + GetKeyValue.Request get_key_value = 155; } message TransactionResponse { @@ -1088,6 +1128,9 @@ repeated Find.Response find = 150; // One message per found resource GetChangesExtended.Response get_changes_extended = 151; CountResources.Response count_resources = 152; + StoreKeyValue.Response store_key_value = 153; + DeleteKeyValue.Response delete_key_value = 154; + GetKeyValue.Response get_key_value = 155; } enum RequestType {
--- a/OrthancServer/Sources/Database/IDatabaseWrapper.h Mon Apr 07 13:23:11 2025 +0200 +++ b/OrthancServer/Sources/Database/IDatabaseWrapper.h Wed May 07 19:27:06 2025 +0200 @@ -57,6 +57,7 @@ bool hasFindSupport_; bool hasExtendedChanges_; bool hasAttachmentCustomDataSupport_; + bool hasKeyValueStore_; public: Capabilities() : @@ -68,7 +69,8 @@ hasMeasureLatency_(false), hasFindSupport_(false), hasExtendedChanges_(false), - hasAttachmentCustomDataSupport_(false) + hasAttachmentCustomDataSupport_(false), + hasKeyValueStore_(false) { } @@ -161,6 +163,16 @@ { return hasFindSupport_; } + + void SetHasKeyValueStore(bool value) + { + hasKeyValueStore_ = value; + } + + bool HasKeyValueStore() const + { + return hasKeyValueStore_; + } }; @@ -402,6 +414,20 @@ int64_t to, uint32_t limit, const std::set<ChangeType>& filterType) = 0; + + // New in Orthanc 1.12.99 + virtual void StoreKeyValue(const std::string& pluginId, + const std::string& key, + const std::string& value) = 0; + + // New in Orthanc 1.12.99 + virtual void DeleteKeyValue(const std::string& pluginId, + const std::string& key) = 0; + + // New in Orthanc 1.12.99 + virtual bool GetKeyValue(std::string& value, + const std::string& pluginId, + const std::string& key) = 0; };
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Sources/Database/InstallKeyValueStore.sql Wed May 07 19:27:06 2025 +0200 @@ -0,0 +1,29 @@ +-- 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) 2024-2025 Orthanc Team SRL, Belgium +-- Copyright (C) 2021-2025 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/>. + + +CREATE TABLE KeyValueStore( + pluginId TEXT NOT NULL, + key TEXT NOT NULL, + value TEXT NOT NULL, + PRIMARY KEY(pluginId, key) -- Prevents duplicates + ); + +CREATE INDEX KeyValueStoreIndex ON KeyValueStore (pluginId, key);
--- a/OrthancServer/Sources/Database/PrepareDatabase.sql Mon Apr 07 13:23:11 2025 +0200 +++ b/OrthancServer/Sources/Database/PrepareDatabase.sql Wed May 07 19:27:06 2025 +0200 @@ -159,6 +159,16 @@ INSERT INTO PatientRecyclingOrder VALUES (NULL, new.internalId); END; +-- new in Orthanc 1.12.99 +CREATE TABLE KeyValueStore( + pluginId TEXT NOT NULL, + key TEXT NOT NULL, + value TEXT NOT NULL, + PRIMARY KEY(pluginId, key) -- Prevents duplicates + ); + +-- new in Orthanc 1.12.99 +CREATE INDEX KeyValueStoreIndex ON KeyValueStore (pluginId, key); -- Set the version of the database schema -- The "1" corresponds to the "GlobalProperty_DatabaseSchemaVersion" enumeration
--- a/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp Mon Apr 07 13:23:11 2025 +0200 +++ b/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp Wed May 07 19:27:06 2025 +0200 @@ -2102,6 +2102,48 @@ target.insert(s.ColumnString(0)); } } + + virtual void StoreKeyValue(const std::string& pluginId, + const std::string& key, + const std::string& value) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO KeyValueStore (pluginId, key, value) VALUES(?, ?, ?)"); + s.BindString(0, pluginId); + s.BindString(1, key); + s.BindString(2, value); + s.Run(); + } + + virtual void DeleteKeyValue(const std::string& pluginId, + const std::string& key) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, "DELETE FROM KeyValueStore WHERE pluginId = ? AND key = ?"); + s.BindString(0, pluginId); + s.BindString(1, key); + s.Run(); + } + + virtual bool GetKeyValue(std::string& value, + const std::string& pluginId, + const std::string& key) ORTHANC_OVERRIDE + { + SQLite::Statement s(db_, SQLITE_FROM_HERE, + "SELECT value FROM KeyValueStore WHERE pluginId=? AND key=?"); + s.BindString(0, pluginId); + s.BindString(1, key); + + if (!s.Step()) + { + // No value found + return false; + } + else + { + value = s.ColumnString(0); + return true; + } + } + }; @@ -2296,11 +2338,12 @@ signalRemainingAncestor_(NULL), version_(0) { - // TODO: implement revisions in SQLite + dbCapabilities_.SetRevisionsSupport(true); dbCapabilities_.SetFlushToDisk(true); dbCapabilities_.SetLabelsSupport(true); dbCapabilities_.SetHasExtendedChanges(true); dbCapabilities_.SetHasFindSupport(HasIntegratedFind()); + dbCapabilities_.SetHasKeyValueStore(true); db_.Open(path); } @@ -2310,11 +2353,12 @@ signalRemainingAncestor_(NULL), version_(0) { - // TODO: implement revisions in SQLite + dbCapabilities_.SetRevisionsSupport(true); dbCapabilities_.SetFlushToDisk(true); dbCapabilities_.SetLabelsSupport(true); dbCapabilities_.SetHasExtendedChanges(true); dbCapabilities_.SetHasFindSupport(HasIntegratedFind()); + dbCapabilities_.SetHasKeyValueStore(true); db_.OpenInMemory(); } @@ -2412,11 +2456,8 @@ ServerResources::GetFileResource(query, ServerResources::INSTALL_LABELS_TABLE); db_.Execute(query); } - } - // New in Orthanc 1.12.99 - if (version_ >= 6) - { + // New in Orthanc 1.12.99 if (!transaction->LookupGlobalProperty(tmp, GlobalProperty_SQLiteHasCustomDataAndRevision, true /* unused in SQLite */) || tmp != "1") { @@ -2425,6 +2466,14 @@ ServerResources::GetFileResource(query, ServerResources::INSTALL_REVISION_AND_CUSTOM_DATA); db_.Execute(query); } + + if (!db_.DoesTableExist("KeyValueStore")) + { + LOG(INFO) << "Installing the \"KeyValueStore\" table"; + std::string query; + ServerResources::GetFileResource(query, ServerResources::INSTALL_KEY_VALUE_STORE); + db_.Execute(query); + } } transaction->Commit(0);
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Mon Apr 07 13:23:11 2025 +0200 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Wed May 07 19:27:06 2025 +0200 @@ -3200,6 +3200,12 @@ return db_.GetDatabaseCapabilities().HasFindSupport(); } + bool StatelessDatabaseOperations::HasKeyValueStore() + { + boost::shared_lock<boost::shared_mutex> lock(mutex_); + return db_.GetDatabaseCapabilities().HasKeyValueStore(); + } + void StatelessDatabaseOperations::ExecuteCount(uint64_t& count, const FindRequest& request) { @@ -3320,4 +3326,94 @@ } } } + + void StatelessDatabaseOperations::StoreKeyValue(const std::string& pluginId, + const std::string& key, + const std::string& value) + { + class Operations : public IReadWriteOperations + { + private: + const std::string& pluginId_; + const std::string& key_; + const std::string& value_; + + public: + Operations(const std::string& pluginId, + const std::string& key, + const std::string& value) : + pluginId_(pluginId), + key_(key), + value_(value) + { + } + + virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE + { + transaction.StoreKeyValue(pluginId_, key_, value_); + } + }; + + Operations operations(pluginId, key, value); + Apply(operations); + } + + void StatelessDatabaseOperations::DeleteKeyValue(const std::string& pluginId, + const std::string& key) + { + class Operations : public IReadWriteOperations + { + private: + const std::string& pluginId_; + const std::string& key_; + + public: + Operations(const std::string& pluginId, + const std::string& key) : + pluginId_(pluginId), + key_(key) + { + } + + virtual void Apply(ReadWriteTransaction& transaction) ORTHANC_OVERRIDE + { + transaction.DeleteKeyValue(pluginId_, key_); + } + }; + + Operations operations(pluginId, key); + Apply(operations); + } + + bool StatelessDatabaseOperations::GetKeyValue(std::string& value, + const std::string& pluginId, + const std::string& key) + { + class Operations : public ReadOnlyOperationsT3<std::string&, const std::string&, const std::string& > + { + bool found_; + public: + Operations(): + found_(false) + {} + + bool HasFound() + { + return found_; + } + + virtual void ApplyTuple(ReadOnlyTransaction& transaction, + const Tuple& tuple) ORTHANC_OVERRIDE + { + found_ = transaction.GetKeyValue(tuple.get<0>(), tuple.get<1>(), tuple.get<2>()); + } + }; + + Operations operations; + operations.Apply(*this, value, pluginId, key); + + return operations.HasFound(); + } + + }
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Mon Apr 07 13:23:11 2025 +0200 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Wed May 07 19:27:06 2025 +0200 @@ -293,6 +293,14 @@ { transaction_.ExecuteExpand(response, capabilities, request, identifier); } + + bool GetKeyValue(std::string& value, + const std::string& pluginId, + const std::string& key) + { + return transaction_.GetKeyValue(value, pluginId, key); + } + }; @@ -428,6 +436,20 @@ { transaction_.RemoveLabel(id, label); } + + void StoreKeyValue(const std::string& pluginId, + const std::string& key, + const std::string& value) + { + transaction_.StoreKeyValue(pluginId, key, value); + } + + void DeleteKeyValue(const std::string& pluginId, + const std::string& key) + { + transaction_.DeleteKeyValue(pluginId, key); + } + }; @@ -544,6 +566,8 @@ bool HasExtendedChanges(); bool HasFindSupport(); + + bool HasKeyValueStore(); void GetExportedResources(Json::Value& target, int64_t since, @@ -724,5 +748,17 @@ void ExecuteCount(uint64_t& count, const FindRequest& request); + + void StoreKeyValue(const std::string& pluginId, + const std::string& key, + const std::string& value); + + void DeleteKeyValue(const std::string& pluginId, + const std::string& key); + + bool GetKeyValue(std::string& value, + const std::string& pluginId, + const std::string& key); + }; }
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp Mon Apr 07 13:23:11 2025 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp Wed May 07 19:27:06 2025 +0200 @@ -95,6 +95,7 @@ static const char* const HAS_LABELS = "HasLabels"; static const char* const CAPABILITIES = "Capabilities"; static const char* const HAS_EXTENDED_CHANGES = "HasExtendedChanges"; + static const char* const HAS_KEY_VALUE_STORE = "HasKeyValueStore"; static const char* const HAS_EXTENDED_FIND = "HasExtendedFind"; static const char* const READ_ONLY = "ReadOnly"; @@ -211,6 +212,7 @@ result[CAPABILITIES] = Json::objectValue; result[CAPABILITIES][HAS_EXTENDED_CHANGES] = OrthancRestApi::GetIndex(call).HasExtendedChanges(); result[CAPABILITIES][HAS_EXTENDED_FIND] = OrthancRestApi::GetIndex(call).HasFindSupport(); + result[CAPABILITIES][HAS_KEY_VALUE_STORE] = OrthancRestApi::GetIndex(call).HasKeyValueStore(); call.GetOutput().AnswerJson(result); }